From 2d6310f3ae62f0564c873193f2699fc72bebef99 Mon Sep 17 00:00:00 2001 From: luoyu Date: Mon, 2 Jun 2025 21:30:55 +0800 Subject: [PATCH] boyuehasfj-java --- .gitignore | 50 + .vscode/launch.json | 15 + .vscode/settings.json | 68 + LICENSE | 20 + README.md | 214 + bin/clean.bat | 12 + bin/package.bat | 12 + bin/run.bat | 14 + boyue-admin/pom.xml | 118 + .../main/java/com/boyue/BoYueApplication.java | 43 + .../com/boyue/BoYueServletInitializer.java | 18 + .../controller/common/CaptchaController.java | 81 + .../controller/common/CommonController.java | 376 ++ .../controller/monitor/CacheController.java | 122 + .../controller/monitor/ServerController.java | 32 + .../monitor/SysLogininforController.java | 96 + .../monitor/SysOperlogController.java | 102 + .../monitor/SysUserOnlineController.java | 72 + .../system/SysConfigController.java | 139 + .../controller/system/SysDeptController.java | 132 + .../system/SysDictDataController.java | 126 + .../system/SysDictTypeController.java | 135 + .../controller/system/SysIndexController.java | 34 + .../controller/system/SysLoginController.java | 112 + .../controller/system/SysMenuController.java | 138 + .../system/SysNoticeController.java | 97 + .../controller/system/SysPostController.java | 129 + .../system/SysProfileController.java | 149 + .../system/SysRegisterController.java | 42 + .../controller/system/SysRoleController.java | 260 ++ .../controller/system/SysUserController.java | 247 ++ .../boyue/web/core/config/SwaggerConfig.java | 104 + .../META-INF/spring-devtools.properties | 1 + .../src/main/resources/application-auth.yml | 37 + .../src/main/resources/application-druid.yml | 91 + .../src/main/resources/application-file.yml | 53 + .../src/main/resources/application-lp.yml | 7 + .../main/resources/application-middleware.yml | 39 + .../main/resources/application-mybatis.yml | 27 + .../src/main/resources/application-pay.yml | 30 + .../src/main/resources/application.yml | 121 + boyue-admin/src/main/resources/banner.txt | 24 + .../main/resources/i18n/messages.properties | 38 + boyue-admin/src/main/resources/logback.xml | 93 + .../main/resources/mybatis/mybatis-config.xml | 24 + .../main/resources/templates/hasfj/case.html | 71 + .../resources/templates/hasfj/case_list.html | 80 + .../main/resources/templates/hasfj/form.html | 74 + .../resources/templates/hasfj/form_list.html | 86 + .../main/resources/templates/hasfj/index.html | 225 + .../main/resources/templates/hasfj/law.html | 61 + .../resources/templates/hasfj/law_list.html | 80 + boyue-admin/src/main/resources/vite.config.ts | 32 + boyue-auth/boyue-auth-common/pom.xml | 33 + .../boyue/auth/common/domain/OauthUser.java | 293 ++ .../common/enums/OauthVerificationUse.java | 59 + .../auth/common/mapper/OauthUserMapper.java | 112 + .../common/service/IOauthUserService.java | 102 + .../service/OauthVerificationCodeService.java | 15 + .../boyue/auth/common/service/TfaService.java | 73 + .../service/impl/OauthUserServiceImpl.java | 149 + .../auth/common/utils/RandomCodeUtil.java | 28 + .../mapper/common/OauthUserMapper.xml | 169 + boyue-auth/boyue-auth-starter/pom.xml | 50 + .../auth/controller/OauthUserController.java | 109 + .../boyue/auth/controller/TfaController.java | 110 + boyue-auth/boyue-oauth-justauth/pom.xml | 37 + .../controller/SysAuthController.java | 180 + .../oauth/justauth/utils/JustAuthUtils.java | 177 + boyue-auth/boyue-oauth-wx/pom.xml | 29 + .../oauth/wx/constant/WxMiniAppConstant.java | 42 + .../oauth/wx/constant/WxPubConstant.java | 41 + .../wx/controller/WxLoginController.java | 69 + .../Impl/WxMiniAppLoginServiceImpl.java | 76 + .../service/Impl/WxPubLoginServiceImpl.java | 77 + .../oauth/wx/service/WxLoginService.java | 34 + ...itional-spring-configuration-metadata.json | 34 + boyue-auth/boyue-tfa-email/pom.xml | 41 + .../boyue/tfa/email/service/IMailService.java | 7 + .../email/service/impl/MailServiceImpl.java | 174 + .../com/boyue/tfa/email/utils/EmailUtil.java | 25 + boyue-auth/boyue-tfa-phone/pom.xml | 33 + .../boyue/tfa/phone/config/DySmsConfig.java | 47 + .../boyue/tfa/phone/domain/DySmsTemplate.java | 46 + .../boyue/tfa/phone/service/DySmsService.java | 14 + .../phone/service/Impl/DySmsServiceImpl.java | 185 + .../com/boyue/tfa/phone/utils/DySmsUtil.java | 88 + ...itional-spring-configuration-metadata.json | 19 + boyue-auth/pom.xml | 118 + boyue-common/pom.xml | 146 + .../boyue/common/annotation/Anonymous.java | 19 + .../boyue/common/annotation/DataScope.java | 33 + .../boyue/common/annotation/DataSource.java | 34 + .../com/boyue/common/annotation/Excel.java | 206 + .../com/boyue/common/annotation/Excels.java | 18 + .../java/com/boyue/common/annotation/Log.java | 51 + .../boyue/common/annotation/RateLimiter.java | 41 + .../boyue/common/annotation/RepeatSubmit.java | 31 + .../boyue/common/annotation/Sensitive.java | 25 + .../com/boyue/common/config/BoYueConfig.java | 135 + .../serializer/SensitiveJsonSerializer.java | 68 + .../boyue/common/constant/CacheConstants.java | 68 + .../com/boyue/common/constant/Constants.java | 175 + .../com/boyue/common/constant/HttpStatus.java | 94 + .../common/constant/ScheduleConstants.java | 50 + .../boyue/common/constant/UserConstants.java | 80 + .../core/controller/BaseController.java | 202 + .../boyue/common/core/domain/AjaxResult.java | 185 + .../boyue/common/core/domain/BaseEntity.java | 129 + .../com/boyue/common/core/domain/Message.java | 172 + .../java/com/boyue/common/core/domain/R.java | 122 + .../boyue/common/core/domain/TreeEntity.java | 79 + .../boyue/common/core/domain/TreeSelect.java | 77 + .../common/core/domain/entity/SysDept.java | 220 + .../core/domain/entity/SysDictData.java | 189 + .../core/domain/entity/SysDictType.java | 104 + .../common/core/domain/entity/SysMenu.java | 260 ++ .../common/core/domain/entity/SysRole.java | 259 ++ .../common/core/domain/entity/SysUser.java | 349 ++ .../common/core/domain/model/LoginBody.java | 97 + .../common/core/domain/model/LoginUser.java | 266 ++ .../core/domain/model/RegisterBody.java | 14 + .../boyue/common/core/page/PageDomain.java | 101 + .../boyue/common/core/page/TableDataInfo.java | 92 + .../boyue/common/core/page/TableSupport.java | 56 + .../security/service/IPermissionService.java | 56 + .../boyue/common/core/text/CharsetKit.java | 86 + .../com/boyue/common/core/text/Convert.java | 1006 +++++ .../boyue/common/core/text/StrFormatter.java | 92 + .../boyue/common/enums/BusinessStatus.java | 20 + .../com/boyue/common/enums/BusinessType.java | 59 + .../boyue/common/enums/DataSourceType.java | 18 + .../boyue/common/enums/DesensitizedType.java | 60 + .../com/boyue/common/enums/HttpMethod.java | 52 + .../com/boyue/common/enums/LimitType.java | 29 + .../com/boyue/common/enums/OperatorType.java | 24 + .../com/boyue/common/enums/UserStatus.java | 35 + .../common/exception/DemoModeException.java | 15 + .../common/exception/GlobalException.java | 58 + .../common/exception/ServiceException.java | 74 + .../boyue/common/exception/UtilException.java | 26 + .../common/exception/base/BaseException.java | 97 + .../common/exception/file/FileException.java | 19 + .../FileNameLengthLimitExceededException.java | 16 + .../file/FileSizeLimitExceededException.java | 16 + .../exception/file/FileUploadException.java | 61 + .../file/InvalidExtensionException.java | 80 + .../common/exception/job/TaskException.java | 40 + .../exception/user/BlackListException.java | 16 + .../exception/user/CaptchaException.java | 16 + .../user/CaptchaExpireException.java | 16 + .../user/IpRetryLimitExceedException.java | 15 + .../common/exception/user/UserException.java | 18 + .../user/UserNotExistsException.java | 16 + .../user/UserPasswordNotMatchException.java | 16 + ...UserPasswordRetryLimitExceedException.java | 16 + .../filter/PropertyPreExcludeFilter.java | 24 + .../boyue/common/filter/RepeatableFilter.java | 52 + .../filter/RepeatedlyRequestWrapper.java | 76 + .../com/boyue/common/filter/XssFilter.java | 75 + .../filter/XssHttpServletRequestWrapper.java | 111 + .../handler/GenericListTypeHandler.java | 131 + .../boyue/common/service/cache/CacheKeys.java | 10 + .../common/service/cache/CacheNoTimeOut.java | 7 + .../common/service/cache/CacheTimeOut.java | 10 + .../datasource/AfterCreateDataSource.java | 10 + .../service/datasource/CreateDataSource.java | 9 + .../mybatis/CreateSqlSessionFactory.java | 10 + .../java/com/boyue/common/utils/Arith.java | 114 + .../com/boyue/common/utils/CacheUtils.java | 161 + .../com/boyue/common/utils/DateUtils.java | 174 + .../boyue/common/utils/DesensitizedUtil.java | 49 + .../com/boyue/common/utils/DictUtils.java | 204 + .../com/boyue/common/utils/ExceptionUtil.java | 39 + .../java/com/boyue/common/utils/LogUtils.java | 18 + .../com/boyue/common/utils/MessageUtils.java | 26 + .../com/boyue/common/utils/MybatisUtils.java | 77 + .../com/boyue/common/utils/PageUtils.java | 35 + .../com/boyue/common/utils/SecurityUtils.java | 178 + .../com/boyue/common/utils/ServletUtils.java | 218 + .../com/boyue/common/utils/StringUtils.java | 588 +++ .../java/com/boyue/common/utils/Threads.java | 99 + .../boyue/common/utils/bean/BeanUtils.java | 110 + .../common/utils/bean/BeanValidators.java | 24 + .../common/utils/file/FileTypeUtils.java | 77 + .../boyue/common/utils/file/FileUtils.java | 405 ++ .../boyue/common/utils/file/ImageUtils.java | 101 + .../common/utils/file/MimeTypeUtils.java | 61 + .../boyue/common/utils/html/EscapeUtil.java | 167 + .../boyue/common/utils/html/HTMLFilter.java | 570 +++ .../common/utils/http/HttpClientUtil.java | 322 ++ .../com/boyue/common/utils/http/HttpConf.java | 33 + .../boyue/common/utils/http/HttpHelper.java | 55 + .../boyue/common/utils/http/HttpUtils.java | 748 ++++ .../http/IdleConnectionMonitorThread.java | 73 + .../boyue/common/utils/ip/AddressUtils.java | 56 + .../com/boyue/common/utils/ip/IpUtils.java | 382 ++ .../common/utils/poi/ExcelHandlerAdapter.java | 19 + .../com/boyue/common/utils/poi/ExcelUtil.java | 1903 ++++++++ .../common/utils/reflect/ReflectUtils.java | 410 ++ .../com/boyue/common/utils/sign/Base64.java | 291 ++ .../com/boyue/common/utils/sign/Md5Utils.java | 166 + .../common/utils/spring/SpringUtils.java | 158 + .../com/boyue/common/utils/sql/SqlUtil.java | 66 + .../com/boyue/common/utils/uuid/IdUtils.java | 49 + .../java/com/boyue/common/utils/uuid/Seq.java | 86 + .../com/boyue/common/utils/uuid/UUID.java | 484 ++ .../main/java/com/boyue/common/xss/Xss.java | 27 + .../com/boyue/common/xss/XssValidator.java | 34 + boyue-file/boyue-file-common/pom.xml | 24 + .../com/boyue/file/domain/SysFileInfo.java | 60 + .../boyue/file/domain/SysFilePartETag.java | 55 + .../local/config/LocalBucketProperties.java | 10 + .../file/local/config/LocalManagement.java | 81 + .../boyue/file/local/domain/LocalBucket.java | 218 + .../file/local/service/LocalFileService.java | 82 + .../boyue/file/mapper/SysFileInfoMapper.java | 61 + .../file/service/ISysFileInfoService.java | 61 + .../service/impl/SysFileInfoServiceImpl.java | 96 + .../com/boyue/file/storage/StorageBucket.java | 109 + .../com/boyue/file/storage/StorageEntity.java | 12 + .../boyue/file/storage/StorageManagement.java | 25 + .../file/storage/StorageManagements.java | 32 + .../boyue/file/storage/StorageService.java | 122 + .../boyue/file/utils/FileOperateUtils.java | 255 ++ ...itional-spring-configuration-metadata.json | 19 + .../mapper/file/SysFileInfoMapper.xml | 119 + boyue-file/boyue-file-minio/pom.xml | 33 + .../minio/config/MinioBucketProperties.java | 12 + .../file/minio/config/MinioManagement.java | 117 + .../boyue/file/minio/domain/MinioBucket.java | 224 + .../file/minio/domain/MinioEntityVO.java | 45 + .../exception/MinioClientErrorException.java | 16 + .../MinioClientNotFundException.java | 15 + .../file/minio/service/MinioFileService.java | 87 + ...itional-spring-configuration-metadata.json | 19 + boyue-file/boyue-file-oss-alibaba/pom.xml | 28 + .../config/AliOssBucketProperties.java | 12 + .../oss/alibaba/config/AliOssManagement.java | 113 + .../file/oss/alibaba/domain/AliOssBucket.java | 176 + .../oss/alibaba/domain/AliOssEntityVO.java | 34 + .../exception/AliOssClientErrorException.java | 11 + .../AliOssClientNotFundException.java | 27 + .../alibaba/service/AliOssFileService.java | 93 + ...itional-spring-configuration-metadata.json | 19 + boyue-file/boyue-file-starter/pom.xml | 29 + .../boyue/file/controller/FileController.java | 314 ++ .../controller/SysFileInfoController.java | 113 + boyue-file/pom.xml | 65 + boyue-framework/pom.xml | 74 + .../framework/aspectj/DataScopeAspect.java | 165 + .../framework/aspectj/DataSourceAspect.java | 70 + .../boyue/framework/aspectj/LogAspect.java | 226 + .../framework/config/ApplicationConfig.java | 31 + .../boyue/framework/config/CaptchaConfig.java | 83 + .../boyue/framework/config/DruidConfig.java | 94 + .../config/DynamicDataSourceProperties.java | 115 + .../boyue/framework/config/FilterConfig.java | 58 + .../boyue/framework/config/I18nConfig.java | 44 + .../framework/config/KaptchaTextCreator.java | 69 + .../boyue/framework/config/MyBatisConfig.java | 58 + .../framework/config/ResourcesConfig.java | 77 + .../framework/config/SecurityConfig.java | 146 + .../boyue/framework/config/ServerConfig.java | 32 + .../config/SqlSessionFactoryConfig.java | 46 + .../framework/config/ThreadPoolConfig.java | 65 + .../config/properties/DruidProperties.java | 195 + .../properties/PermitAllUrlProperties.java | 89 + .../datasource/DataSourceCreate.java | 43 + .../datasource/DataSourceManagement.java | 104 + .../datasource/DynamicDataSource.java | 28 + .../DynamicDataSourceContextHolder.java | 60 + .../datasource/DynamicSqlSessionTemplate.java | 311 ++ .../datasource/DynamicTransactionManager.java | 28 + .../interceptor/RepeatSubmitInterceptor.java | 55 + .../impl/SameUrlDataInterceptor.java | 104 + .../boyue/framework/manager/AsyncManager.java | 55 + .../framework/manager/ShutdownManager.java | 43 + .../manager/factory/AsyncFactory.java | 102 + .../context/AuthenticationContextHolder.java | 28 + .../context/PermissionContextHolder.java | 27 + .../filter/JwtAuthenticationTokenFilter.java | 44 + .../handle/AuthenticationEntryPointImpl.java | 34 + .../handle/LogoutSuccessHandlerImpl.java | 55 + .../boyue/framework/web/domain/Server.java | 240 + .../framework/web/domain/server/Cpu.java | 101 + .../framework/web/domain/server/Jvm.java | 130 + .../framework/web/domain/server/Mem.java | 61 + .../framework/web/domain/server/Sys.java | 84 + .../framework/web/domain/server/SysFile.java | 114 + .../web/exception/GlobalExceptionHandler.java | 137 + .../web/service/PermissionService.java | 130 + .../web/service/SysLoginService.java | 186 + .../web/service/SysPasswordService.java | 125 + .../web/service/SysPermissionService.java | 76 + .../web/service/SysRegisterService.java | 110 + .../framework/web/service/TokenService.java | 203 + .../web/service/UserDetailsServiceImpl.java | 72 + .../main/resources/mybatis/mybatis-config.xml | 24 + .../boyue-middleware-rabbitmq/pom.xml | 33 + .../rabbitmq/DirectRabbitConfig.java | 80 + .../middleware/rabbitmq/DirectReceiver.java | 19 + .../rabbitmq/SendMessageController.java | 41 + .../boyue-middleware-redis/pom.xml | 33 + .../redis/aspectj/RateLimiterAspect.java | 92 + .../config/FastJson2JsonRedisSerializer.java | 54 + .../middleware/redis/config/RedisConfig.java | 97 + .../controller/RedisCacheController.java | 64 + .../middleware/redis/utils/RedisCache.java | 288 ++ .../boyue-middleware-starter/pom.xml | 38 + boyue-middleware/pom.xml | 44 + boyue-models/boyue-flowable/pom.xml | 64 + .../common/constant/ProcessConstants.java | 80 + .../flowable/common/enums/FlowComment.java | 43 + .../flowable/common/expand/el/BaseEl.java | 12 + .../flowable/common/expand/el/FlowEl.java | 34 + .../boyue/flowable/config/FlowableConfig.java | 42 + .../controller/FlowDefinitionController.java | 228 + .../controller/FlowInstanceController.java | 72 + .../controller/FlowTaskController.java | 289 ++ .../controller/SysExpressionController.java | 100 + .../controller/SysListenerController.java | 100 + .../boyue/flowable/domain/SysDeployForm.java | 65 + .../boyue/flowable/domain/SysExpression.java | 96 + .../boyue/flowable/domain/SysListener.java | 127 + .../boyue/flowable/domain/SysTaskForm.java | 66 + .../flowable/domain/dto/FlowCommentDto.java | 25 + .../flowable/domain/dto/FlowFromFieldDTO.java | 15 + .../flowable/domain/dto/FlowNextDto.java | 30 + .../flowable/domain/dto/FlowProcDefDto.java | 56 + .../flowable/domain/dto/FlowSaveXmlVo.java | 28 + .../flowable/domain/dto/FlowTaskDto.java | 104 + .../flowable/domain/dto/FlowViewerDto.java | 23 + .../boyue/flowable/domain/vo/FlowQueryVo.java | 32 + .../boyue/flowable/domain/vo/FlowTaskVo.java | 55 + .../flowable/domain/vo/ReturnTaskNodeVo.java | 22 + .../expression/FlowDelegationExpression.java | 16 + .../flowable/factory/FlowServiceFactory.java | 48 + .../flow/CustomProcessDiagramCanvas.java | 457 ++ .../flow/CustomProcessDiagramGenerator.java | 431 ++ .../boyue/flowable/flow/FindNextNodeUtil.java | 286 ++ .../boyue/flowable/flow/FlowableUtils.java | 772 ++++ .../listener/FlowExecutionListener.java | 37 + .../flowable/listener/FlowTaskListener.java | 38 + .../flowable/mapper/FlowDeployMapper.java | 23 + .../flowable/mapper/SysDeployFormMapper.java | 74 + .../flowable/mapper/SysExpressionMapper.java | 63 + .../flowable/mapper/SysListenerMapper.java | 62 + .../flowable/mapper/SysTaskFormMapper.java | 64 + .../service/IFlowDefinitionService.java | 80 + .../service/IFlowInstanceService.java | 53 + .../flowable/service/IFlowTaskService.java | 218 + .../service/ISysDeployFormService.java | 69 + .../service/ISysExpressionService.java | 62 + .../flowable/service/ISysListenerService.java | 62 + .../impl/FlowDefinitionServiceImpl.java | 246 ++ .../service/impl/FlowInstanceServiceImpl.java | 140 + .../service/impl/FlowTaskServiceImpl.java | 1315 ++++++ .../impl/SysDeployFormServiceImpl.java | 106 + .../impl/SysExpressionServiceImpl.java | 98 + .../service/impl/SysListenerServiceImpl.java | 98 + .../mapper/flowable/FlowDeployMapper.xml | 31 + .../mapper/flowable/SysDeployFormMapper.xml | 75 + .../mapper/flowable/SysExpressionMapper.xml | 90 + .../mapper/flowable/SysListenerMapper.xml | 115 + .../mapper/flowable/SysTaskFormMapper.xml | 61 + boyue-models/boyue-form/pom.xml | 27 + .../form/controller/FormDataController.java | 118 + .../controller/FormTemplateController.java | 113 + .../java/com/boyue/form/domain/FormData.java | 140 + .../com/boyue/form/domain/FormTemplate.java | 131 + .../com/boyue/form/mapper/FormDataMapper.java | 61 + .../boyue/form/mapper/FormTemplateMapper.java | 61 + .../boyue/form/service/IFormDataService.java | 61 + .../form/service/IFormTemplateService.java | 61 + .../service/impl/FormDataServiceImpl.java | 96 + .../service/impl/FormTemplateServiceImpl.java | 96 + .../resources/mapper/form/FormDataMapper.xml | 129 + .../mapper/form/FormTemplateMapper.xml | 108 + boyue-models/boyue-generator/pom.xml | 40 + .../com/boyue/generator/config/GenConfig.java | 73 + .../generator/constant/GenConstants.java | 129 + .../generator/controller/GenController.java | 267 ++ .../boyue/generator/domain/GenJoinTable.java | 45 + .../com/boyue/generator/domain/GenTable.java | 149 + .../generator/domain/GenTableColumn.java | 194 + .../boyue/generator/domain/vo/GenTableVo.java | 108 + .../generator/mapper/GenJoinTableMapper.java | 40 + .../mapper/GenTableColumnMapper.java | 60 + .../generator/mapper/GenTableMapper.java | 90 + .../service/GenJoinTableServiceImpl.java | 145 + .../service/GenTableColumnServiceImpl.java | 68 + .../service/GenTableServiceImpl.java | 497 +++ .../service/IGenJoinTableService.java | 46 + .../service/IGenTableColumnService.java | 44 + .../generator/service/IGenTableService.java | 128 + .../com/boyue/generator/util/GenUtils.java | 257 ++ .../generator/util/VelocityInitializer.java | 34 + .../boyue/generator/util/VelocityUtils.java | 448 ++ .../src/main/resources/generator.yml | 10 + .../mapper/generator/GenJoinTableMapper.xml | 92 + .../mapper/generator/GenTableColumnMapper.xml | 147 + .../mapper/generator/GenTableMapper.xml | 223 + .../main/resources/vm/java/controller.java.vm | 124 + .../src/main/resources/vm/java/domain.java.vm | 143 + .../src/main/resources/vm/java/mapper.java.vm | 91 + .../main/resources/vm/java/service.java.vm | 61 + .../resources/vm/java/serviceImpl.java.vm | 169 + .../main/resources/vm/java/sub-domain.java.vm | 75 + .../src/main/resources/vm/js/api.js.vm | 44 + .../src/main/resources/vm/sql/sql.vm | 22 + .../src/main/resources/vm/uniapp/edit.vue.vm | 155 + .../src/main/resources/vm/uniapp/list.vue.vm | 140 + .../src/main/resources/vm/uniapp/show.vue.vm | 85 + .../resources/vm/vue/v2/index-tree.vue.vm | 575 +++ .../src/main/resources/vm/vue/v2/index.vue.vm | 672 +++ .../resources/vm/vue/v3/index-tree.vue.vm | 474 ++ .../src/main/resources/vm/vue/v3/index.vue.vm | 642 +++ .../src/main/resources/vm/xml/mapper.xml.vm | 167 + boyue-models/boyue-hasfj/pom.xml | 37 + .../boyue/hasfj/controller/ApiController.java | 46 + .../hasfj/controller/FrontendController.java | 47 + .../controller/HtmlPageApiController.java | 569 +++ .../controller/HtmlPageViewController.java | 171 + .../hasfj/controller/HtmlPagesController.java | 296 ++ .../com/boyue/hasfj/domain/HtmlPages.java | 250 ++ .../boyue/hasfj/mapper/HtmlPagesMapper.java | 85 + .../hasfj/service/IHtmlPagesService.java | 111 + .../service/impl/HtmlPagesServiceImpl.java | 403 ++ .../mapper/hasfj/HtmlPagesMapper.xml | 149 + boyue-models/boyue-message/pom.xml | 45 + .../modelMessage/annotation/MessageLog.java | 36 + .../controller/MessageSystemController.java | 214 + .../controller/MessageTemplateController.java | 116 + .../controller/MessageVariableController.java | 139 + .../modelMessage/domain/MessageSystem.java | 159 + .../modelMessage/domain/MessageTemplate.java | 134 + .../modelMessage/domain/MessageVariable.java | 107 + .../boyue/modelMessage/enums/MessageType.java | 25 + .../boyue/modelMessage/enums/SendMode.java | 32 + .../mapper/MessageSystemMapper.java | 121 + .../mapper/MessageTemplateMapper.java | 73 + .../mapper/MessageVariableMapper.java | 75 + .../service/IMessageSystemService.java | 108 + .../service/IMessageTemplateService.java | 62 + .../service/IMessageVariableService.java | 67 + .../impl/MessageSystemServiceImpl.java | 305 ++ .../impl/MessageTemplateServiceImpl.java | 104 + .../impl/MessageVariableServiceImpl.java | 129 + .../modelMessage/utils/MessageLogAspect.java | 68 + .../utils/MessageSystemUtils.java | 224 + .../modelMessage/MessageSystemMapper.xml | 118 + .../modelMessage/MessageTemplateMapper.xml | 93 + .../modelMessage/MessageVariableMapper.xml | 92 + boyue-models/boyue-models-starter/pom.xml | 57 + boyue-models/boyue-online/pom.xml | 26 + .../online/controller/OnLineController.java | 141 + .../online/controller/OnlineDbController.java | 39 + .../online/controller/OnlineMbController.java | 116 + .../com/boyue/online/domain/OnlineMb.java | 221 + .../boyue/online/mapper/OnlineDbMapper.java | 17 + .../boyue/online/mapper/OnlineMbMapper.java | 62 + .../online/service/IOnlineMbService.java | 62 + .../service/impl/OnlineMbServiceImpl.java | 95 + .../com/boyue/online/utils/SqlMapper.java | 411 ++ .../mapper/online/OnlineDbMapper.xml | 16 + .../mapper/online/OnlineMbMapper.xml | 119 + boyue-models/boyue-quartz/pom.xml | 39 + .../boyue/quartz/config/ScheduleConfig.java | 57 + .../quartz/controller/SysJobController.java | 188 + .../controller/SysJobLogController.java | 95 + .../java/com/boyue/quartz/domain/SysJob.java | 171 + .../com/boyue/quartz/domain/SysJobLog.java | 155 + .../boyue/quartz/mapper/SysJobLogMapper.java | 64 + .../com/boyue/quartz/mapper/SysJobMapper.java | 67 + .../quartz/service/ISysJobLogService.java | 56 + .../boyue/quartz/service/ISysJobService.java | 102 + .../service/impl/SysJobLogServiceImpl.java | 87 + .../service/impl/SysJobServiceImpl.java | 261 ++ .../java/com/boyue/quartz/task/RyTask.java | 28 + .../boyue/quartz/util/AbstractQuartzJob.java | 107 + .../java/com/boyue/quartz/util/CronUtils.java | 63 + .../com/boyue/quartz/util/JobInvokeUtil.java | 182 + .../QuartzDisallowConcurrentExecution.java | 21 + .../boyue/quartz/util/QuartzJobExecution.java | 19 + .../com/boyue/quartz/util/ScheduleUtils.java | 141 + .../mapper/quartz/SysJobLogMapper.xml | 94 + .../resources/mapper/quartz/SysJobMapper.xml | 111 + boyue-models/pom.xml | 97 + boyue-pay/boyue-pay-alipay/pom.xml | 31 + .../boyue/pay/alipay/config/AliPayConfig.java | 80 + .../pay/alipay/service/IAliPayService.java | 9 + .../alipay/service/Impl/AliPayService.java | 126 + ...itional-spring-configuration-metadata.json | 29 + boyue-pay/boyue-pay-common/pom.xml | 28 + .../java/com/boyue/pay/domain/PayInvoice.java | 168 + .../java/com/boyue/pay/domain/PayOrder.java | 141 + .../boyue/pay/mapper/PayInvoiceMapper.java | 62 + .../com/boyue/pay/mapper/PayOrderMapper.java | 73 + .../boyue/pay/service/IPayInvoiceService.java | 62 + .../boyue/pay/service/IPayOrderService.java | 80 + .../com/boyue/pay/service/PayService.java | 16 + .../service/impl/PayInvoiceServiceImpl.java | 98 + .../pay/service/impl/PayOrderServiceImpl.java | 117 + .../resources/mapper/pay/PayInvoiceMapper.xml | 108 + .../resources/mapper/pay/PayOrderMapper.xml | 119 + boyue-pay/boyue-pay-sqb/pom.xml | 27 + .../com/boyue/pay/sqb/config/SqbConfig.java | 116 + .../boyue/pay/sqb/service/ISqbPayService.java | 6 + .../pay/sqb/service/Impl/SQBServiceImpl.java | 333 ++ ...itional-spring-configuration-metadata.json | 49 + boyue-pay/boyue-pay-starter/pom.xml | 46 + .../boyue/pay/controller/PayController.java | 98 + .../pay/controller/PayInvoiceController.java | 116 + .../pay/controller/PayOrderController.java | 133 + boyue-pay/boyue-pay-wx/pom.xml | 31 + .../com/boyue/pay/wx/config/WxPayConfig.java | 139 + .../boyue/pay/wx/service/IWxPayService.java | 6 + .../pay/wx/service/Impl/WxPayService.java | 124 + ...itional-spring-configuration-metadata.json | 39 + boyue-pay/pom.xml | 75 + boyue-plugins/boyue-atomikos/pom.xml | 33 + .../boyue/atomikos/config/AtomikosConfig.java | 65 + .../datasource/AtomikosDataSourceCreate.java | 41 + boyue-plugins/boyue-ehcache/pom.xml | 33 + .../boyue/ehcache/config/Ehcache3Cache.java | 62 + .../boyue/ehcache/config/Ehcache3Config.java | 52 + .../boyue-mybatis-interceptor/pom.xml | 26 + .../annotation/DataSecurity.java | 16 + .../annotation/MybatisHandlerOrder.java | 10 + .../aspectj/DataSecurityAspect.java | 77 + .../context/page/PageContextHolder.java | 44 + .../context/page/model/BoYueTableData.java | 25 + .../context/page/model/PageInfo.java | 63 + .../context/page/model/TableInfo.java | 22 + .../context/sqlContext/SqlContextHolder.java | 55 + .../enums/DataSecurityStrategy.java | 12 + .../mybatisinterceptor/enums/SqlType.java | 22 + .../handler/MybatisAfterHandler.java | 7 + .../handler/MybatisPreHandler.java | 15 + .../dataSecurity/DataSecurityPreHandler.java | 133 + .../handler/page/PageAfterHandler.java | 30 + .../handler/page/PagePreHandler.java | 144 + .../mybatis/MybatisInterceptor.java | 108 + .../model/JoinTableModel.java | 85 + .../mybatisinterceptor/model/WhereModel.java | 67 + .../util/DataSecurityUtil.java | 14 + .../mybatisinterceptor/util/PageUtils.java | 30 + .../mybatisinterceptor/util/SqlUtil.java | 64 + boyue-plugins/boyue-mybatis-jpa/pom.xml | 33 + .../com/boyue/mybatis/annotation/Column.java | 18 + .../boyue/mybatis/annotation/ColumnMap.java | 25 + .../mybatis/annotation/EnableTableMap.java | 35 + .../com/boyue/mybatis/annotation/Query.java | 27 + .../com/boyue/mybatis/annotation/Table.java | 14 + .../boyue/mybatis/domain/BaseColumnInfo.java | 54 + .../com/boyue/mybatis/domain/ColumnInfo.java | 39 + .../boyue/mybatis/domain/MapColumnInfo.java | 45 + .../com/boyue/mybatis/domain/TableInfo.java | 206 + .../com/boyue/mybatis/enums/QueryEnum.java | 29 + .../com/boyue/mybatis/mapper/JPAMapper.java | 30 + .../com/boyue/mybatis/service/JPAService.java | 18 + .../mybatis/service/impl/JPAServiceImpl.java | 40 + .../com/boyue/mybatis/utils/QueryUtil.java | 105 + .../boyue/mybatis/utils/QueryWrapperUtil.java | 84 + .../com/boyue/mybatis/utils/SQLGenerator.java | 196 + .../boyue/mybatis/utils/TableContainer.java | 31 + boyue-plugins/boyue-mybatis-plus/pom.xml | 32 + .../mybatisplus/config/MybatisPlusConfig.java | 99 + .../controller/SysStudentController.java | 107 + .../boyue/mybatisplus/domain/SysStudent.java | 132 + .../mybatisplus/mapper/SysStudentMapper.java | 14 + .../service/ISysStudentService.java | 21 + .../service/impl/SysStudentServiceImpl.java | 43 + boyue-plugins/boyue-netty/pom.xml | 30 + .../netty/websocket/NettyServerRunner.java | 28 + .../annotations/NettyWebSocketEndpoint.java | 12 + .../endpoints/TestNettyWebSocket.java | 44 + .../NettyWebSocketEndpointHandler.java | 63 + .../nettyServer/NettyWebSocketServer.java | 89 + .../nettyServer/handler/WebSocketHandler.java | 167 + .../netty/websocket/utils/CommonUtil.java | 78 + boyue-plugins/boyue-plugins-starter/pom.xml | 66 + boyue-plugins/boyue-websocket/pom.xml | 31 + .../com/boyue/websocket/SemaphoreUtils.java | 59 + .../com/boyue/websocket/WebSocketConfig.java | 20 + .../com/boyue/websocket/WebSocketServer.java | 105 + .../com/boyue/websocket/WebSocketUsers.java | 142 + boyue-plugins/pom.xml | 123 + boyue-system/pom.xml | 28 + .../com/boyue/system/domain/SysCache.java | 88 + .../com/boyue/system/domain/SysConfig.java | 120 + .../boyue/system/domain/SysLogininfor.java | 157 + .../com/boyue/system/domain/SysNotice.java | 111 + .../com/boyue/system/domain/SysOperLog.java | 291 ++ .../java/com/boyue/system/domain/SysPost.java | 134 + .../com/boyue/system/domain/SysRoleDept.java | 51 + .../com/boyue/system/domain/SysRoleMenu.java | 51 + .../boyue/system/domain/SysUserOnline.java | 124 + .../com/boyue/system/domain/SysUserPost.java | 51 + .../com/boyue/system/domain/SysUserRole.java | 51 + .../com/boyue/system/domain/vo/MetaVo.java | 106 + .../com/boyue/system/domain/vo/RouterVo.java | 148 + .../boyue/system/mapper/SysConfigMapper.java | 76 + .../boyue/system/mapper/SysDeptMapper.java | 118 + .../system/mapper/SysDictDataMapper.java | 95 + .../system/mapper/SysDictTypeMapper.java | 83 + .../system/mapper/SysLogininforMapper.java | 42 + .../boyue/system/mapper/SysMenuMapper.java | 125 + .../boyue/system/mapper/SysNoticeMapper.java | 60 + .../boyue/system/mapper/SysOperLogMapper.java | 70 + .../boyue/system/mapper/SysPostMapper.java | 99 + .../system/mapper/SysRoleDeptMapper.java | 44 + .../boyue/system/mapper/SysRoleMapper.java | 107 + .../system/mapper/SysRoleMenuMapper.java | 44 + .../boyue/system/mapper/SysUserMapper.java | 144 + .../system/mapper/SysUserPostMapper.java | 44 + .../system/mapper/SysUserRoleMapper.java | 62 + .../system/service/ISysConfigService.java | 89 + .../boyue/system/service/ISysDeptService.java | 124 + .../system/service/ISysDictDataService.java | 60 + .../system/service/ISysDictTypeService.java | 98 + .../system/service/ISysLogininforService.java | 40 + .../boyue/system/service/ISysMenuService.java | 144 + .../system/service/ISysNoticeService.java | 60 + .../system/service/ISysOperLogService.java | 70 + .../boyue/system/service/ISysPostService.java | 99 + .../boyue/system/service/ISysRoleService.java | 173 + .../system/service/ISysUserOnlineService.java | 48 + .../boyue/system/service/ISysUserService.java | 222 + .../service/impl/SysConfigServiceImpl.java | 226 + .../service/impl/SysDeptServiceImpl.java | 338 ++ .../service/impl/SysDictDataServiceImpl.java | 111 + .../service/impl/SysDictTypeServiceImpl.java | 226 + .../impl/SysLogininforServiceImpl.java | 65 + .../service/impl/SysMenuServiceImpl.java | 484 ++ .../service/impl/SysNoticeServiceImpl.java | 92 + .../service/impl/SysOperLogServiceImpl.java | 99 + .../service/impl/SysPostServiceImpl.java | 178 + .../service/impl/SysRoleServiceImpl.java | 424 ++ .../impl/SysUserOnlineServiceImpl.java | 96 + .../service/impl/SysUserServiceImpl.java | 515 +++ .../mapper/system/SysConfigMapper.xml | 117 + .../resources/mapper/system/SysDeptMapper.xml | 159 + .../mapper/system/SysDictDataMapper.xml | 124 + .../mapper/system/SysDictTypeMapper.xml | 105 + .../mapper/system/SysLogininforMapper.xml | 57 + .../resources/mapper/system/SysMenuMapper.xml | 206 + .../mapper/system/SysNoticeMapper.xml | 89 + .../mapper/system/SysOperLogMapper.xml | 157 + .../resources/mapper/system/SysPostMapper.xml | 122 + .../mapper/system/SysRoleDeptMapper.xml | 34 + .../resources/mapper/system/SysRoleMapper.xml | 152 + .../mapper/system/SysRoleMenuMapper.xml | 34 + .../resources/mapper/system/SysUserMapper.xml | 231 + .../mapper/system/SysUserPostMapper.xml | 34 + .../mapper/system/SysUserRoleMapper.xml | 44 + doc/image/code-edit.png | Bin 0 -> 180320 bytes doc/image/code-show.png | Bin 0 -> 170060 bytes doc/image/form-edit.png | Bin 0 -> 166287 bytes doc/image/logo.png | Bin 0 -> 4879 bytes doc/image/online-mb-code.png | Bin 0 -> 149041 bytes doc/image/online-mb-edit.png | Bin 0 -> 185794 bytes doc/image/online-mb-list.png | Bin 0 -> 149588 bytes doc/代码生成.drawio | 311 ++ doc/关于多数据源与分布式事务.drawio | 149 + doc/微信登录.drawio | 76 + doc/支付流程图.drawio | 43 + doc/支付系统订单状态图.drawio | 222 + doc/权限控制.md | 68 + doc/模块依赖关系.drawio | 100 + doc/登录+JWT逻辑.drawio | 131 + doc/若依环境使用手册.docx | Bin 0 -> 428430 bytes doc/邮箱或短信验证码登录注册重置流程图.drawio | 260 ++ doc/限流逻辑.drawio | 37 + pom.xml | 368 ++ sql/boyuehasfj.sql | 2362 ++++++++++ sql/hasfj/boyuehasfj.sql | 3879 +++++++++++++++++ 678 files changed, 78592 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 LICENSE create mode 100644 README.md create mode 100644 bin/clean.bat create mode 100644 bin/package.bat create mode 100644 bin/run.bat create mode 100644 boyue-admin/pom.xml create mode 100644 boyue-admin/src/main/java/com/boyue/BoYueApplication.java create mode 100644 boyue-admin/src/main/java/com/boyue/BoYueServletInitializer.java create mode 100644 boyue-admin/src/main/java/com/boyue/web/controller/common/CaptchaController.java create mode 100644 boyue-admin/src/main/java/com/boyue/web/controller/common/CommonController.java create mode 100644 boyue-admin/src/main/java/com/boyue/web/controller/monitor/CacheController.java create mode 100644 boyue-admin/src/main/java/com/boyue/web/controller/monitor/ServerController.java create mode 100644 boyue-admin/src/main/java/com/boyue/web/controller/monitor/SysLogininforController.java create mode 100644 boyue-admin/src/main/java/com/boyue/web/controller/monitor/SysOperlogController.java create mode 100644 boyue-admin/src/main/java/com/boyue/web/controller/monitor/SysUserOnlineController.java create mode 100644 boyue-admin/src/main/java/com/boyue/web/controller/system/SysConfigController.java create mode 100644 boyue-admin/src/main/java/com/boyue/web/controller/system/SysDeptController.java create mode 100644 boyue-admin/src/main/java/com/boyue/web/controller/system/SysDictDataController.java create mode 100644 boyue-admin/src/main/java/com/boyue/web/controller/system/SysDictTypeController.java create mode 100644 boyue-admin/src/main/java/com/boyue/web/controller/system/SysIndexController.java create mode 100644 boyue-admin/src/main/java/com/boyue/web/controller/system/SysLoginController.java create mode 100644 boyue-admin/src/main/java/com/boyue/web/controller/system/SysMenuController.java create mode 100644 boyue-admin/src/main/java/com/boyue/web/controller/system/SysNoticeController.java create mode 100644 boyue-admin/src/main/java/com/boyue/web/controller/system/SysPostController.java create mode 100644 boyue-admin/src/main/java/com/boyue/web/controller/system/SysProfileController.java create mode 100644 boyue-admin/src/main/java/com/boyue/web/controller/system/SysRegisterController.java create mode 100644 boyue-admin/src/main/java/com/boyue/web/controller/system/SysRoleController.java create mode 100644 boyue-admin/src/main/java/com/boyue/web/controller/system/SysUserController.java create mode 100644 boyue-admin/src/main/java/com/boyue/web/core/config/SwaggerConfig.java create mode 100644 boyue-admin/src/main/resources/META-INF/spring-devtools.properties create mode 100644 boyue-admin/src/main/resources/application-auth.yml create mode 100644 boyue-admin/src/main/resources/application-druid.yml create mode 100644 boyue-admin/src/main/resources/application-file.yml create mode 100644 boyue-admin/src/main/resources/application-lp.yml create mode 100644 boyue-admin/src/main/resources/application-middleware.yml create mode 100644 boyue-admin/src/main/resources/application-mybatis.yml create mode 100644 boyue-admin/src/main/resources/application-pay.yml create mode 100644 boyue-admin/src/main/resources/application.yml create mode 100644 boyue-admin/src/main/resources/banner.txt create mode 100644 boyue-admin/src/main/resources/i18n/messages.properties create mode 100644 boyue-admin/src/main/resources/logback.xml create mode 100644 boyue-admin/src/main/resources/mybatis/mybatis-config.xml create mode 100644 boyue-admin/src/main/resources/templates/hasfj/case.html create mode 100644 boyue-admin/src/main/resources/templates/hasfj/case_list.html create mode 100644 boyue-admin/src/main/resources/templates/hasfj/form.html create mode 100644 boyue-admin/src/main/resources/templates/hasfj/form_list.html create mode 100644 boyue-admin/src/main/resources/templates/hasfj/index.html create mode 100644 boyue-admin/src/main/resources/templates/hasfj/law.html create mode 100644 boyue-admin/src/main/resources/templates/hasfj/law_list.html create mode 100644 boyue-admin/src/main/resources/vite.config.ts create mode 100644 boyue-auth/boyue-auth-common/pom.xml create mode 100644 boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/domain/OauthUser.java create mode 100644 boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/enums/OauthVerificationUse.java create mode 100644 boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/mapper/OauthUserMapper.java create mode 100644 boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/service/IOauthUserService.java create mode 100644 boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/service/OauthVerificationCodeService.java create mode 100644 boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/service/TfaService.java create mode 100644 boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/service/impl/OauthUserServiceImpl.java create mode 100644 boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/utils/RandomCodeUtil.java create mode 100644 boyue-auth/boyue-auth-common/src/main/resources/mapper/common/OauthUserMapper.xml create mode 100644 boyue-auth/boyue-auth-starter/pom.xml create mode 100644 boyue-auth/boyue-auth-starter/src/main/java/com/boyue/auth/controller/OauthUserController.java create mode 100644 boyue-auth/boyue-auth-starter/src/main/java/com/boyue/auth/controller/TfaController.java create mode 100644 boyue-auth/boyue-oauth-justauth/pom.xml create mode 100644 boyue-auth/boyue-oauth-justauth/src/main/java/com/boyue/oauth/justauth/controller/SysAuthController.java create mode 100644 boyue-auth/boyue-oauth-justauth/src/main/java/com/boyue/oauth/justauth/utils/JustAuthUtils.java create mode 100644 boyue-auth/boyue-oauth-wx/pom.xml create mode 100644 boyue-auth/boyue-oauth-wx/src/main/java/com/boyue/oauth/wx/constant/WxMiniAppConstant.java create mode 100644 boyue-auth/boyue-oauth-wx/src/main/java/com/boyue/oauth/wx/constant/WxPubConstant.java create mode 100644 boyue-auth/boyue-oauth-wx/src/main/java/com/boyue/oauth/wx/controller/WxLoginController.java create mode 100644 boyue-auth/boyue-oauth-wx/src/main/java/com/boyue/oauth/wx/service/Impl/WxMiniAppLoginServiceImpl.java create mode 100644 boyue-auth/boyue-oauth-wx/src/main/java/com/boyue/oauth/wx/service/Impl/WxPubLoginServiceImpl.java create mode 100644 boyue-auth/boyue-oauth-wx/src/main/java/com/boyue/oauth/wx/service/WxLoginService.java create mode 100644 boyue-auth/boyue-oauth-wx/src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 boyue-auth/boyue-tfa-email/pom.xml create mode 100644 boyue-auth/boyue-tfa-email/src/main/java/com/boyue/tfa/email/service/IMailService.java create mode 100644 boyue-auth/boyue-tfa-email/src/main/java/com/boyue/tfa/email/service/impl/MailServiceImpl.java create mode 100644 boyue-auth/boyue-tfa-email/src/main/java/com/boyue/tfa/email/utils/EmailUtil.java create mode 100644 boyue-auth/boyue-tfa-phone/pom.xml create mode 100644 boyue-auth/boyue-tfa-phone/src/main/java/com/boyue/tfa/phone/config/DySmsConfig.java create mode 100644 boyue-auth/boyue-tfa-phone/src/main/java/com/boyue/tfa/phone/domain/DySmsTemplate.java create mode 100644 boyue-auth/boyue-tfa-phone/src/main/java/com/boyue/tfa/phone/service/DySmsService.java create mode 100644 boyue-auth/boyue-tfa-phone/src/main/java/com/boyue/tfa/phone/service/Impl/DySmsServiceImpl.java create mode 100644 boyue-auth/boyue-tfa-phone/src/main/java/com/boyue/tfa/phone/utils/DySmsUtil.java create mode 100644 boyue-auth/boyue-tfa-phone/src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 boyue-auth/pom.xml create mode 100644 boyue-common/pom.xml create mode 100644 boyue-common/src/main/java/com/boyue/common/annotation/Anonymous.java create mode 100644 boyue-common/src/main/java/com/boyue/common/annotation/DataScope.java create mode 100644 boyue-common/src/main/java/com/boyue/common/annotation/DataSource.java create mode 100644 boyue-common/src/main/java/com/boyue/common/annotation/Excel.java create mode 100644 boyue-common/src/main/java/com/boyue/common/annotation/Excels.java create mode 100644 boyue-common/src/main/java/com/boyue/common/annotation/Log.java create mode 100644 boyue-common/src/main/java/com/boyue/common/annotation/RateLimiter.java create mode 100644 boyue-common/src/main/java/com/boyue/common/annotation/RepeatSubmit.java create mode 100644 boyue-common/src/main/java/com/boyue/common/annotation/Sensitive.java create mode 100644 boyue-common/src/main/java/com/boyue/common/config/BoYueConfig.java create mode 100644 boyue-common/src/main/java/com/boyue/common/config/serializer/SensitiveJsonSerializer.java create mode 100644 boyue-common/src/main/java/com/boyue/common/constant/CacheConstants.java create mode 100644 boyue-common/src/main/java/com/boyue/common/constant/Constants.java create mode 100644 boyue-common/src/main/java/com/boyue/common/constant/HttpStatus.java create mode 100644 boyue-common/src/main/java/com/boyue/common/constant/ScheduleConstants.java create mode 100644 boyue-common/src/main/java/com/boyue/common/constant/UserConstants.java create mode 100644 boyue-common/src/main/java/com/boyue/common/core/controller/BaseController.java create mode 100644 boyue-common/src/main/java/com/boyue/common/core/domain/AjaxResult.java create mode 100644 boyue-common/src/main/java/com/boyue/common/core/domain/BaseEntity.java create mode 100644 boyue-common/src/main/java/com/boyue/common/core/domain/Message.java create mode 100644 boyue-common/src/main/java/com/boyue/common/core/domain/R.java create mode 100644 boyue-common/src/main/java/com/boyue/common/core/domain/TreeEntity.java create mode 100644 boyue-common/src/main/java/com/boyue/common/core/domain/TreeSelect.java create mode 100644 boyue-common/src/main/java/com/boyue/common/core/domain/entity/SysDept.java create mode 100644 boyue-common/src/main/java/com/boyue/common/core/domain/entity/SysDictData.java create mode 100644 boyue-common/src/main/java/com/boyue/common/core/domain/entity/SysDictType.java create mode 100644 boyue-common/src/main/java/com/boyue/common/core/domain/entity/SysMenu.java create mode 100644 boyue-common/src/main/java/com/boyue/common/core/domain/entity/SysRole.java create mode 100644 boyue-common/src/main/java/com/boyue/common/core/domain/entity/SysUser.java create mode 100644 boyue-common/src/main/java/com/boyue/common/core/domain/model/LoginBody.java create mode 100644 boyue-common/src/main/java/com/boyue/common/core/domain/model/LoginUser.java create mode 100644 boyue-common/src/main/java/com/boyue/common/core/domain/model/RegisterBody.java create mode 100644 boyue-common/src/main/java/com/boyue/common/core/page/PageDomain.java create mode 100644 boyue-common/src/main/java/com/boyue/common/core/page/TableDataInfo.java create mode 100644 boyue-common/src/main/java/com/boyue/common/core/page/TableSupport.java create mode 100644 boyue-common/src/main/java/com/boyue/common/core/security/service/IPermissionService.java create mode 100644 boyue-common/src/main/java/com/boyue/common/core/text/CharsetKit.java create mode 100644 boyue-common/src/main/java/com/boyue/common/core/text/Convert.java create mode 100644 boyue-common/src/main/java/com/boyue/common/core/text/StrFormatter.java create mode 100644 boyue-common/src/main/java/com/boyue/common/enums/BusinessStatus.java create mode 100644 boyue-common/src/main/java/com/boyue/common/enums/BusinessType.java create mode 100644 boyue-common/src/main/java/com/boyue/common/enums/DataSourceType.java create mode 100644 boyue-common/src/main/java/com/boyue/common/enums/DesensitizedType.java create mode 100644 boyue-common/src/main/java/com/boyue/common/enums/HttpMethod.java create mode 100644 boyue-common/src/main/java/com/boyue/common/enums/LimitType.java create mode 100644 boyue-common/src/main/java/com/boyue/common/enums/OperatorType.java create mode 100644 boyue-common/src/main/java/com/boyue/common/enums/UserStatus.java create mode 100644 boyue-common/src/main/java/com/boyue/common/exception/DemoModeException.java create mode 100644 boyue-common/src/main/java/com/boyue/common/exception/GlobalException.java create mode 100644 boyue-common/src/main/java/com/boyue/common/exception/ServiceException.java create mode 100644 boyue-common/src/main/java/com/boyue/common/exception/UtilException.java create mode 100644 boyue-common/src/main/java/com/boyue/common/exception/base/BaseException.java create mode 100644 boyue-common/src/main/java/com/boyue/common/exception/file/FileException.java create mode 100644 boyue-common/src/main/java/com/boyue/common/exception/file/FileNameLengthLimitExceededException.java create mode 100644 boyue-common/src/main/java/com/boyue/common/exception/file/FileSizeLimitExceededException.java create mode 100644 boyue-common/src/main/java/com/boyue/common/exception/file/FileUploadException.java create mode 100644 boyue-common/src/main/java/com/boyue/common/exception/file/InvalidExtensionException.java create mode 100644 boyue-common/src/main/java/com/boyue/common/exception/job/TaskException.java create mode 100644 boyue-common/src/main/java/com/boyue/common/exception/user/BlackListException.java create mode 100644 boyue-common/src/main/java/com/boyue/common/exception/user/CaptchaException.java create mode 100644 boyue-common/src/main/java/com/boyue/common/exception/user/CaptchaExpireException.java create mode 100644 boyue-common/src/main/java/com/boyue/common/exception/user/IpRetryLimitExceedException.java create mode 100644 boyue-common/src/main/java/com/boyue/common/exception/user/UserException.java create mode 100644 boyue-common/src/main/java/com/boyue/common/exception/user/UserNotExistsException.java create mode 100644 boyue-common/src/main/java/com/boyue/common/exception/user/UserPasswordNotMatchException.java create mode 100644 boyue-common/src/main/java/com/boyue/common/exception/user/UserPasswordRetryLimitExceedException.java create mode 100644 boyue-common/src/main/java/com/boyue/common/filter/PropertyPreExcludeFilter.java create mode 100644 boyue-common/src/main/java/com/boyue/common/filter/RepeatableFilter.java create mode 100644 boyue-common/src/main/java/com/boyue/common/filter/RepeatedlyRequestWrapper.java create mode 100644 boyue-common/src/main/java/com/boyue/common/filter/XssFilter.java create mode 100644 boyue-common/src/main/java/com/boyue/common/filter/XssHttpServletRequestWrapper.java create mode 100644 boyue-common/src/main/java/com/boyue/common/handler/GenericListTypeHandler.java create mode 100644 boyue-common/src/main/java/com/boyue/common/service/cache/CacheKeys.java create mode 100644 boyue-common/src/main/java/com/boyue/common/service/cache/CacheNoTimeOut.java create mode 100644 boyue-common/src/main/java/com/boyue/common/service/cache/CacheTimeOut.java create mode 100644 boyue-common/src/main/java/com/boyue/common/service/datasource/AfterCreateDataSource.java create mode 100644 boyue-common/src/main/java/com/boyue/common/service/datasource/CreateDataSource.java create mode 100644 boyue-common/src/main/java/com/boyue/common/service/mybatis/CreateSqlSessionFactory.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/Arith.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/CacheUtils.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/DateUtils.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/DesensitizedUtil.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/DictUtils.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/ExceptionUtil.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/LogUtils.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/MessageUtils.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/MybatisUtils.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/PageUtils.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/SecurityUtils.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/ServletUtils.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/StringUtils.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/Threads.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/bean/BeanUtils.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/bean/BeanValidators.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/file/FileTypeUtils.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/file/FileUtils.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/file/ImageUtils.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/file/MimeTypeUtils.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/html/EscapeUtil.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/html/HTMLFilter.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/http/HttpClientUtil.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/http/HttpConf.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/http/HttpHelper.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/http/HttpUtils.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/http/IdleConnectionMonitorThread.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/ip/AddressUtils.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/ip/IpUtils.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/poi/ExcelHandlerAdapter.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/poi/ExcelUtil.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/reflect/ReflectUtils.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/sign/Base64.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/sign/Md5Utils.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/spring/SpringUtils.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/sql/SqlUtil.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/uuid/IdUtils.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/uuid/Seq.java create mode 100644 boyue-common/src/main/java/com/boyue/common/utils/uuid/UUID.java create mode 100644 boyue-common/src/main/java/com/boyue/common/xss/Xss.java create mode 100644 boyue-common/src/main/java/com/boyue/common/xss/XssValidator.java create mode 100644 boyue-file/boyue-file-common/pom.xml create mode 100644 boyue-file/boyue-file-common/src/main/java/com/boyue/file/domain/SysFileInfo.java create mode 100644 boyue-file/boyue-file-common/src/main/java/com/boyue/file/domain/SysFilePartETag.java create mode 100644 boyue-file/boyue-file-common/src/main/java/com/boyue/file/local/config/LocalBucketProperties.java create mode 100644 boyue-file/boyue-file-common/src/main/java/com/boyue/file/local/config/LocalManagement.java create mode 100644 boyue-file/boyue-file-common/src/main/java/com/boyue/file/local/domain/LocalBucket.java create mode 100644 boyue-file/boyue-file-common/src/main/java/com/boyue/file/local/service/LocalFileService.java create mode 100644 boyue-file/boyue-file-common/src/main/java/com/boyue/file/mapper/SysFileInfoMapper.java create mode 100644 boyue-file/boyue-file-common/src/main/java/com/boyue/file/service/ISysFileInfoService.java create mode 100644 boyue-file/boyue-file-common/src/main/java/com/boyue/file/service/impl/SysFileInfoServiceImpl.java create mode 100644 boyue-file/boyue-file-common/src/main/java/com/boyue/file/storage/StorageBucket.java create mode 100644 boyue-file/boyue-file-common/src/main/java/com/boyue/file/storage/StorageEntity.java create mode 100644 boyue-file/boyue-file-common/src/main/java/com/boyue/file/storage/StorageManagement.java create mode 100644 boyue-file/boyue-file-common/src/main/java/com/boyue/file/storage/StorageManagements.java create mode 100644 boyue-file/boyue-file-common/src/main/java/com/boyue/file/storage/StorageService.java create mode 100644 boyue-file/boyue-file-common/src/main/java/com/boyue/file/utils/FileOperateUtils.java create mode 100644 boyue-file/boyue-file-common/src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 boyue-file/boyue-file-common/src/main/resources/mapper/file/SysFileInfoMapper.xml create mode 100644 boyue-file/boyue-file-minio/pom.xml create mode 100644 boyue-file/boyue-file-minio/src/main/java/com/boyue/file/minio/config/MinioBucketProperties.java create mode 100644 boyue-file/boyue-file-minio/src/main/java/com/boyue/file/minio/config/MinioManagement.java create mode 100644 boyue-file/boyue-file-minio/src/main/java/com/boyue/file/minio/domain/MinioBucket.java create mode 100644 boyue-file/boyue-file-minio/src/main/java/com/boyue/file/minio/domain/MinioEntityVO.java create mode 100644 boyue-file/boyue-file-minio/src/main/java/com/boyue/file/minio/exception/MinioClientErrorException.java create mode 100644 boyue-file/boyue-file-minio/src/main/java/com/boyue/file/minio/exception/MinioClientNotFundException.java create mode 100644 boyue-file/boyue-file-minio/src/main/java/com/boyue/file/minio/service/MinioFileService.java create mode 100644 boyue-file/boyue-file-minio/src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 boyue-file/boyue-file-oss-alibaba/pom.xml create mode 100644 boyue-file/boyue-file-oss-alibaba/src/main/java/com/boyue/file/oss/alibaba/config/AliOssBucketProperties.java create mode 100644 boyue-file/boyue-file-oss-alibaba/src/main/java/com/boyue/file/oss/alibaba/config/AliOssManagement.java create mode 100644 boyue-file/boyue-file-oss-alibaba/src/main/java/com/boyue/file/oss/alibaba/domain/AliOssBucket.java create mode 100644 boyue-file/boyue-file-oss-alibaba/src/main/java/com/boyue/file/oss/alibaba/domain/AliOssEntityVO.java create mode 100644 boyue-file/boyue-file-oss-alibaba/src/main/java/com/boyue/file/oss/alibaba/exception/AliOssClientErrorException.java create mode 100644 boyue-file/boyue-file-oss-alibaba/src/main/java/com/boyue/file/oss/alibaba/exception/AliOssClientNotFundException.java create mode 100644 boyue-file/boyue-file-oss-alibaba/src/main/java/com/boyue/file/oss/alibaba/service/AliOssFileService.java create mode 100644 boyue-file/boyue-file-oss-alibaba/src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 boyue-file/boyue-file-starter/pom.xml create mode 100644 boyue-file/boyue-file-starter/src/main/java/com/boyue/file/controller/FileController.java create mode 100644 boyue-file/boyue-file-starter/src/main/java/com/boyue/file/controller/SysFileInfoController.java create mode 100644 boyue-file/pom.xml create mode 100644 boyue-framework/pom.xml create mode 100644 boyue-framework/src/main/java/com/boyue/framework/aspectj/DataScopeAspect.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/aspectj/DataSourceAspect.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/aspectj/LogAspect.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/config/ApplicationConfig.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/config/CaptchaConfig.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/config/DruidConfig.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/config/DynamicDataSourceProperties.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/config/FilterConfig.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/config/I18nConfig.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/config/KaptchaTextCreator.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/config/MyBatisConfig.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/config/ResourcesConfig.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/config/SecurityConfig.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/config/ServerConfig.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/config/SqlSessionFactoryConfig.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/config/ThreadPoolConfig.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/config/properties/DruidProperties.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/config/properties/PermitAllUrlProperties.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/datasource/DataSourceCreate.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/datasource/DataSourceManagement.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/datasource/DynamicDataSource.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/datasource/DynamicDataSourceContextHolder.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/datasource/DynamicSqlSessionTemplate.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/datasource/DynamicTransactionManager.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/interceptor/RepeatSubmitInterceptor.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/interceptor/impl/SameUrlDataInterceptor.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/manager/AsyncManager.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/manager/ShutdownManager.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/manager/factory/AsyncFactory.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/security/context/AuthenticationContextHolder.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/security/context/PermissionContextHolder.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/security/filter/JwtAuthenticationTokenFilter.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/security/handle/AuthenticationEntryPointImpl.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/security/handle/LogoutSuccessHandlerImpl.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/web/domain/Server.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/web/domain/server/Cpu.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/web/domain/server/Jvm.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/web/domain/server/Mem.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/web/domain/server/Sys.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/web/domain/server/SysFile.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/web/exception/GlobalExceptionHandler.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/web/service/PermissionService.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/web/service/SysLoginService.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/web/service/SysPasswordService.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/web/service/SysPermissionService.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/web/service/SysRegisterService.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/web/service/TokenService.java create mode 100644 boyue-framework/src/main/java/com/boyue/framework/web/service/UserDetailsServiceImpl.java create mode 100644 boyue-framework/src/main/resources/mybatis/mybatis-config.xml create mode 100644 boyue-middleware/boyue-middleware-rabbitmq/pom.xml create mode 100644 boyue-middleware/boyue-middleware-rabbitmq/src/main/java/com/boyue/middleware/rabbitmq/DirectRabbitConfig.java create mode 100644 boyue-middleware/boyue-middleware-rabbitmq/src/main/java/com/boyue/middleware/rabbitmq/DirectReceiver.java create mode 100644 boyue-middleware/boyue-middleware-rabbitmq/src/main/java/com/boyue/middleware/rabbitmq/SendMessageController.java create mode 100644 boyue-middleware/boyue-middleware-redis/pom.xml create mode 100644 boyue-middleware/boyue-middleware-redis/src/main/java/com/boyue/middleware/redis/aspectj/RateLimiterAspect.java create mode 100644 boyue-middleware/boyue-middleware-redis/src/main/java/com/boyue/middleware/redis/config/FastJson2JsonRedisSerializer.java create mode 100644 boyue-middleware/boyue-middleware-redis/src/main/java/com/boyue/middleware/redis/config/RedisConfig.java create mode 100644 boyue-middleware/boyue-middleware-redis/src/main/java/com/boyue/middleware/redis/controller/RedisCacheController.java create mode 100644 boyue-middleware/boyue-middleware-redis/src/main/java/com/boyue/middleware/redis/utils/RedisCache.java create mode 100644 boyue-middleware/boyue-middleware-starter/pom.xml create mode 100644 boyue-middleware/pom.xml create mode 100644 boyue-models/boyue-flowable/pom.xml create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/common/constant/ProcessConstants.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/common/enums/FlowComment.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/common/expand/el/BaseEl.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/common/expand/el/FlowEl.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/config/FlowableConfig.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/controller/FlowDefinitionController.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/controller/FlowInstanceController.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/controller/FlowTaskController.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/controller/SysExpressionController.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/controller/SysListenerController.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/SysDeployForm.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/SysExpression.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/SysListener.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/SysTaskForm.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/dto/FlowCommentDto.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/dto/FlowFromFieldDTO.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/dto/FlowNextDto.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/dto/FlowProcDefDto.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/dto/FlowSaveXmlVo.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/dto/FlowTaskDto.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/dto/FlowViewerDto.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/vo/FlowQueryVo.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/vo/FlowTaskVo.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/vo/ReturnTaskNodeVo.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/expression/FlowDelegationExpression.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/factory/FlowServiceFactory.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/flow/CustomProcessDiagramCanvas.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/flow/CustomProcessDiagramGenerator.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/flow/FindNextNodeUtil.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/flow/FlowableUtils.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/listener/FlowExecutionListener.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/listener/FlowTaskListener.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/mapper/FlowDeployMapper.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/mapper/SysDeployFormMapper.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/mapper/SysExpressionMapper.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/mapper/SysListenerMapper.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/mapper/SysTaskFormMapper.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/IFlowDefinitionService.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/IFlowInstanceService.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/IFlowTaskService.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/ISysDeployFormService.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/ISysExpressionService.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/ISysListenerService.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/impl/FlowDefinitionServiceImpl.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/impl/FlowInstanceServiceImpl.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/impl/FlowTaskServiceImpl.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/impl/SysDeployFormServiceImpl.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/impl/SysExpressionServiceImpl.java create mode 100644 boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/impl/SysListenerServiceImpl.java create mode 100644 boyue-models/boyue-flowable/src/main/resources/mapper/flowable/FlowDeployMapper.xml create mode 100644 boyue-models/boyue-flowable/src/main/resources/mapper/flowable/SysDeployFormMapper.xml create mode 100644 boyue-models/boyue-flowable/src/main/resources/mapper/flowable/SysExpressionMapper.xml create mode 100644 boyue-models/boyue-flowable/src/main/resources/mapper/flowable/SysListenerMapper.xml create mode 100644 boyue-models/boyue-flowable/src/main/resources/mapper/flowable/SysTaskFormMapper.xml create mode 100644 boyue-models/boyue-form/pom.xml create mode 100644 boyue-models/boyue-form/src/main/java/com/boyue/form/controller/FormDataController.java create mode 100644 boyue-models/boyue-form/src/main/java/com/boyue/form/controller/FormTemplateController.java create mode 100644 boyue-models/boyue-form/src/main/java/com/boyue/form/domain/FormData.java create mode 100644 boyue-models/boyue-form/src/main/java/com/boyue/form/domain/FormTemplate.java create mode 100644 boyue-models/boyue-form/src/main/java/com/boyue/form/mapper/FormDataMapper.java create mode 100644 boyue-models/boyue-form/src/main/java/com/boyue/form/mapper/FormTemplateMapper.java create mode 100644 boyue-models/boyue-form/src/main/java/com/boyue/form/service/IFormDataService.java create mode 100644 boyue-models/boyue-form/src/main/java/com/boyue/form/service/IFormTemplateService.java create mode 100644 boyue-models/boyue-form/src/main/java/com/boyue/form/service/impl/FormDataServiceImpl.java create mode 100644 boyue-models/boyue-form/src/main/java/com/boyue/form/service/impl/FormTemplateServiceImpl.java create mode 100644 boyue-models/boyue-form/src/main/resources/mapper/form/FormDataMapper.xml create mode 100644 boyue-models/boyue-form/src/main/resources/mapper/form/FormTemplateMapper.xml create mode 100644 boyue-models/boyue-generator/pom.xml create mode 100644 boyue-models/boyue-generator/src/main/java/com/boyue/generator/config/GenConfig.java create mode 100644 boyue-models/boyue-generator/src/main/java/com/boyue/generator/constant/GenConstants.java create mode 100644 boyue-models/boyue-generator/src/main/java/com/boyue/generator/controller/GenController.java create mode 100644 boyue-models/boyue-generator/src/main/java/com/boyue/generator/domain/GenJoinTable.java create mode 100644 boyue-models/boyue-generator/src/main/java/com/boyue/generator/domain/GenTable.java create mode 100644 boyue-models/boyue-generator/src/main/java/com/boyue/generator/domain/GenTableColumn.java create mode 100644 boyue-models/boyue-generator/src/main/java/com/boyue/generator/domain/vo/GenTableVo.java create mode 100644 boyue-models/boyue-generator/src/main/java/com/boyue/generator/mapper/GenJoinTableMapper.java create mode 100644 boyue-models/boyue-generator/src/main/java/com/boyue/generator/mapper/GenTableColumnMapper.java create mode 100644 boyue-models/boyue-generator/src/main/java/com/boyue/generator/mapper/GenTableMapper.java create mode 100644 boyue-models/boyue-generator/src/main/java/com/boyue/generator/service/GenJoinTableServiceImpl.java create mode 100644 boyue-models/boyue-generator/src/main/java/com/boyue/generator/service/GenTableColumnServiceImpl.java create mode 100644 boyue-models/boyue-generator/src/main/java/com/boyue/generator/service/GenTableServiceImpl.java create mode 100644 boyue-models/boyue-generator/src/main/java/com/boyue/generator/service/IGenJoinTableService.java create mode 100644 boyue-models/boyue-generator/src/main/java/com/boyue/generator/service/IGenTableColumnService.java create mode 100644 boyue-models/boyue-generator/src/main/java/com/boyue/generator/service/IGenTableService.java create mode 100644 boyue-models/boyue-generator/src/main/java/com/boyue/generator/util/GenUtils.java create mode 100644 boyue-models/boyue-generator/src/main/java/com/boyue/generator/util/VelocityInitializer.java create mode 100644 boyue-models/boyue-generator/src/main/java/com/boyue/generator/util/VelocityUtils.java create mode 100644 boyue-models/boyue-generator/src/main/resources/generator.yml create mode 100644 boyue-models/boyue-generator/src/main/resources/mapper/generator/GenJoinTableMapper.xml create mode 100644 boyue-models/boyue-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml create mode 100644 boyue-models/boyue-generator/src/main/resources/mapper/generator/GenTableMapper.xml create mode 100644 boyue-models/boyue-generator/src/main/resources/vm/java/controller.java.vm create mode 100644 boyue-models/boyue-generator/src/main/resources/vm/java/domain.java.vm create mode 100644 boyue-models/boyue-generator/src/main/resources/vm/java/mapper.java.vm create mode 100644 boyue-models/boyue-generator/src/main/resources/vm/java/service.java.vm create mode 100644 boyue-models/boyue-generator/src/main/resources/vm/java/serviceImpl.java.vm create mode 100644 boyue-models/boyue-generator/src/main/resources/vm/java/sub-domain.java.vm create mode 100644 boyue-models/boyue-generator/src/main/resources/vm/js/api.js.vm create mode 100644 boyue-models/boyue-generator/src/main/resources/vm/sql/sql.vm create mode 100644 boyue-models/boyue-generator/src/main/resources/vm/uniapp/edit.vue.vm create mode 100644 boyue-models/boyue-generator/src/main/resources/vm/uniapp/list.vue.vm create mode 100644 boyue-models/boyue-generator/src/main/resources/vm/uniapp/show.vue.vm create mode 100644 boyue-models/boyue-generator/src/main/resources/vm/vue/v2/index-tree.vue.vm create mode 100644 boyue-models/boyue-generator/src/main/resources/vm/vue/v2/index.vue.vm create mode 100644 boyue-models/boyue-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm create mode 100644 boyue-models/boyue-generator/src/main/resources/vm/vue/v3/index.vue.vm create mode 100644 boyue-models/boyue-generator/src/main/resources/vm/xml/mapper.xml.vm create mode 100644 boyue-models/boyue-hasfj/pom.xml create mode 100644 boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/controller/ApiController.java create mode 100644 boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/controller/FrontendController.java create mode 100644 boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/controller/HtmlPageApiController.java create mode 100644 boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/controller/HtmlPageViewController.java create mode 100644 boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/controller/HtmlPagesController.java create mode 100644 boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/domain/HtmlPages.java create mode 100644 boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/mapper/HtmlPagesMapper.java create mode 100644 boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/service/IHtmlPagesService.java create mode 100644 boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/service/impl/HtmlPagesServiceImpl.java create mode 100644 boyue-models/boyue-hasfj/src/main/resources/mapper/hasfj/HtmlPagesMapper.xml create mode 100644 boyue-models/boyue-message/pom.xml create mode 100644 boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/annotation/MessageLog.java create mode 100644 boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/controller/MessageSystemController.java create mode 100644 boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/controller/MessageTemplateController.java create mode 100644 boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/controller/MessageVariableController.java create mode 100644 boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/domain/MessageSystem.java create mode 100644 boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/domain/MessageTemplate.java create mode 100644 boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/domain/MessageVariable.java create mode 100644 boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/enums/MessageType.java create mode 100644 boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/enums/SendMode.java create mode 100644 boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/mapper/MessageSystemMapper.java create mode 100644 boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/mapper/MessageTemplateMapper.java create mode 100644 boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/mapper/MessageVariableMapper.java create mode 100644 boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/service/IMessageSystemService.java create mode 100644 boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/service/IMessageTemplateService.java create mode 100644 boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/service/IMessageVariableService.java create mode 100644 boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/service/impl/MessageSystemServiceImpl.java create mode 100644 boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/service/impl/MessageTemplateServiceImpl.java create mode 100644 boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/service/impl/MessageVariableServiceImpl.java create mode 100644 boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/utils/MessageLogAspect.java create mode 100644 boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/utils/MessageSystemUtils.java create mode 100644 boyue-models/boyue-message/src/main/resources/mapper/modelMessage/MessageSystemMapper.xml create mode 100644 boyue-models/boyue-message/src/main/resources/mapper/modelMessage/MessageTemplateMapper.xml create mode 100644 boyue-models/boyue-message/src/main/resources/mapper/modelMessage/MessageVariableMapper.xml create mode 100644 boyue-models/boyue-models-starter/pom.xml create mode 100644 boyue-models/boyue-online/pom.xml create mode 100644 boyue-models/boyue-online/src/main/java/com/boyue/online/controller/OnLineController.java create mode 100644 boyue-models/boyue-online/src/main/java/com/boyue/online/controller/OnlineDbController.java create mode 100644 boyue-models/boyue-online/src/main/java/com/boyue/online/controller/OnlineMbController.java create mode 100644 boyue-models/boyue-online/src/main/java/com/boyue/online/domain/OnlineMb.java create mode 100644 boyue-models/boyue-online/src/main/java/com/boyue/online/mapper/OnlineDbMapper.java create mode 100644 boyue-models/boyue-online/src/main/java/com/boyue/online/mapper/OnlineMbMapper.java create mode 100644 boyue-models/boyue-online/src/main/java/com/boyue/online/service/IOnlineMbService.java create mode 100644 boyue-models/boyue-online/src/main/java/com/boyue/online/service/impl/OnlineMbServiceImpl.java create mode 100644 boyue-models/boyue-online/src/main/java/com/boyue/online/utils/SqlMapper.java create mode 100644 boyue-models/boyue-online/src/main/resources/mapper/online/OnlineDbMapper.xml create mode 100644 boyue-models/boyue-online/src/main/resources/mapper/online/OnlineMbMapper.xml create mode 100644 boyue-models/boyue-quartz/pom.xml create mode 100644 boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/config/ScheduleConfig.java create mode 100644 boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/controller/SysJobController.java create mode 100644 boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/controller/SysJobLogController.java create mode 100644 boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/domain/SysJob.java create mode 100644 boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/domain/SysJobLog.java create mode 100644 boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/mapper/SysJobLogMapper.java create mode 100644 boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/mapper/SysJobMapper.java create mode 100644 boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/service/ISysJobLogService.java create mode 100644 boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/service/ISysJobService.java create mode 100644 boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/service/impl/SysJobLogServiceImpl.java create mode 100644 boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/service/impl/SysJobServiceImpl.java create mode 100644 boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/task/RyTask.java create mode 100644 boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/util/AbstractQuartzJob.java create mode 100644 boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/util/CronUtils.java create mode 100644 boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/util/JobInvokeUtil.java create mode 100644 boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/util/QuartzDisallowConcurrentExecution.java create mode 100644 boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/util/QuartzJobExecution.java create mode 100644 boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/util/ScheduleUtils.java create mode 100644 boyue-models/boyue-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml create mode 100644 boyue-models/boyue-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml create mode 100644 boyue-models/pom.xml create mode 100644 boyue-pay/boyue-pay-alipay/pom.xml create mode 100644 boyue-pay/boyue-pay-alipay/src/main/java/com/boyue/pay/alipay/config/AliPayConfig.java create mode 100644 boyue-pay/boyue-pay-alipay/src/main/java/com/boyue/pay/alipay/service/IAliPayService.java create mode 100644 boyue-pay/boyue-pay-alipay/src/main/java/com/boyue/pay/alipay/service/Impl/AliPayService.java create mode 100644 boyue-pay/boyue-pay-alipay/src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 boyue-pay/boyue-pay-common/pom.xml create mode 100644 boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/domain/PayInvoice.java create mode 100644 boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/domain/PayOrder.java create mode 100644 boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/mapper/PayInvoiceMapper.java create mode 100644 boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/mapper/PayOrderMapper.java create mode 100644 boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/service/IPayInvoiceService.java create mode 100644 boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/service/IPayOrderService.java create mode 100644 boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/service/PayService.java create mode 100644 boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/service/impl/PayInvoiceServiceImpl.java create mode 100644 boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/service/impl/PayOrderServiceImpl.java create mode 100644 boyue-pay/boyue-pay-common/src/main/resources/mapper/pay/PayInvoiceMapper.xml create mode 100644 boyue-pay/boyue-pay-common/src/main/resources/mapper/pay/PayOrderMapper.xml create mode 100644 boyue-pay/boyue-pay-sqb/pom.xml create mode 100644 boyue-pay/boyue-pay-sqb/src/main/java/com/boyue/pay/sqb/config/SqbConfig.java create mode 100644 boyue-pay/boyue-pay-sqb/src/main/java/com/boyue/pay/sqb/service/ISqbPayService.java create mode 100644 boyue-pay/boyue-pay-sqb/src/main/java/com/boyue/pay/sqb/service/Impl/SQBServiceImpl.java create mode 100644 boyue-pay/boyue-pay-sqb/src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 boyue-pay/boyue-pay-starter/pom.xml create mode 100644 boyue-pay/boyue-pay-starter/src/main/java/com/boyue/pay/controller/PayController.java create mode 100644 boyue-pay/boyue-pay-starter/src/main/java/com/boyue/pay/controller/PayInvoiceController.java create mode 100644 boyue-pay/boyue-pay-starter/src/main/java/com/boyue/pay/controller/PayOrderController.java create mode 100644 boyue-pay/boyue-pay-wx/pom.xml create mode 100644 boyue-pay/boyue-pay-wx/src/main/java/com/boyue/pay/wx/config/WxPayConfig.java create mode 100644 boyue-pay/boyue-pay-wx/src/main/java/com/boyue/pay/wx/service/IWxPayService.java create mode 100644 boyue-pay/boyue-pay-wx/src/main/java/com/boyue/pay/wx/service/Impl/WxPayService.java create mode 100644 boyue-pay/boyue-pay-wx/src/main/resources/META-INF/additional-spring-configuration-metadata.json create mode 100644 boyue-pay/pom.xml create mode 100644 boyue-plugins/boyue-atomikos/pom.xml create mode 100644 boyue-plugins/boyue-atomikos/src/main/java/com/boyue/atomikos/config/AtomikosConfig.java create mode 100644 boyue-plugins/boyue-atomikos/src/main/java/com/boyue/atomikos/datasource/AtomikosDataSourceCreate.java create mode 100644 boyue-plugins/boyue-ehcache/pom.xml create mode 100644 boyue-plugins/boyue-ehcache/src/main/java/com/boyue/ehcache/config/Ehcache3Cache.java create mode 100644 boyue-plugins/boyue-ehcache/src/main/java/com/boyue/ehcache/config/Ehcache3Config.java create mode 100644 boyue-plugins/boyue-mybatis-interceptor/pom.xml create mode 100644 boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/annotation/DataSecurity.java create mode 100644 boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/annotation/MybatisHandlerOrder.java create mode 100644 boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/aspectj/DataSecurityAspect.java create mode 100644 boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/context/page/PageContextHolder.java create mode 100644 boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/context/page/model/BoYueTableData.java create mode 100644 boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/context/page/model/PageInfo.java create mode 100644 boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/context/page/model/TableInfo.java create mode 100644 boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/context/sqlContext/SqlContextHolder.java create mode 100644 boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/enums/DataSecurityStrategy.java create mode 100644 boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/enums/SqlType.java create mode 100644 boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/handler/MybatisAfterHandler.java create mode 100644 boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/handler/MybatisPreHandler.java create mode 100644 boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/handler/dataSecurity/DataSecurityPreHandler.java create mode 100644 boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/handler/page/PageAfterHandler.java create mode 100644 boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/handler/page/PagePreHandler.java create mode 100644 boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/interceptor/mybatis/MybatisInterceptor.java create mode 100644 boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/model/JoinTableModel.java create mode 100644 boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/model/WhereModel.java create mode 100644 boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/util/DataSecurityUtil.java create mode 100644 boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/util/PageUtils.java create mode 100644 boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/util/SqlUtil.java create mode 100644 boyue-plugins/boyue-mybatis-jpa/pom.xml create mode 100644 boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/annotation/Column.java create mode 100644 boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/annotation/ColumnMap.java create mode 100644 boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/annotation/EnableTableMap.java create mode 100644 boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/annotation/Query.java create mode 100644 boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/annotation/Table.java create mode 100644 boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/domain/BaseColumnInfo.java create mode 100644 boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/domain/ColumnInfo.java create mode 100644 boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/domain/MapColumnInfo.java create mode 100644 boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/domain/TableInfo.java create mode 100644 boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/enums/QueryEnum.java create mode 100644 boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/mapper/JPAMapper.java create mode 100644 boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/service/JPAService.java create mode 100644 boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/service/impl/JPAServiceImpl.java create mode 100644 boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/utils/QueryUtil.java create mode 100644 boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/utils/QueryWrapperUtil.java create mode 100644 boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/utils/SQLGenerator.java create mode 100644 boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/utils/TableContainer.java create mode 100644 boyue-plugins/boyue-mybatis-plus/pom.xml create mode 100644 boyue-plugins/boyue-mybatis-plus/src/main/java/com/boyue/mybatisplus/config/MybatisPlusConfig.java create mode 100644 boyue-plugins/boyue-mybatis-plus/src/main/java/com/boyue/mybatisplus/controller/SysStudentController.java create mode 100644 boyue-plugins/boyue-mybatis-plus/src/main/java/com/boyue/mybatisplus/domain/SysStudent.java create mode 100644 boyue-plugins/boyue-mybatis-plus/src/main/java/com/boyue/mybatisplus/mapper/SysStudentMapper.java create mode 100644 boyue-plugins/boyue-mybatis-plus/src/main/java/com/boyue/mybatisplus/service/ISysStudentService.java create mode 100644 boyue-plugins/boyue-mybatis-plus/src/main/java/com/boyue/mybatisplus/service/impl/SysStudentServiceImpl.java create mode 100644 boyue-plugins/boyue-netty/pom.xml create mode 100644 boyue-plugins/boyue-netty/src/main/java/com/boyue/netty/websocket/NettyServerRunner.java create mode 100644 boyue-plugins/boyue-netty/src/main/java/com/boyue/netty/websocket/annotations/NettyWebSocketEndpoint.java create mode 100644 boyue-plugins/boyue-netty/src/main/java/com/boyue/netty/websocket/endpoints/TestNettyWebSocket.java create mode 100644 boyue-plugins/boyue-netty/src/main/java/com/boyue/netty/websocket/nettyServer/NettyWebSocketEndpointHandler.java create mode 100644 boyue-plugins/boyue-netty/src/main/java/com/boyue/netty/websocket/nettyServer/NettyWebSocketServer.java create mode 100644 boyue-plugins/boyue-netty/src/main/java/com/boyue/netty/websocket/nettyServer/handler/WebSocketHandler.java create mode 100644 boyue-plugins/boyue-netty/src/main/java/com/boyue/netty/websocket/utils/CommonUtil.java create mode 100644 boyue-plugins/boyue-plugins-starter/pom.xml create mode 100644 boyue-plugins/boyue-websocket/pom.xml create mode 100644 boyue-plugins/boyue-websocket/src/main/java/com/boyue/websocket/SemaphoreUtils.java create mode 100644 boyue-plugins/boyue-websocket/src/main/java/com/boyue/websocket/WebSocketConfig.java create mode 100644 boyue-plugins/boyue-websocket/src/main/java/com/boyue/websocket/WebSocketServer.java create mode 100644 boyue-plugins/boyue-websocket/src/main/java/com/boyue/websocket/WebSocketUsers.java create mode 100644 boyue-plugins/pom.xml create mode 100644 boyue-system/pom.xml create mode 100644 boyue-system/src/main/java/com/boyue/system/domain/SysCache.java create mode 100644 boyue-system/src/main/java/com/boyue/system/domain/SysConfig.java create mode 100644 boyue-system/src/main/java/com/boyue/system/domain/SysLogininfor.java create mode 100644 boyue-system/src/main/java/com/boyue/system/domain/SysNotice.java create mode 100644 boyue-system/src/main/java/com/boyue/system/domain/SysOperLog.java create mode 100644 boyue-system/src/main/java/com/boyue/system/domain/SysPost.java create mode 100644 boyue-system/src/main/java/com/boyue/system/domain/SysRoleDept.java create mode 100644 boyue-system/src/main/java/com/boyue/system/domain/SysRoleMenu.java create mode 100644 boyue-system/src/main/java/com/boyue/system/domain/SysUserOnline.java create mode 100644 boyue-system/src/main/java/com/boyue/system/domain/SysUserPost.java create mode 100644 boyue-system/src/main/java/com/boyue/system/domain/SysUserRole.java create mode 100644 boyue-system/src/main/java/com/boyue/system/domain/vo/MetaVo.java create mode 100644 boyue-system/src/main/java/com/boyue/system/domain/vo/RouterVo.java create mode 100644 boyue-system/src/main/java/com/boyue/system/mapper/SysConfigMapper.java create mode 100644 boyue-system/src/main/java/com/boyue/system/mapper/SysDeptMapper.java create mode 100644 boyue-system/src/main/java/com/boyue/system/mapper/SysDictDataMapper.java create mode 100644 boyue-system/src/main/java/com/boyue/system/mapper/SysDictTypeMapper.java create mode 100644 boyue-system/src/main/java/com/boyue/system/mapper/SysLogininforMapper.java create mode 100644 boyue-system/src/main/java/com/boyue/system/mapper/SysMenuMapper.java create mode 100644 boyue-system/src/main/java/com/boyue/system/mapper/SysNoticeMapper.java create mode 100644 boyue-system/src/main/java/com/boyue/system/mapper/SysOperLogMapper.java create mode 100644 boyue-system/src/main/java/com/boyue/system/mapper/SysPostMapper.java create mode 100644 boyue-system/src/main/java/com/boyue/system/mapper/SysRoleDeptMapper.java create mode 100644 boyue-system/src/main/java/com/boyue/system/mapper/SysRoleMapper.java create mode 100644 boyue-system/src/main/java/com/boyue/system/mapper/SysRoleMenuMapper.java create mode 100644 boyue-system/src/main/java/com/boyue/system/mapper/SysUserMapper.java create mode 100644 boyue-system/src/main/java/com/boyue/system/mapper/SysUserPostMapper.java create mode 100644 boyue-system/src/main/java/com/boyue/system/mapper/SysUserRoleMapper.java create mode 100644 boyue-system/src/main/java/com/boyue/system/service/ISysConfigService.java create mode 100644 boyue-system/src/main/java/com/boyue/system/service/ISysDeptService.java create mode 100644 boyue-system/src/main/java/com/boyue/system/service/ISysDictDataService.java create mode 100644 boyue-system/src/main/java/com/boyue/system/service/ISysDictTypeService.java create mode 100644 boyue-system/src/main/java/com/boyue/system/service/ISysLogininforService.java create mode 100644 boyue-system/src/main/java/com/boyue/system/service/ISysMenuService.java create mode 100644 boyue-system/src/main/java/com/boyue/system/service/ISysNoticeService.java create mode 100644 boyue-system/src/main/java/com/boyue/system/service/ISysOperLogService.java create mode 100644 boyue-system/src/main/java/com/boyue/system/service/ISysPostService.java create mode 100644 boyue-system/src/main/java/com/boyue/system/service/ISysRoleService.java create mode 100644 boyue-system/src/main/java/com/boyue/system/service/ISysUserOnlineService.java create mode 100644 boyue-system/src/main/java/com/boyue/system/service/ISysUserService.java create mode 100644 boyue-system/src/main/java/com/boyue/system/service/impl/SysConfigServiceImpl.java create mode 100644 boyue-system/src/main/java/com/boyue/system/service/impl/SysDeptServiceImpl.java create mode 100644 boyue-system/src/main/java/com/boyue/system/service/impl/SysDictDataServiceImpl.java create mode 100644 boyue-system/src/main/java/com/boyue/system/service/impl/SysDictTypeServiceImpl.java create mode 100644 boyue-system/src/main/java/com/boyue/system/service/impl/SysLogininforServiceImpl.java create mode 100644 boyue-system/src/main/java/com/boyue/system/service/impl/SysMenuServiceImpl.java create mode 100644 boyue-system/src/main/java/com/boyue/system/service/impl/SysNoticeServiceImpl.java create mode 100644 boyue-system/src/main/java/com/boyue/system/service/impl/SysOperLogServiceImpl.java create mode 100644 boyue-system/src/main/java/com/boyue/system/service/impl/SysPostServiceImpl.java create mode 100644 boyue-system/src/main/java/com/boyue/system/service/impl/SysRoleServiceImpl.java create mode 100644 boyue-system/src/main/java/com/boyue/system/service/impl/SysUserOnlineServiceImpl.java create mode 100644 boyue-system/src/main/java/com/boyue/system/service/impl/SysUserServiceImpl.java create mode 100644 boyue-system/src/main/resources/mapper/system/SysConfigMapper.xml create mode 100644 boyue-system/src/main/resources/mapper/system/SysDeptMapper.xml create mode 100644 boyue-system/src/main/resources/mapper/system/SysDictDataMapper.xml create mode 100644 boyue-system/src/main/resources/mapper/system/SysDictTypeMapper.xml create mode 100644 boyue-system/src/main/resources/mapper/system/SysLogininforMapper.xml create mode 100644 boyue-system/src/main/resources/mapper/system/SysMenuMapper.xml create mode 100644 boyue-system/src/main/resources/mapper/system/SysNoticeMapper.xml create mode 100644 boyue-system/src/main/resources/mapper/system/SysOperLogMapper.xml create mode 100644 boyue-system/src/main/resources/mapper/system/SysPostMapper.xml create mode 100644 boyue-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml create mode 100644 boyue-system/src/main/resources/mapper/system/SysRoleMapper.xml create mode 100644 boyue-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml create mode 100644 boyue-system/src/main/resources/mapper/system/SysUserMapper.xml create mode 100644 boyue-system/src/main/resources/mapper/system/SysUserPostMapper.xml create mode 100644 boyue-system/src/main/resources/mapper/system/SysUserRoleMapper.xml create mode 100644 doc/image/code-edit.png create mode 100644 doc/image/code-show.png create mode 100644 doc/image/form-edit.png create mode 100644 doc/image/logo.png create mode 100644 doc/image/online-mb-code.png create mode 100644 doc/image/online-mb-edit.png create mode 100644 doc/image/online-mb-list.png create mode 100644 doc/代码生成.drawio create mode 100644 doc/关于多数据源与分布式事务.drawio create mode 100644 doc/微信登录.drawio create mode 100644 doc/支付流程图.drawio create mode 100644 doc/支付系统订单状态图.drawio create mode 100644 doc/权限控制.md create mode 100644 doc/模块依赖关系.drawio create mode 100644 doc/登录+JWT逻辑.drawio create mode 100644 doc/若依环境使用手册.docx create mode 100644 doc/邮箱或短信验证码登录注册重置流程图.drawio create mode 100644 doc/限流逻辑.drawio create mode 100644 pom.xml create mode 100644 sql/boyuehasfj.sql create mode 100644 sql/hasfj/boyuehasfj.sql diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3d92e2e --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ +###################################################################### +# Build Tools + +.gradle +/build/ +!gradle/wrapper/gradle-wrapper.jar + +target/ +!.mvn/wrapper/maven-wrapper.jar + +###################################################################### +# IDE + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### JRebel ### +rebel.xml + +### NetBeans ### +nbproject/private/ +build/* +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ + +###################################################################### +# Others +*.log +*.xml.versionsBackup +*.swp +*.lck + +!*/build/*.java +!*/build/*.html +!*/build/*.xml + +effective-pom.xml \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..37717e7 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "java", + "name": "boyueApplication", + "request": "launch", + "mainClass": "com.boyue.boyueApplication", + "projectName": "boyue-admin" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..38674ba --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,68 @@ +{ + "java.configuration.updateBuildConfiguration": "interactive", + "java.compile.nullAnalysis.mode": "disabled", + "maven.view": "hierarchical", + "maven.executable.options": "-T 4", + "maven.pomfile.autoUpdateEffectivePOM": true, + "java.debug.settings.hotCodeReplace": "auto", + "spring-boot.ls.java.home": "", + "java.jdt.ls.vmargs": "-XX:+UseParallelGC -XX:GCTimeRatio=9 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true -Xmx4G -Xms4G -Xlog:disable", + "maven.excludedFolders": [ + "**/.vscode", + "**/.idea", + "**/target", + "**/.*", + "**/node_modules", + "**/target", + "**/bin", + "**/archetype-resources" + ], + "boot-java.rewrite.refactorings.on": true, + "maven.executable.preferMavenWrapper": true, + "java.import.maven.enabled": true, + "java.dependency.packagePresentation": "hierarchical", + "dbcode.connections": [ + { + "connectionId": "btr-_nFe7R0oOvCj0mMun", + "name": "ry-mysql", + "driver": "mysql", + "connectionType": "host", + "host": "127.0.0.1", + "port": 3306, + "ssl": false, + "username": "root", + "password": "123456", + "savePassword": "secretStorage", + "database": "ry", + "connectionTimeout": 30, + "driverOptions": { + "retrievePublickey": true + } + }, + { + "connectionId": "7NX2UhXl__9t3Ca6TzEsB", + "name": "ry-postgres", + "driver": "postgres", + "connectionType": "host", + "host": "127.0.0.1", + "port": 5432, + "ssl": false, + "username": "postgres", + "password": "123456", + "savePassword": "secretStorage", + "connectionTimeout": 30 + }, + { + "connectionId": "fNsY4HlOb21w_5TnIGy_d", + "name": "localhost", + "driver": "redis", + "connectionType": "host", + "host": "127.0.0.1", + "port": 6379, + "ssl": false, + "savePassword": "na", + "readOnly": false, + "connectionTimeout": 30 + } + ], +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..54416f5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2018 boyue + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..53d511e --- /dev/null +++ b/README.md @@ -0,0 +1,214 @@ +

+ + logo + + + + + logo + +

+

boyue-Geek v3.8.9-G

+

基于SpringBoot3+Vue3前后端分离的Java快速开发框架

+

+ +

+ +# 当前版本是3.8.9-G + +本人的其他两个推荐搭配的项目 + +1. [boyue-App-Geek: 这是若依极客生态的小程序版本 (gitee.com)](https://gitee.com/geek-xd/geek-uniapp-vue3-uview-plus-uchart) +2. [boyue-Vue3-Geek: 这是若依极客生态的Vue3版本 (gitee.com)](https://gitee.com/geek-xd/ruo-yi-vue3-geek) + +与本项目同为一个作者开发,兼容性最好,学习成本最低。 + +# 引言 + +boyue-Vue与boyue-App是基于SpringBoot2+Vue2打造的企业级开发框架,得到了广大开发者的喜爱和积极反馈。随着技术的迭代进步,SpringBoot3与Vue3逐渐进入开发者的视野。为了满足开发者对于新技术的追求,boyue官方文档提供了SpringBoot2至SpringBoot3的升级方法。与此同时,社区也涌现出了boyue-Vue3、boyue-App-Vue3的版本,展现了开发者社区对于技术升级的热情与努力。 + +然而,在升级的过程中,官方的方法为了兼顾Java1.8的特性与一些老旧的方法,未完全拥抱SpringBoot3与Java17的全部特性。而社区的boyue-Vue3、boyue-App-Vue3版本由于出自不同的团队之手,兼容性及整合性上存在些许不足。更为关键的是,尽管这些版本支持TypeScript,但缺乏与之相匹配的tsconfig.json配置文件,这使得在主流编辑器如VSCode中,TypeScript的语法提示环境并未达到最佳状态。 + +鉴于此,boyue-Geek生态应运而生。它旨在为广大开发者提供一个既保留原版本核心特性,又整合社区版优点的全新解决方案。在boyue-Geek中,我们深入调研了企业开发中常用的boyue扩展,并直接在框架中集成,确保开发者能够快速上手,高效开发。同时,我们采用了最新的SpringBoot3+Vue3技术栈,彻底移除了为了兼容Java1.8而保留的老旧方法。更为重要的是,我们为TypeScript开发环境加入了常用的tsconfig.json配置,使得开发者在VSCode等编辑器中能够获得更为舒适、便捷的语法提示体验。 + +boyue-Geek不仅仅是一个简单的升级版本,更是对于boyue生态的一次全面优化与整合。我们相信,通过boyue-Geek,开发者将能够更为高效、愉悦地开发出优秀的企业级应用。 + +## 平台简介 + +若依是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。 + +* 前端采用Vue3、Element Plus。 +* 后端采用Spring Boot3、Spring Security、Redis & Jwt。 +* 权限认证使用Jwt,支持多终端认证系统。 +* 支持加载动态权限菜单,多方式轻松权限控制。 +* 高效率开发,使用代码生成器可以一键生成前后端代码。 +* 多数据源与分库分表默认集成 +* 所有非基本模块可随意插拔,让开发更加简单高效 +* 提供了多个工具模块助力开发,如:在线接口模块、mybatis-jpa模块 +* 提供了多个常见业务模块简化开发,如:第三方认证模块、支付模块 +* 提供了多个常见的服务模块集成开发,如:websocket模块、minio模块 +* 特别鸣谢:[element](https://github.com/ElemeFE/element),[vue-element-admin](https://github.com/PanJiaChen/vue-element-admin),[eladmin-web](https://github.com/elunez/eladmin-web)。 +* 阿里云折扣场:[点我进入](http://aly.boyue.vip),腾讯云秒杀场:[点我进入](http://txy.boyue.vip)   +* 阿里云优惠券:[点我领取](https://www.aliyun.com/minisite/goods?userCode=brki8iof&share_source=copy_link),腾讯云优惠券:[点我领取](https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console)   + +## 本项目与原项目的区别 + +### 核心 + +**模块化架构设计,支持各个模块的快速安拆,对第三方认证、第三方支付模块设计了基础的规范和基础模块。** + +### 细节 + +1. 改用SpringBoot3+java17的更新的技术栈并改掉所有的弃用的方法,全面拥抱java17的全新特性。 +2. 升级了代码生成器(配合本项目的vue3版本才可用),使关联表生成更加简单! +3. 改用最新版本的SpringSecurity安全框架,以及采用最新的lambda 表达式的配置方式,更加通俗易懂! +4. 自动Api文档以springfox替代springdoc来适配knife4j框架的4.x版本,更好的适配springboot3! +5. 默认引入mybatis-plus增强mybatis,并自创工具模块mybatis-jpa简化CRUD! +6. 默认引入lombok简化代码(注:基础模块并未使用mybatis-plus和lombok,对这两个扩展有争议的小伙伴可以直接删除,不会影响到框架本身滴,以及knife4j直接删除也不会影响到springdoc,主要还是为了方便咱们开发者呢!) +7. 提供了大量可随意插拔的模块,助力快速开发!同时boyue插件集成中的常用插件也以模块的形式直接集成在了项目中,也是可以随意插拔的嗷~ + +## 模块介绍(简单开发必看) + +* 最简单的开发就是删除所有的可移除模块,按需添加模块。 +* 测试中的模块请自己使用的时候一定要测试一下。 +* 对于小白,开发中的模块请直接删除。 + +``` + + +com.boyue +├── common // 工具类 +│ └── annotation // 自定义注解 +│ └── config // 全局配置 +│ └── constant // 通用常量 +│ └── core // 核心控制 +│ └── enums // 通用枚举 +│ └── exception // 通用异常 +│ └── filter // 过滤器处理 +│ └── utils // 通用类处理 +├── framework // 框架核心 +│ └── aspectj // 注解实现 +│ └── config // 系统配置 +│ └── datasource // 数据权限 +│ └── interceptor // 拦截器 +│ └── manager // 异步处理 +│ └── security // 权限控制 +│ └── web // 前端控制 +├── boyue-admin // 后台服务 +├── boyue-pay // 支付场景(开发中) +│ └── common // 支付框架基础模块(测试中) +│ └── sqb // 收钱吧支付模块(测试中) +│ └── wx // 微信支付模块(待开发) +│ └── alipay // 支付宝支付模块(开发中) +│ └── starter // 支付场景启动器 +├── boyue-oauth // 第三方认证场景(测试中) +│ └── common // 第三方认证基础模块(开发中) +│ └── justauth // 网站第三方认证模块(测试中,参照若依扩展改进,因没有这么多场景的code,请大家测试出问题后help解决一下发出来) +│ └── wx // 微信小程序认证模块(测试中) +│ └── phone // 手机认证模块(测试中) +│ └── email // 邮箱认证模块(开发中) +│ └── starter // 第三方认证启动器 +├── boyue-middleware // 中间件场景(可移除) +│ └── minio // minio文件服务模块 +│ └── rabbitmq // rabbitmq队列服务模块 +│ └── redis // redis缓存服务模块(与ehcache插件同类,两者二选一) +│ └── starter // 中间件整合模块 +├── boyue-models // 业务场景 +│ └── online // 在线开发模块(可移除) +│ └── quartz // 定时任务(可移除) +│ └── generator // 代码生成(可移除) +│ └── starter // 业务场景启动器 +├── boyue-plugins // 插件 +│ └── ehcache // ehcache缓存插件(与redis模块同类,两者二选一) +│ └── mybatis-jpa // mybatis-jpa插件(可移除)(简化CRUD,以数据模型为基础开发) +│ └── mybatis-plus // mybatis-plus插件(可移除)(简化CRUD) +│ └── mybatis-interceptor // mybatis-interceptor插件(可移除)(简化数据鉴权和分页,扩展性强) +│ └── atomikos // atomikos分布式事务插件(可移除) +│ └── netty // netty插件(可移除) +│ └── websocket // websocket插件(可移除) +│ └── starter // 插件整合模块 +├── boyue-system // 系统代码 +``` + +## 内置功能 + +1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。 +2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。 +3. 岗位管理:配置系统用户所属担任职务。 +4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。 +5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。 +6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。 +7. 参数管理:对系统动态配置常用参数。 +8. 通知公告:系统通知公告信息发布维护。 +9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。 +10. 登录日志:系统登录日志记录查询包含登录异常。 +11. 在线用户:当前系统中活跃用户状态监控。 +12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。 +13. 代码生成:前后端代码的生成(java、html、xml、sql)支持CRUD下载 。 +14. 系统接口:根据业务代码自动生成相关的api接口文档。 +15. 服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。 +16. 缓存监控:对系统的缓存信息查询,命令统计等。 +17. 在线构建器:拖动表单元素生成相应的HTML代码。 +18. 连接池监视:监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。 +19. 支付场景 +20. 第三方登录场景 +21. 中间件场景 + +## 演示图 + +### 新加功能和增强功能演示 + + + + + + + + + + + + + + +
+ +### 原有功能演示 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +# 联系我们: + +QQ交流群:744785891 diff --git a/bin/clean.bat b/bin/clean.bat new file mode 100644 index 0000000..24c0974 --- /dev/null +++ b/bin/clean.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [Ϣ] target· +echo. + +%~d0 +cd %~dp0 + +cd .. +call mvn clean + +pause \ No newline at end of file diff --git a/bin/package.bat b/bin/package.bat new file mode 100644 index 0000000..c693ec0 --- /dev/null +++ b/bin/package.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [Ϣ] Weḅwar/jarļ +echo. + +%~d0 +cd %~dp0 + +cd .. +call mvn clean package -Dmaven.test.skip=true + +pause \ No newline at end of file diff --git a/bin/run.bat b/bin/run.bat new file mode 100644 index 0000000..d15561d --- /dev/null +++ b/bin/run.bat @@ -0,0 +1,14 @@ +@echo off +echo. +echo [��Ϣ] ʹ��Jar��������Web���̡� +echo. + +cd %~dp0 +cd ../boyue-admin/target + +set JAVA_OPTS=-Xms256m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m + +java -jar %JAVA_OPTS% boyue-admin.jar + +cd bin +pause \ No newline at end of file diff --git a/boyue-admin/pom.xml b/boyue-admin/pom.xml new file mode 100644 index 0000000..1850d38 --- /dev/null +++ b/boyue-admin/pom.xml @@ -0,0 +1,118 @@ + + + + boyuehasfj-java + com.boyue + 3.8.9-G + + 4.0.0 + jar + boyue-admin + + + web服务入口 + + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + org.springframework.boot + spring-boot-devtools + true + + + + + com.mysql + mysql-connector-j + + + + org.postgresql + postgresql + + + + + com.boyue + boyue-framework + + + + + com.boyue + boyue-auth-starter + + + + + com.boyue + boyue-pay-starter + + + + + com.boyue + boyue-middleware-starter + + + + + com.boyue + boyue-plugins-starter + + + + + com.boyue + boyue-models-starter + + + + + com.boyue + boyue-file-starter + + + + + com.github.xiaoymin + knife4j-openapi3-jakarta-spring-boot-starter + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + org.apache.maven.plugins + maven-war-plugin + 3.1.0 + + false + ${project.artifactId} + + + + ${project.artifactId} + + + \ No newline at end of file diff --git a/boyue-admin/src/main/java/com/boyue/BoYueApplication.java b/boyue-admin/src/main/java/com/boyue/BoYueApplication.java new file mode 100644 index 0000000..84c908a --- /dev/null +++ b/boyue-admin/src/main/java/com/boyue/BoYueApplication.java @@ -0,0 +1,43 @@ +package com.boyue; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.Environment; + +/** + * 启动程序 + * + * @author boyue + */ +@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }, + excludeName = { + // 排除Flowable自动配置 + "org.flowable.spring.boot.process.ProcessEngineAutoConfiguration", + "org.flowable.spring.boot.process.ProcessEngineServicesAutoConfiguration", + "org.flowable.spring.boot.app.AppEngineAutoConfiguration", + "org.flowable.spring.boot.app.AppEngineServicesAutoConfiguration" + } +) +public class BoYueApplication { + public static void main(String[] args) throws UnknownHostException { + // System.setProperty("spring.devtools.restart.enabled", "false"); + ConfigurableApplicationContext application = SpringApplication.run(BoYueApplication.class, args); + System.out.println("(♥◠‿◠)ノ゙ 启动成功 ლ(´ڡ`ლ)゙ \n"); + + Environment env = application.getEnvironment(); + String ip = InetAddress.getLocalHost().getHostAddress(); + String port = env.getProperty("server.port"); + System.out.println("\n----------------------------------------------------------\n" + + " Application boyue-Geek is running! Access URLs:\n" + + " Local: http://localhost:" + port + "/\n" + + " External: http://" + ip + ":" + port + "/\n" + + " Swagger文档: http://" + ip + ":" + port + "/swagger-ui/index.html\n" + + " Knife4j文档: http://" + ip + ":" + port + "/doc.html" + "" + "\n" + + "----------------------------------------------------------"); + } +} diff --git a/boyue-admin/src/main/java/com/boyue/BoYueServletInitializer.java b/boyue-admin/src/main/java/com/boyue/BoYueServletInitializer.java new file mode 100644 index 0000000..90df777 --- /dev/null +++ b/boyue-admin/src/main/java/com/boyue/BoYueServletInitializer.java @@ -0,0 +1,18 @@ +package com.boyue; + +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +/** + * web容器中进行部署 + * + * @author boyue + */ +public class BoYueServletInitializer extends SpringBootServletInitializer +{ + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) + { + return application.sources(BoYueApplication.class); + } +} diff --git a/boyue-admin/src/main/java/com/boyue/web/controller/common/CaptchaController.java b/boyue-admin/src/main/java/com/boyue/web/controller/common/CaptchaController.java new file mode 100644 index 0000000..f0fda88 --- /dev/null +++ b/boyue-admin/src/main/java/com/boyue/web/controller/common/CaptchaController.java @@ -0,0 +1,81 @@ +package com.boyue.web.controller.common; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import javax.imageio.ImageIO; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.FastByteArrayOutputStream; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.google.code.kaptcha.Producer; +import com.boyue.common.config.BoYueConfig; +import com.boyue.common.constant.CacheConstants; +import com.boyue.common.constant.Constants; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.utils.CacheUtils; +import com.boyue.common.utils.sign.Base64; +import com.boyue.common.utils.uuid.IdUtils; +import com.boyue.system.service.ISysConfigService; + +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 验证码操作处理 + * + * @author boyue + */ +@RestController +public class CaptchaController { + + @Resource(name = "captchaProducer") + private Producer captchaProducer; + + @Resource(name = "captchaProducerMath") + private Producer captchaProducerMath; + + @Autowired + private ISysConfigService configService; + + /** + * 生成验证码 + */ + @GetMapping("/captchaImage") + public AjaxResult getCode(HttpServletResponse response) throws IOException { + AjaxResult ajax = AjaxResult.success(); + boolean captchaEnabled = configService.selectCaptchaEnabled(); + ajax.put("captchaEnabled", captchaEnabled); + if (!captchaEnabled) { + return ajax; + } + // 保存验证码信息 + String uuid = IdUtils.simpleUUID(), capStr = null, code = null; + BufferedImage image = null; + // 生成验证码 + String captchaType = BoYueConfig.getCaptchaType(); + if ("math".equals(captchaType)) { + String capText = captchaProducerMath.createText(); + capStr = capText.substring(0, capText.lastIndexOf("@")); + code = capText.substring(capText.lastIndexOf("@") + 1); + image = captchaProducerMath.createImage(capStr); + } else if ("char".equals(captchaType)) { + capStr = code = captchaProducer.createText(); + image = captchaProducer.createImage(capStr); + } + CacheUtils.put(CacheConstants.CAPTCHA_CODE_KEY, uuid, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES); + // 转换流信息写出 + FastByteArrayOutputStream os = new FastByteArrayOutputStream(); + try { + ImageIO.write(image, "jpg", os); + } catch (IOException e) { + return AjaxResult.error(e.getMessage()); + } + ajax.put("uuid", uuid); + ajax.put("img", Base64.encode(os.toByteArray())); + return ajax; + } +} diff --git a/boyue-admin/src/main/java/com/boyue/web/controller/common/CommonController.java b/boyue-admin/src/main/java/com/boyue/web/controller/common/CommonController.java new file mode 100644 index 0000000..30ca4fc --- /dev/null +++ b/boyue-admin/src/main/java/com/boyue/web/controller/common/CommonController.java @@ -0,0 +1,376 @@ +package com.boyue.web.controller.common; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +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 org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.multipart.MultipartFile; + +import com.boyue.common.annotation.Anonymous; +import com.boyue.common.config.BoYueConfig; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.file.FileUtils; +import com.boyue.common.utils.file.MimeTypeUtils; +import com.boyue.file.utils.FileOperateUtils; +import com.boyue.framework.config.ServerConfig; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 通用请求处理 + * + * @author boyue + */ +@Tag(name = "通用请求处理") +@RestController +@RequestMapping("/common") +public class CommonController { + + private static final Logger log = LoggerFactory.getLogger(CommonController.class); + + private static final String FILE_DELIMETER = ","; + + @Autowired + private ServerConfig serverConfig; + + /** + * 通用下载请求 + * + * @param fileName 文件名称 + * @param delete 是否删除 + */ + @Operation(summary = "通用下载请求") + @Parameters({ + @Parameter(name = "fileName", description = "文件名称"), + @Parameter(name = "delete", description = "是否删除") + }) + @GetMapping("/download") + @Anonymous + public void fileDownload( + @RequestParam("fileName") String fileName, + @RequestParam("delete") Boolean delete, + HttpServletResponse response, + HttpServletRequest request) { + try { + if (!FileUtils.checkAllowDownload(fileName)) { + throw new Exception(StringUtils.format("文件名称({})非法,不允许下载。 ", fileName)); + } + String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1); + String filePath = BoYueConfig.getDownloadPath() + fileName; + + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + FileUtils.setAttachmentResponseHeader(response, realFileName); + // FileUtils.writeBytes(filePath, response.getOutputStream()); + FileOperateUtils.downLoad(filePath, response.getOutputStream()); + if (delete) { + FileOperateUtils.deleteFile(fileName); + } + } catch (Exception e) { + log.error("下载文件失败", e); + } + } + + /** + * 通用上传请求(单个) + */ + @Operation(summary = "通用上传请求(单个)") + @PostMapping("/upload") + @Anonymous + public AjaxResult uploadFile(MultipartFile file) throws Exception { + try { + if (file == null) { + return AjaxResult.error("上传的文件不能为空"); + } + + // 记录文件信息 + log.info("接收到文件上传请求,文件名: {}, 大小: {}KB", + file.getOriginalFilename(), file.getSize() / 1024); + + String url = FileOperateUtils.upload(file); + + log.info("文件上传成功,文件地址: {}", url); + + AjaxResult ajax = AjaxResult.success(); + ajax.put("url", url); + ajax.put("fileName", getFileName(file.getOriginalFilename())); + ajax.put("newFileName", FileUtils.getName(file.getOriginalFilename())); + ajax.put("originalFilename", file.getOriginalFilename()); + ajax.put("code", 200); // 确保返回正确的状态码 + + return ajax; + } catch (Exception e) { + log.error("文件上传失败", e); + return AjaxResult.error("文件上传失败: " + e.getMessage()); + } + } + + /** + * 从文件名中提取文件名(不带路径) + */ + private String getFileName(String name) { + if (name == null) { + return ""; + } + int lastIndex = Math.max(name.lastIndexOf('/'), name.lastIndexOf('\\')); + return lastIndex >= 0 ? name.substring(lastIndex + 1) : name; + } + + /** + * 通用上传请求(多个) + */ + @Operation(summary = "通用上传请求(多个)") + @PostMapping("/uploads") + @Anonymous + public AjaxResult uploadFiles(@RequestBody List files) + throws Exception { + try { + // 上传文件路径 + String filePath = BoYueConfig.getUploadPath(); + List urls = new ArrayList(); + List fileNames = new ArrayList(); + List newFileNames = new ArrayList(); + List originalFilenames = new ArrayList(); + for (MultipartFile file : files) { + // 上传并返回新文件名称 + String fileName = FileOperateUtils.upload(filePath, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); + String url = serverConfig.getUrl() + fileName; + urls.add(url); + fileNames.add(fileName); + newFileNames.add(FileUtils.getName(fileName)); + originalFilenames.add(file.getOriginalFilename()); + } + AjaxResult ajax = AjaxResult.success(); + ajax.put("urls", StringUtils.join(urls, FILE_DELIMETER)); + ajax.put("fileNames", StringUtils.join(fileNames, FILE_DELIMETER)); + ajax.put("newFileNames", StringUtils.join(newFileNames, FILE_DELIMETER)); + ajax.put("originalFilenames", StringUtils.join(originalFilenames, FILE_DELIMETER)); + return ajax; + } catch (Exception e) { + return AjaxResult.error(e.getMessage()); + } + } + + /** + * 本地资源通用下载 + */ + @Operation(summary = "本地资源通用下载") + @GetMapping("/download/resource") + @Anonymous + public void resourceDownload(@Parameter(name = "resource", description = "资源名称") String resource, + HttpServletRequest request, HttpServletResponse response) + throws Exception { + try { + if (!FileUtils.checkAllowDownload(resource)) { + throw new Exception(StringUtils.format("资源文件({})非法,不允许下载。 ", resource)); + } + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + FileUtils.setAttachmentResponseHeader(response, resource); + FileOperateUtils.downLoad(resource, response.getOutputStream()); + } catch (Exception e) { + log.error("下载文件失败", e); + } + } + + /** + * 删除文件请求 + * + * @param filePathParam 包含文件路径的参数对象 + * @return 删除结果 + */ + @Operation(summary = "删除文件请求") + @PostMapping("/deleteFile") + @Anonymous + public AjaxResult deleteFile(@RequestBody(required = false) FilePathParam filePathParam, + @RequestParam(value = "filePath", required = false) String filePathParam2) { + try { + String filePath; + + // 支持两种方式传参:请求体或URL参数 + if (filePathParam != null && StringUtils.isNotEmpty(filePathParam.filePath)) { + filePath = filePathParam.filePath; + } else if (StringUtils.isNotEmpty(filePathParam2)) { + filePath = filePathParam2; + } else { + return AjaxResult.error("文件路径不能为空"); + } + + log.info("原始文件路径: {}", filePath); + + // 处理文件路径格式 + if (filePath.contains("http://") || filePath.contains("https://")) { + // 如果是完整URL,提取文件名或相对路径部分 + if (filePath.contains("/profile/")) { + filePath = filePath.substring(filePath.indexOf("/profile/") + 9); + } else { + // 尝试从路径中提取文件名 + filePath = filePath.substring(filePath.lastIndexOf('/') + 1); + } + } + + // 处理反斜杠 + filePath = filePath.replace('\\', '/'); + + // 去除开头的斜杠 + if (filePath.startsWith("/")) { + filePath = filePath.substring(1); + } + + log.info("处理后的文件路径: {}", filePath); + + // 安全检查 + if (!FileUtils.checkAllowDownload(filePath)) { + return AjaxResult.error(StringUtils.format("文件名称({})非法,不允许删除。", filePath)); + } + + // 执行删除 + boolean result = FileOperateUtils.deleteFile(filePath); + if (result) { + return AjaxResult.success("删除成功"); + } else { + return AjaxResult.error("文件删除失败"); + } + } catch (Exception e) { + log.error("删除文件失败", e); + return AjaxResult.error("删除文件失败: " + e.getMessage()); + } + } + + /** + * 删除文件请求 - GET方法 + * + * @param filePath 文件路径 + * @return 删除结果 + */ + @Operation(summary = "删除文件请求 - GET方式") + @GetMapping("/deleteFile") + @Anonymous + public AjaxResult deleteFileGet(@RequestParam("filePath") String filePath) { + FilePathParam param = new FilePathParam(); + param.setFilePath(filePath); + return deleteFile(param, null); + } + + /** + * 文件路径参数类 + */ + public static class FilePathParam { + public String filePath; + + public FilePathParam() { + } + + public String getFilePath() { + return filePath; + } + + public void setFilePath(String filePath) { + this.filePath = filePath; + } + } + + /** + * 文件上传诊断工具 + */ + @Operation(summary = "文件上传诊断工具") + @GetMapping("/diagnose-upload") + @Anonymous + public AjaxResult diagnoseUpload() { + try { + Map diagnostics = new HashMap<>(); + + // 获取系统配置 + diagnostics.put("uploadPath", BoYueConfig.getProfile()); + diagnostics.put("fileServer", BoYueConfig.getFileServer()); + + // 系统信息 + Map systemInfo = new HashMap<>(); + systemInfo.put("os.name", System.getProperty("os.name")); + systemInfo.put("file.separator", File.separator); + systemInfo.put("user.dir", System.getProperty("user.dir")); + diagnostics.put("systemInfo", systemInfo); + + // 检查目录 + List pathsToCheck = new ArrayList<>(); + pathsToCheck.add(BoYueConfig.getProfile()); + pathsToCheck.add(BoYueConfig.getProfile() + "/files"); + pathsToCheck.add(BoYueConfig.getProfile() + "/files/master"); + + List> directoryChecks = new ArrayList<>(); + for (String path : pathsToCheck) { + Map check = new HashMap<>(); + File directory = new File(path); + check.put("path", path); + check.put("exists", directory.exists()); + check.put("isDirectory", directory.exists() && directory.isDirectory()); + check.put("canWrite", directory.exists() && directory.canWrite()); + check.put("canRead", directory.exists() && directory.canRead()); + check.put("absolutePath", directory.getAbsolutePath()); + directoryChecks.add(check); + } + diagnostics.put("directoryChecks", directoryChecks); + + // 尝试创建测试文件 + try { + String testDirectory = BoYueConfig.getProfile() + "/files/master"; + File dir = new File(testDirectory); + if (!dir.exists()) { + dir.mkdirs(); + } + + String testFilePath = testDirectory + "/test_" + System.currentTimeMillis() + ".txt"; + File testFile = new File(testFilePath); + boolean created = testFile.createNewFile(); + + Map testFileInfo = new HashMap<>(); + testFileInfo.put("path", testFilePath); + testFileInfo.put("created", created); + testFileInfo.put("exists", testFile.exists()); + + if (created && testFile.exists()) { + // 写入一些内容 + try (java.io.FileWriter writer = new java.io.FileWriter(testFile)) { + writer.write("Test file created at: " + new java.util.Date()); + } + + // 生成访问URL + String url = FileOperateUtils.getURL(testFilePath.substring(BoYueConfig.getProfile().length() + 1)); + testFileInfo.put("url", url); + + // 删除测试文件 + boolean deleted = testFile.delete(); + testFileInfo.put("deleted", deleted); + } + + diagnostics.put("testFileInfo", testFileInfo); + } catch (Exception e) { + Map testFileError = new HashMap<>(); + testFileError.put("error", e.getMessage()); + testFileError.put("stackTrace", e.getStackTrace()); + diagnostics.put("testFileError", testFileError); + } + + return AjaxResult.success("诊断完成").put("diagnostics", diagnostics); + } catch (Exception e) { + log.error("诊断失败", e); + return AjaxResult.error("诊断失败: " + e.getMessage()); + } + } +} diff --git a/boyue-admin/src/main/java/com/boyue/web/controller/monitor/CacheController.java b/boyue-admin/src/main/java/com/boyue/web/controller/monitor/CacheController.java new file mode 100644 index 0000000..0ae20c6 --- /dev/null +++ b/boyue-admin/src/main/java/com/boyue/web/controller/monitor/CacheController.java @@ -0,0 +1,122 @@ +package com.boyue.web.controller.monitor; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.springframework.cache.Cache.ValueWrapper; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.constant.CacheConstants; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.text.Convert; +import com.boyue.common.utils.CacheUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.system.domain.SysCache; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 缓存监控 + * + * @author boyue + */ +@Tag(name = "缓存监控") +@RestController +@RequestMapping("/monitor/cache") +public class CacheController { + + private static String tmpCacheName = ""; + + private final static List caches = new ArrayList(); + { + caches.add(new SysCache(CacheConstants.LOGIN_TOKEN_KEY, "用户信息")); + caches.add(new SysCache(CacheConstants.SYS_CONFIG_KEY, "配置信息")); + caches.add(new SysCache(CacheConstants.SYS_DICT_KEY, "数据字典")); + caches.add(new SysCache(CacheConstants.CAPTCHA_CODE_KEY, "验证码")); + caches.add(new SysCache(CacheConstants.PHONE_CODES, "短信验证码")); + caches.add(new SysCache(CacheConstants.EMAIL_CODES, "邮箱验证码")); + caches.add(new SysCache(CacheConstants.REPEAT_SUBMIT_KEY, "防重提交")); + caches.add(new SysCache(CacheConstants.RATE_LIMIT_KEY, "限流处理")); + caches.add(new SysCache(CacheConstants.PWD_ERR_CNT_KEY, "密码错误次数")); + caches.add(new SysCache(CacheConstants.IP_ERR_CNT_KEY, "IP错误次数")); + caches.add(new SysCache(CacheConstants.FILE_MD5_PATH_KEY, "path-md5")); + caches.add(new SysCache(CacheConstants.FILE_PATH_MD5_KEY, "md5-path")); + } + + @Operation(summary = "获取缓存名列表") + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping("/getNames") + public AjaxResult cache() { + return AjaxResult.success(caches); + } + + @Operation(summary = "获取缓存键列表") + @Parameters({ + @Parameter(name = "cacheName", description = "缓存名称", required = true), + }) + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping("/getKeys/{cacheName}") + public AjaxResult getCacheKeys(@PathVariable String cacheName) { + tmpCacheName = cacheName; + Set keyset = CacheUtils.getkeys(cacheName); + return AjaxResult.success(keyset); + } + + @Operation(summary = "获取缓存值列表") + @Parameters({ + @Parameter(name = "cacheName", description = "缓存名称", required = true), + @Parameter(name = "cacheKey", description = "缓存键名", required = true) + }) + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping("/getValue/{cacheName}/{cacheKey}") + public AjaxResult getCacheValue(@PathVariable String cacheName, @PathVariable String cacheKey) { + ValueWrapper valueWrapper = CacheUtils.get(cacheName, cacheKey); + SysCache sysCache = new SysCache(); + sysCache.setCacheName(cacheName); + sysCache.setCacheKey(cacheKey); + if (StringUtils.isNotNull(valueWrapper)) { + sysCache.setCacheValue(Convert.toStr(valueWrapper.get(), "")); + } + return AjaxResult.success(sysCache); + } + + @Operation(summary = "清除缓存") + @Parameters({ + @Parameter(name = "cacheName", description = "缓存名称", required = true) + }) + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @DeleteMapping("/clearCacheName/{cacheName}") + public AjaxResult clearCacheName(@PathVariable String cacheName) { + CacheUtils.clear(cacheName); + return AjaxResult.success(); + } + + @Operation(summary = "清除缓存值") + @Parameters({ + @Parameter(name = "cacheKey", description = "缓存键名", required = true) + }) + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @DeleteMapping("/clearCacheKey/{cacheKey}") + public AjaxResult clearCacheKey(@PathVariable String cacheKey) { + CacheUtils.removeIfPresent(tmpCacheName, cacheKey); + return AjaxResult.success(); + } + + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @DeleteMapping("/clearCacheAll") + public AjaxResult clearCacheAll() { + for (String cacheName : CacheUtils.getCacheManager().getCacheNames()) { + CacheUtils.clear(cacheName); + } + return AjaxResult.success(); + } +} diff --git a/boyue-admin/src/main/java/com/boyue/web/controller/monitor/ServerController.java b/boyue-admin/src/main/java/com/boyue/web/controller/monitor/ServerController.java new file mode 100644 index 0000000..1f9b317 --- /dev/null +++ b/boyue-admin/src/main/java/com/boyue/web/controller/monitor/ServerController.java @@ -0,0 +1,32 @@ +package com.boyue.web.controller.monitor; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.framework.web.domain.Server; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 服务器监控 + * + * @author boyue + */ +@Tag(name = "服务器监控") +@RestController +@RequestMapping("/monitor/server") +public class ServerController { + + @Operation(summary = "获取服务器监控信息") + @PreAuthorize("@ss.hasPermi('monitor:server:list')") + @GetMapping() + public AjaxResult getInfo() throws Exception { + Server server = new Server(); + server.copyTo(); + return AjaxResult.success(server); + } +} diff --git a/boyue-admin/src/main/java/com/boyue/web/controller/monitor/SysLogininforController.java b/boyue-admin/src/main/java/com/boyue/web/controller/monitor/SysLogininforController.java new file mode 100644 index 0000000..6cda1fb --- /dev/null +++ b/boyue-admin/src/main/java/com/boyue/web/controller/monitor/SysLogininforController.java @@ -0,0 +1,96 @@ +package com.boyue.web.controller.monitor; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.enums.BusinessType; +import com.boyue.common.utils.poi.ExcelUtil; +import com.boyue.framework.web.service.SysPasswordService; +import com.boyue.system.domain.SysLogininfor; +import com.boyue.system.service.ISysLogininforService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 系统访问记录 + * + * @author boyue + */ +@Tag(name = "系统访问记录") +@RestController +@RequestMapping("/monitor/logininfor") +public class SysLogininforController extends BaseController { + + @Autowired + private ISysLogininforService logininforService; + + @Autowired + private SysPasswordService passwordService; + + @Operation(summary = "获取系统访问记录列表") + @PreAuthorize("@ss.hasPermi('monitor:logininfor:list')") + @GetMapping("/list") + public TableDataInfo list(SysLogininfor logininfor) { + startPage(); + List list = logininforService.selectLogininforList(logininfor); + return getDataTable(list); + } + + @Operation(summary = "导出系统访问记录列表") + @Log(title = "登录日志", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('monitor:logininfor:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysLogininfor logininfor) { + List list = logininforService.selectLogininforList(logininfor); + ExcelUtil util = new ExcelUtil(SysLogininfor.class); + util.exportExcel(response, list, "登录日志"); + } + + @Operation(summary = "删除系统访问记录") + @Parameters({ + @Parameter(name = "infoIds", description = "记录id数组", required = true), + }) + @PreAuthorize("@ss.hasPermi('monitor:logininfor:remove')") + @Log(title = "登录日志", businessType = BusinessType.DELETE) + @DeleteMapping("/{infoIds}") + public AjaxResult remove(@PathVariable(name = "infoIds") Long[] infoIds) { + return toAjax(logininforService.deleteLogininforByIds(infoIds)); + } + + @Operation(summary = "清除系统访问记录") + @PreAuthorize("@ss.hasPermi('monitor:logininfor:remove')") + @Log(title = "登录日志", businessType = BusinessType.CLEAN) + @DeleteMapping("/clean") + public AjaxResult clean() { + logininforService.cleanLogininfor(); + return success(); + } + + @Operation(summary = "账户解锁") + @Parameters({ + @Parameter(name = "userName", description = "用户名", required = true), + }) + @PreAuthorize("@ss.hasPermi('monitor:logininfor:unlock')") + @Log(title = "账户解锁", businessType = BusinessType.OTHER) + @GetMapping("/unlock/{userName}") + public AjaxResult unlock(@PathVariable("userName") String userName) { + passwordService.clearLoginRecordCache(userName); + return success(); + } +} diff --git a/boyue-admin/src/main/java/com/boyue/web/controller/monitor/SysOperlogController.java b/boyue-admin/src/main/java/com/boyue/web/controller/monitor/SysOperlogController.java new file mode 100644 index 0000000..88b72ee --- /dev/null +++ b/boyue-admin/src/main/java/com/boyue/web/controller/monitor/SysOperlogController.java @@ -0,0 +1,102 @@ +package com.boyue.web.controller.monitor; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.domain.R; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.enums.BusinessType; +import com.boyue.common.utils.poi.ExcelUtil; +import com.boyue.system.domain.SysOperLog; +import com.boyue.system.service.ISysOperLogService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 操作日志记录 + * + * @author boyue + */ +@Tag(name = "操作日志记录") +@RestController +@RequestMapping("/monitor/operlog") +public class SysOperlogController extends BaseController { + + @Autowired + private ISysOperLogService operLogService; + + @Operation(summary = "获取操作日志记录列表") + @PreAuthorize("@ss.hasPermi('monitor:operlog:list')") + @GetMapping("/list") + public TableDataInfo list(SysOperLog operLog) { + startPage(); + List list = operLogService.selectOperLogList(operLog); + return getDataTable(list); + } + + @Operation(summary = "导出操作日志记录列表") + @Log(title = "操作日志", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('monitor:operlog:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysOperLog operLog) { + List list = operLogService.selectOperLogList(operLog); + ExcelUtil util = new ExcelUtil(SysOperLog.class); + util.exportExcel(response, list, "操作日志"); + } + + @Operation(summary = "删除操作日志记录") + @Parameters({ + @Parameter(name = "operIds", description = "记录id数组", required = true), + }) + @Log(title = "操作日志", businessType = BusinessType.DELETE) + @PreAuthorize("@ss.hasPermi('monitor:operlog:remove')") + @DeleteMapping("/{operIds}") + public AjaxResult remove(@PathVariable(name = "operIds") Long[] operIds) { + return toAjax(operLogService.deleteOperLogByIds(operIds)); + } + + @Operation(summary = "清除操作日志记录") + @Log(title = "操作日志", businessType = BusinessType.CLEAN) + @PreAuthorize("@ss.hasPermi('monitor:operlog:remove')") + @DeleteMapping("/clean") + public AjaxResult clean() { + operLogService.cleanOperLog(); + return success(); + } + + @Operation(summary = "业务监控") + @GetMapping("/business") + public R> business(SysOperLog operLog) { + // 查询并获取统计数据 + List> successStats = operLogService.getSuccessOperationStats(operLog); + List> failureStats = operLogService.getFailureOperationStats(operLog); + List> statusStats = operLogService.getStatusStats(operLog); + List> moduleOperationStats = operLogService.getModuleOperationStats(operLog); + // 创建一个新的 Map 来组织数据 + Map result = new LinkedHashMap<>(); + result.put("successStats", successStats); + result.put("failureStats", failureStats); + result.put("statusStats", statusStats); + result.put("moduleOperationStats", moduleOperationStats); + result.put("total", + successStats.size() + failureStats.size() + statusStats.size() + moduleOperationStats.size()); + return R.ok(result); + } +} diff --git a/boyue-admin/src/main/java/com/boyue/web/controller/monitor/SysUserOnlineController.java b/boyue-admin/src/main/java/com/boyue/web/controller/monitor/SysUserOnlineController.java new file mode 100644 index 0000000..208e021 --- /dev/null +++ b/boyue-admin/src/main/java/com/boyue/web/controller/monitor/SysUserOnlineController.java @@ -0,0 +1,72 @@ +package com.boyue.web.controller.monitor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.annotation.Log; +import com.boyue.common.constant.CacheConstants; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.domain.model.LoginUser; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.enums.BusinessType; +import com.boyue.common.utils.CacheUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.system.domain.SysUserOnline; +import com.boyue.system.service.ISysUserOnlineService; + +/** + * 在线用户监控 + * + * @author boyue + */ +@RestController +@RequestMapping("/monitor/online") +public class SysUserOnlineController extends BaseController { + + @Autowired + private ISysUserOnlineService userOnlineService; + + @PreAuthorize("@ss.hasPermi('monitor:online:list')") + @GetMapping("/list") + public TableDataInfo list(String ipaddr, String userName) { + Collection keys = CacheUtils.getkeys(CacheConstants.LOGIN_TOKEN_KEY); + List userOnlineList = new ArrayList(); + for (String key : keys) { + LoginUser user = CacheUtils.get(CacheConstants.LOGIN_TOKEN_KEY, key, LoginUser.class); + if (StringUtils.isNotEmpty(ipaddr) && StringUtils.isNotEmpty(userName)) { + userOnlineList.add(userOnlineService.selectOnlineByInfo(ipaddr, userName, user)); + } else if (StringUtils.isNotEmpty(ipaddr)) { + userOnlineList.add(userOnlineService.selectOnlineByIpaddr(ipaddr, user)); + } else if (StringUtils.isNotEmpty(userName) && StringUtils.isNotNull(user.getUser())) { + userOnlineList.add(userOnlineService.selectOnlineByUserName(userName, user)); + } else { + userOnlineList.add(userOnlineService.loginUserToUserOnline(user)); + } + } + Collections.reverse(userOnlineList); + userOnlineList.removeAll(Collections.singleton(null)); + return getDataTable(userOnlineList); + } + + /** + * 强退用户 + */ + @PreAuthorize("@ss.hasPermi('monitor:online:forceLogout')") + @Log(title = "在线用户", businessType = BusinessType.FORCE) + @DeleteMapping("/{tokenId}") + public AjaxResult forceLogout(@PathVariable String tokenId) { + CacheUtils.removeIfPresent(CacheConstants.LOGIN_TOKEN_KEY, tokenId); + return success(); + } +} diff --git a/boyue-admin/src/main/java/com/boyue/web/controller/system/SysConfigController.java b/boyue-admin/src/main/java/com/boyue/web/controller/system/SysConfigController.java new file mode 100644 index 0000000..e8bbcba --- /dev/null +++ b/boyue-admin/src/main/java/com/boyue/web/controller/system/SysConfigController.java @@ -0,0 +1,139 @@ +package com.boyue.web.controller.system; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.annotation.Anonymous; +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.enums.BusinessType; +import com.boyue.common.utils.poi.ExcelUtil; +import com.boyue.system.domain.SysConfig; +import com.boyue.system.service.ISysConfigService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 参数配置 信息操作处理 + * + * @author boyue + */ +@Tag(name = "参数配置") +@RestController +@RequestMapping("/system/config") +public class SysConfigController extends BaseController { + + @Autowired + private ISysConfigService configService; + + /** + * 获取参数配置列表 + */ + @Operation(summary = "获取参数配置列表") + @PreAuthorize("@ss.hasPermi('system:config:list')") + @GetMapping("/list") + public TableDataInfo list(SysConfig config) { + startPage(); + List list = configService.selectConfigList(config); + return getDataTable(list); + } + + @Operation(summary = "参数管理") + @Log(title = "参数管理", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:config:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysConfig config) { + List list = configService.selectConfigList(config); + ExcelUtil util = new ExcelUtil(SysConfig.class); + util.exportExcel(response, list, "参数数据"); + } + + /** + * 根据参数编号获取详细信息 + */ + @Operation(summary = "根据参数编号获取详细信息") + @PreAuthorize("@ss.hasPermi('system:config:query')") + @GetMapping(value = "/{configId}") + public AjaxResult getInfo(@PathVariable(name = "configId") Long configId) { + return success(configService.selectConfigById(configId)); + } + + /** + * 根据参数键名查询参数值 + */ + @Operation(summary = "根据参数键名查询参数值") + @GetMapping(value = "/configKey/{configKey}") + @Anonymous + public AjaxResult getConfigKey(@PathVariable(name = "configKey") String configKey) { + return success(configService.selectConfigByKey(configKey)); + } + + /** + * 新增参数配置 + */ + @Operation(summary = "新增参数配置") + @PreAuthorize("@ss.hasPermi('system:config:add')") + @Log(title = "参数管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysConfig config) { + if (!configService.checkConfigKeyUnique(config)) { + return error("新增参数'" + config.getConfigName() + "'失败,参数键名已存在"); + } + config.setCreateBy(getUsername()); + return toAjax(configService.insertConfig(config)); + } + + /** + * 修改参数配置 + */ + @Operation(summary = "修改参数配置") + @PreAuthorize("@ss.hasPermi('system:config:edit')") + @Log(title = "参数管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysConfig config) { + if (!configService.checkConfigKeyUnique(config)) { + return error("修改参数'" + config.getConfigName() + "'失败,参数键名已存在"); + } + config.setUpdateBy(getUsername()); + return toAjax(configService.updateConfig(config)); + } + + /** + * 删除参数配置 + */ + @Operation(summary = "删除参数配置") + @PreAuthorize("@ss.hasPermi('system:config:remove')") + @Log(title = "参数管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{configIds}") + public AjaxResult remove(@PathVariable(name = "configIds") Long[] configIds) { + configService.deleteConfigByIds(configIds); + return success(); + } + + /** + * 刷新参数缓存 + */ + @Operation(summary = "刷新参数缓存") + @PreAuthorize("@ss.hasPermi('system:config:remove')") + @Log(title = "参数管理", businessType = BusinessType.CLEAN) + @DeleteMapping("/refreshCache") + public AjaxResult refreshCache() { + configService.resetConfigCache(); + return success(); + } +} diff --git a/boyue-admin/src/main/java/com/boyue/web/controller/system/SysDeptController.java b/boyue-admin/src/main/java/com/boyue/web/controller/system/SysDeptController.java new file mode 100644 index 0000000..f79b4be --- /dev/null +++ b/boyue-admin/src/main/java/com/boyue/web/controller/system/SysDeptController.java @@ -0,0 +1,132 @@ +package com.boyue.web.controller.system; + +import java.util.List; + +import org.apache.commons.lang3.ArrayUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.annotation.Log; +import com.boyue.common.constant.UserConstants; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.domain.entity.SysDept; +import com.boyue.common.enums.BusinessType; +import com.boyue.common.utils.StringUtils; +import com.boyue.system.service.ISysDeptService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 部门信息 + * + * @author boyue + */ +@Tag(name = "部门信息") +@RestController +@RequestMapping("/system/dept") +public class SysDeptController extends BaseController { + + @Autowired + private ISysDeptService deptService; + + /** + * 获取部门列表 + */ + @Operation(summary = "获取部门列表") + @PreAuthorize("@ss.hasPermi('system:dept:list')") + @GetMapping("/list") + public AjaxResult list(SysDept dept) { + List depts = deptService.selectDeptList(dept); + return success(depts); + } + + /** + * 查询部门列表(排除节点) + */ + @Operation(summary = "查询部门列表", description = "排除节点") + @PreAuthorize("@ss.hasPermi('system:dept:list')") + @GetMapping("/list/exclude/{deptId}") + public AjaxResult excludeChild(@PathVariable(value = "deptId", required = false) Long deptId) { + List depts = deptService.selectDeptList(new SysDept()); + depts.removeIf(d -> d.getDeptId().intValue() == deptId + || ArrayUtils.contains(StringUtils.split(d.getAncestors(), ","), deptId + "")); + return success(depts); + } + + /** + * 根据部门编号获取详细信息 + */ + @Operation(summary = "根据部门编号获取详细信息") + @PreAuthorize("@ss.hasPermi('system:dept:query')") + @GetMapping(value = "/{deptId}") + public AjaxResult getInfo(@PathVariable(name = "deptId") Long deptId) { + deptService.checkDeptDataScope(deptId); + return success(deptService.selectDeptById(deptId)); + } + + /** + * 新增部门 + */ + @Operation(summary = "新增部门") + @PreAuthorize("@ss.hasPermi('system:dept:add')") + @Log(title = "部门管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysDept dept) { + if (!deptService.checkDeptNameUnique(dept)) { + return error("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在"); + } + dept.setCreateBy(getUsername()); + return toAjax(deptService.insertDept(dept)); + } + + /** + * 修改部门 + */ + @Operation(summary = "修改部门") + @PreAuthorize("@ss.hasPermi('system:dept:edit')") + @Log(title = "部门管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysDept dept) { + Long deptId = dept.getDeptId(); + deptService.checkDeptDataScope(deptId); + if (!deptService.checkDeptNameUnique(dept)) { + return error("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在"); + } else if (dept.getParentId().equals(deptId)) { + return error("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己"); + } else if (StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus()) + && deptService.selectNormalChildrenDeptById(deptId) > 0) { + return error("该部门包含未停用的子部门!"); + } + dept.setUpdateBy(getUsername()); + return toAjax(deptService.updateDept(dept)); + } + + /** + * 删除部门 + */ + @Operation(summary = "删除部门") + @PreAuthorize("@ss.hasPermi('system:dept:remove')") + @Log(title = "部门管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{deptId}") + public AjaxResult remove(@PathVariable(name = "deptId") Long deptId) { + if (deptService.hasChildByDeptId(deptId)) { + return warn("存在下级部门,不允许删除"); + } + if (deptService.checkDeptExistUser(deptId)) { + return warn("部门存在用户,不允许删除"); + } + deptService.checkDeptDataScope(deptId); + return toAjax(deptService.deleteDeptById(deptId)); + } +} diff --git a/boyue-admin/src/main/java/com/boyue/web/controller/system/SysDictDataController.java b/boyue-admin/src/main/java/com/boyue/web/controller/system/SysDictDataController.java new file mode 100644 index 0000000..e960505 --- /dev/null +++ b/boyue-admin/src/main/java/com/boyue/web/controller/system/SysDictDataController.java @@ -0,0 +1,126 @@ +package com.boyue.web.controller.system; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.domain.entity.SysDictData; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.enums.BusinessType; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.poi.ExcelUtil; +import com.boyue.system.service.ISysDictDataService; +import com.boyue.system.service.ISysDictTypeService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 数据字典信息 + * + * @author boyue + */ +@Tag(name = "数据字典信息(数据)") +@RestController +@RequestMapping("/system/dict/data") +public class SysDictDataController extends BaseController { + + @Autowired + private ISysDictDataService dictDataService; + + @Autowired + private ISysDictTypeService dictTypeService; + + @Operation(summary = "查询字典数据列表") + @PreAuthorize("@ss.hasPermi('system:dict:list')") + @GetMapping("/list") + public TableDataInfo list(SysDictData dictData) { + startPage(); + List list = dictDataService.selectDictDataList(dictData); + return getDataTable(list); + } + + @Operation(summary = "导出字典数据列表") + @Log(title = "字典数据", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:dict:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysDictData dictData) { + List list = dictDataService.selectDictDataList(dictData); + ExcelUtil util = new ExcelUtil(SysDictData.class); + util.exportExcel(response, list, "字典数据"); + } + + /** + * 查询字典数据详细 + */ + @Operation(summary = "查询字典数据详细") + @PreAuthorize("@ss.hasPermi('system:dict:query')") + @GetMapping(value = "/{dictCode}") + public AjaxResult getInfo(@PathVariable(name = "dictCode") Long dictCode) { + return success(dictDataService.selectDictDataById(dictCode)); + } + + /** + * 根据字典类型查询字典数据信息 + */ + @Operation(summary = "根据字典类型查询字典数据信息") + @GetMapping(value = "/type/{dictType}") + public AjaxResult dictType(@PathVariable(name = "dictType") String dictType) { + List data = dictTypeService.selectDictDataByType(dictType); + if (StringUtils.isNull(data)) { + data = new ArrayList(); + } + return success(data); + } + + /** + * 新增字典类型 + */ + @Operation(summary = "新增字典类型") + @PreAuthorize("@ss.hasPermi('system:dict:add')") + @Log(title = "字典数据", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysDictData dict) { + dict.setCreateBy(getUsername()); + return toAjax(dictDataService.insertDictData(dict)); + } + + /** + * 修改保存字典类型 + */ + @Operation(summary = "修改保存字典类型") + @PreAuthorize("@ss.hasPermi('system:dict:edit')") + @Log(title = "字典数据", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysDictData dict) { + dict.setUpdateBy(getUsername()); + return toAjax(dictDataService.updateDictData(dict)); + } + + /** + * 删除字典类型 + */ + @Operation(summary = "删除字典类型") + @PreAuthorize("@ss.hasPermi('system:dict:remove')") + @Log(title = "字典类型", businessType = BusinessType.DELETE) + @DeleteMapping("/{dictCodes}") + public AjaxResult remove(@PathVariable(name = "dictCodes") Long[] dictCodes) { + dictDataService.deleteDictDataByIds(dictCodes); + return success(); + } +} diff --git a/boyue-admin/src/main/java/com/boyue/web/controller/system/SysDictTypeController.java b/boyue-admin/src/main/java/com/boyue/web/controller/system/SysDictTypeController.java new file mode 100644 index 0000000..98fd465 --- /dev/null +++ b/boyue-admin/src/main/java/com/boyue/web/controller/system/SysDictTypeController.java @@ -0,0 +1,135 @@ +package com.boyue.web.controller.system; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.domain.entity.SysDictType; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.enums.BusinessType; +import com.boyue.common.utils.poi.ExcelUtil; +import com.boyue.system.service.ISysDictTypeService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 数据字典信息 + * + * @author boyue + */ +@Tag(name = "数据字典信息(类型)") +@RestController +@RequestMapping("/system/dict/type") +public class SysDictTypeController extends BaseController { + + @Autowired + private ISysDictTypeService dictTypeService; + + @Operation(summary = "查询字典类型列表") + @PreAuthorize("@ss.hasPermi('system:dict:list')") + @GetMapping("/list") + public TableDataInfo list(SysDictType dictType) { + startPage(); + List list = dictTypeService.selectDictTypeList(dictType); + return getDataTable(list); + } + + @Operation(summary = "导出字典类型列表") + @Log(title = "字典类型", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:dict:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysDictType dictType) { + List list = dictTypeService.selectDictTypeList(dictType); + ExcelUtil util = new ExcelUtil(SysDictType.class); + util.exportExcel(response, list, "字典类型"); + } + + /** + * 查询字典类型详细 + */ + @Operation(summary = "查询字典类型详细") + @PreAuthorize("@ss.hasPermi('system:dict:query')") + @GetMapping(value = "/{dictId}") + public AjaxResult getInfo(@PathVariable(name = "dictId") Long dictId) { + return success(dictTypeService.selectDictTypeById(dictId)); + } + + /** + * 新增字典类型 + */ + @Operation(summary = "新增字典类型") + @PreAuthorize("@ss.hasPermi('system:dict:add')") + @Log(title = "字典类型", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysDictType dict) { + if (!dictTypeService.checkDictTypeUnique(dict)) { + return error("新增字典'" + dict.getDictName() + "'失败,字典类型已存在"); + } + dict.setCreateBy(getUsername()); + return toAjax(dictTypeService.insertDictType(dict)); + } + + /** + * 修改字典类型 + */ + @Operation(summary = "修改字典类型") + @PreAuthorize("@ss.hasPermi('system:dict:edit')") + @Log(title = "字典类型", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysDictType dict) { + if (!dictTypeService.checkDictTypeUnique(dict)) { + return error("修改字典'" + dict.getDictName() + "'失败,字典类型已存在"); + } + dict.setUpdateBy(getUsername()); + return toAjax(dictTypeService.updateDictType(dict)); + } + + /** + * 删除字典类型 + */ + @Operation(summary = "删除字典类型") + @PreAuthorize("@ss.hasPermi('system:dict:remove')") + @Log(title = "字典类型", businessType = BusinessType.DELETE) + @DeleteMapping("/{dictIds}") + public AjaxResult remove(@PathVariable(name = "dictIds") Long[] dictIds) { + dictTypeService.deleteDictTypeByIds(dictIds); + return success(); + } + + /** + * 刷新字典缓存 + */ + @Operation(summary = "刷新字典缓存") + @PreAuthorize("@ss.hasPermi('system:dict:remove')") + @Log(title = "字典类型", businessType = BusinessType.CLEAN) + @DeleteMapping("/refreshCache") + public AjaxResult refreshCache() { + dictTypeService.resetDictCache(); + return success(); + } + + /** + * 获取字典选择框列表 + */ + @Operation(summary = "获取字典选择框列表") + @GetMapping("/optionselect") + public AjaxResult optionselect() { + List dictTypes = dictTypeService.selectDictTypeAll(); + return success(dictTypes); + } +} diff --git a/boyue-admin/src/main/java/com/boyue/web/controller/system/SysIndexController.java b/boyue-admin/src/main/java/com/boyue/web/controller/system/SysIndexController.java new file mode 100644 index 0000000..8777739 --- /dev/null +++ b/boyue-admin/src/main/java/com/boyue/web/controller/system/SysIndexController.java @@ -0,0 +1,34 @@ +package com.boyue.web.controller.system; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.config.BoYueConfig; +import com.boyue.common.utils.StringUtils; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 首页 + * + * @author boyue + */ +@Tag(name = "首页") +@RestController +public class SysIndexController { + + /** 系统基础配置 */ + @Autowired + private BoYueConfig BoYueConfig; + + /** + * 访问首页,提示语 + */ + @Operation(summary = "访问首页", description = "提示语") + @RequestMapping("/") + public String index() { + return StringUtils.format("欢迎使用{}后台管理框架,当前版本:v{},请通过前端地址访问。", BoYueConfig.getName(), BoYueConfig.getVersion()); + } +} diff --git a/boyue-admin/src/main/java/com/boyue/web/controller/system/SysLoginController.java b/boyue-admin/src/main/java/com/boyue/web/controller/system/SysLoginController.java new file mode 100644 index 0000000..f93f477 --- /dev/null +++ b/boyue-admin/src/main/java/com/boyue/web/controller/system/SysLoginController.java @@ -0,0 +1,112 @@ +package com.boyue.web.controller.system; + +import java.util.List; +import java.util.Set; + +import org.springframework.beans.factory.annotation.Autowired; +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 com.boyue.common.constant.Constants; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.domain.entity.SysMenu; +import com.boyue.common.core.domain.entity.SysUser; +import com.boyue.common.core.domain.model.LoginBody; +import com.boyue.common.core.domain.model.LoginUser; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.file.utils.FileOperateUtils; +import com.boyue.framework.web.service.SysLoginService; +import com.boyue.framework.web.service.SysPermissionService; +import com.boyue.framework.web.service.TokenService; +import com.boyue.system.service.ISysMenuService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 登录验证 + * + * @author boyue + */ +@Tag(name = "登录验证") +@RestController +public class SysLoginController { + + @Autowired + private SysLoginService loginService; + + @Autowired + private ISysMenuService menuService; + + @Autowired + private SysPermissionService permissionService; + + @Autowired + private TokenService tokenService; + + /** + * 登录方法 + * + * @param loginBody 登录信息 + * @return 结果 + */ + @Operation(summary = "登录方法") + @PostMapping("/login") + public AjaxResult login(@RequestBody LoginBody loginBody) { + AjaxResult ajax = AjaxResult.success(); + // 生成令牌 + String token = loginService.login( + loginBody.getUsername(), + loginBody.getPassword(), + loginBody.getCode(), + loginBody.getUuid()); + ajax.put(Constants.TOKEN, token); + return ajax; + } + + /** + * 获取用户信息 + * + * @return 用户信息 + */ + @Operation(summary = "获取用户信息") + @GetMapping("getInfo") + public AjaxResult getInfo() { + LoginUser loginUser = SecurityUtils.getLoginUser(); + SysUser user = loginUser.getUser(); + // 角色集合 + Set roles = permissionService.getRolePermission(user); + // 权限集合 + Set permissions = permissionService.getMenuPermission(user); + if (!loginUser.getPermissions().equals(permissions)) { + loginUser.setPermissions(permissions); + tokenService.refreshToken(loginUser); + } + if (user.getAvatar() != null) { + try { + user.setAvatar(FileOperateUtils.getURL(user.getAvatar())); + } catch (Exception e) { + } + } + AjaxResult ajax = AjaxResult.success(); + ajax.put("user", user); + ajax.put("roles", roles); + ajax.put("permissions", permissions); + return ajax; + } + + /** + * 获取路由信息 + * + * @return 路由信息 + */ + @Operation(summary = "获取路由信息") + @GetMapping("getRouters") + public AjaxResult getRouters() { + Long userId = SecurityUtils.getUserId(); + List menus = menuService.selectMenuTreeByUserId(userId); + return AjaxResult.success(menuService.buildMenus(menus)); + } +} diff --git a/boyue-admin/src/main/java/com/boyue/web/controller/system/SysMenuController.java b/boyue-admin/src/main/java/com/boyue/web/controller/system/SysMenuController.java new file mode 100644 index 0000000..736a276 --- /dev/null +++ b/boyue-admin/src/main/java/com/boyue/web/controller/system/SysMenuController.java @@ -0,0 +1,138 @@ +package com.boyue.web.controller.system; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.annotation.Log; +import com.boyue.common.constant.UserConstants; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.domain.entity.SysMenu; +import com.boyue.common.enums.BusinessType; +import com.boyue.common.utils.StringUtils; +import com.boyue.system.service.ISysMenuService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 菜单信息 + * + * @author boyue + */ +@Tag(name = "菜单信息") +@RestController +@RequestMapping("/system/menu") +public class SysMenuController extends BaseController { + + @Autowired + private ISysMenuService menuService; + + /** + * 获取菜单列表 + */ + @Operation(summary = "获取菜单列表") + @PreAuthorize("@ss.hasPermi('system:menu:list')") + @GetMapping("/list") + public AjaxResult list(SysMenu menu) { + List menus = menuService.selectMenuList(menu, getUserId()); + return success(menus); + } + + /** + * 根据菜单编号获取详细信息 + */ + @Operation(summary = "根据菜单编号获取详细信息") + @PreAuthorize("@ss.hasPermi('system:menu:query')") + @GetMapping(value = "/{menuId}") + public AjaxResult getInfo(@PathVariable(name = "menuId") Long menuId) { + return success(menuService.selectMenuById(menuId)); + } + + /** + * 获取菜单下拉树列表 + */ + @Operation(summary = "获取菜单下拉树列表") + @GetMapping("/treeselect") + public AjaxResult treeselect(SysMenu menu) { + List menus = menuService.selectMenuList(menu, getUserId()); + return success(menuService.buildMenuTreeSelect(menus)); + } + + /** + * 加载对应角色菜单列表树 + */ + @Operation(summary = "加载对应角色菜单列表树") + @GetMapping(value = "/roleMenuTreeselect/{roleId}") + public AjaxResult roleMenuTreeselect(@PathVariable("roleId") Long roleId) { + List menus = menuService.selectMenuList(getUserId()); + AjaxResult ajax = AjaxResult.success(); + ajax.put("checkedKeys", menuService.selectMenuListByRoleId(roleId)); + ajax.put("menus", menuService.buildMenuTreeSelect(menus)); + return ajax; + } + + /** + * 新增菜单 + */ + @Operation(summary = "新增菜单") + @PreAuthorize("@ss.hasPermi('system:menu:add')") + @Log(title = "菜单管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysMenu menu) { + if (!menuService.checkMenuNameUnique(menu)) { + return error("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); + } else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) { + return error("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头"); + } + menu.setCreateBy(getUsername()); + return toAjax(menuService.insertMenu(menu)); + } + + /** + * 修改菜单 + */ + @Operation(summary = "修改菜单") + @PreAuthorize("@ss.hasPermi('system:menu:edit')") + @Log(title = "菜单管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysMenu menu) { + if (!menuService.checkMenuNameUnique(menu)) { + return error("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); + } else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) { + return error("修改菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头"); + } else if (menu.getMenuId().equals(menu.getParentId())) { + return error("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己"); + } + menu.setUpdateBy(getUsername()); + return toAjax(menuService.updateMenu(menu)); + } + + /** + * 删除菜单 + */ + @Operation(summary = "删除菜单") + @PreAuthorize("@ss.hasPermi('system:menu:remove')") + @Log(title = "菜单管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{menuId}") + public AjaxResult remove(@PathVariable("menuId") Long menuId) { + if (menuService.hasChildByMenuId(menuId)) { + return warn("存在子菜单,不允许删除"); + } + if (menuService.checkMenuExistRole(menuId)) { + return warn("菜单已分配,不允许删除"); + } + return toAjax(menuService.deleteMenuById(menuId)); + } +} \ No newline at end of file diff --git a/boyue-admin/src/main/java/com/boyue/web/controller/system/SysNoticeController.java b/boyue-admin/src/main/java/com/boyue/web/controller/system/SysNoticeController.java new file mode 100644 index 0000000..1e5c0c6 --- /dev/null +++ b/boyue-admin/src/main/java/com/boyue/web/controller/system/SysNoticeController.java @@ -0,0 +1,97 @@ +package com.boyue.web.controller.system; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.enums.BusinessType; +import com.boyue.system.domain.SysNotice; +import com.boyue.system.service.ISysNoticeService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 公告 信息操作处理 + * + * @author boyue + */ +@Tag(name = "公告", description = "信息操作处理") +@RestController +@RequestMapping("/system/notice") +public class SysNoticeController extends BaseController { + + @Autowired + private ISysNoticeService noticeService; + + /** + * 获取通知公告列表 + */ + @Operation(summary = "获取通知公告列表") + @PreAuthorize("@ss.hasPermi('system:notice:list')") + @GetMapping("/list") + public TableDataInfo list(SysNotice notice) { + startPage(); + List list = noticeService.selectNoticeList(notice); + return getDataTable(list); + } + + /** + * 根据通知公告编号获取详细信息 + */ + @Operation(summary = "根据通知公告编号获取详细信息") + @PreAuthorize("@ss.hasPermi('system:notice:query')") + @GetMapping(value = "/{noticeId}") + public AjaxResult getInfo(@PathVariable(name = "noticeId") Long noticeId) { + return success(noticeService.selectNoticeById(noticeId)); + } + + /** + * 新增通知公告 + */ + @Operation(summary = "新增通知公告") + @PreAuthorize("@ss.hasPermi('system:notice:add')") + @Log(title = "通知公告", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysNotice notice) { + notice.setCreateBy(getUsername()); + return toAjax(noticeService.insertNotice(notice)); + } + + /** + * 修改通知公告 + */ + @Operation(summary = "修改通知公告") + @PreAuthorize("@ss.hasPermi('system:notice:edit')") + @Log(title = "通知公告", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysNotice notice) { + notice.setUpdateBy(getUsername()); + return toAjax(noticeService.updateNotice(notice)); + } + + /** + * 删除通知公告 + */ + @Operation(summary = "删除通知公告") + @PreAuthorize("@ss.hasPermi('system:notice:remove')") + @Log(title = "通知公告", businessType = BusinessType.DELETE) + @DeleteMapping("/{noticeIds}") + public AjaxResult remove(@PathVariable(name = "noticeIds") Long[] noticeIds) { + return toAjax(noticeService.deleteNoticeByIds(noticeIds)); + } +} diff --git a/boyue-admin/src/main/java/com/boyue/web/controller/system/SysPostController.java b/boyue-admin/src/main/java/com/boyue/web/controller/system/SysPostController.java new file mode 100644 index 0000000..8de7786 --- /dev/null +++ b/boyue-admin/src/main/java/com/boyue/web/controller/system/SysPostController.java @@ -0,0 +1,129 @@ +package com.boyue.web.controller.system; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.enums.BusinessType; +import com.boyue.common.utils.poi.ExcelUtil; +import com.boyue.system.domain.SysPost; +import com.boyue.system.service.ISysPostService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 岗位信息操作处理 + * + * @author boyue + */ +@Tag(name = "岗位信息操作处理") +@RestController +@RequestMapping("/system/post") +public class SysPostController extends BaseController { + + @Autowired + private ISysPostService postService; + + /** + * 获取岗位列表 + */ + @Operation(summary = "获取岗位列表") + @PreAuthorize("@ss.hasPermi('system:post:list')") + @GetMapping("/list") + public TableDataInfo list(SysPost post) { + startPage(); + List list = postService.selectPostList(post); + return getDataTable(list); + } + + @Operation(summary = "导出岗位列表") + @Log(title = "岗位管理", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:post:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysPost post) { + List list = postService.selectPostList(post); + ExcelUtil util = new ExcelUtil(SysPost.class); + util.exportExcel(response, list, "岗位数据"); + } + + /** + * 根据岗位编号获取详细信息 + */ + @Operation(summary = "根据岗位编号获取详细信息") + @PreAuthorize("@ss.hasPermi('system:post:query')") + @GetMapping(value = "/{postId}") + public AjaxResult getInfo(@PathVariable(name = "postId") Long postId) { + return success(postService.selectPostById(postId)); + } + + /** + * 新增岗位 + */ + @Operation(summary = "新增岗位") + @PreAuthorize("@ss.hasPermi('system:post:add')") + @Log(title = "岗位管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysPost post) { + if (!postService.checkPostNameUnique(post)) { + return error("新增岗位'" + post.getPostName() + "'失败,岗位名称已存在"); + } else if (!postService.checkPostCodeUnique(post)) { + return error("新增岗位'" + post.getPostName() + "'失败,岗位编码已存在"); + } + post.setCreateBy(getUsername()); + return toAjax(postService.insertPost(post)); + } + + /** + * 修改岗位 + */ + @Operation(summary = "修改岗位") + @PreAuthorize("@ss.hasPermi('system:post:edit')") + @Log(title = "岗位管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysPost post) { + if (!postService.checkPostNameUnique(post)) { + return error("修改岗位'" + post.getPostName() + "'失败,岗位名称已存在"); + } else if (!postService.checkPostCodeUnique(post)) { + return error("修改岗位'" + post.getPostName() + "'失败,岗位编码已存在"); + } + post.setUpdateBy(getUsername()); + return toAjax(postService.updatePost(post)); + } + + /** + * 删除岗位 + */ + @Operation(summary = "删除岗位") + @PreAuthorize("@ss.hasPermi('system:post:remove')") + @Log(title = "岗位管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{postIds}") + public AjaxResult remove(@PathVariable(name = "postIds") Long[] postIds) { + return toAjax(postService.deletePostByIds(postIds)); + } + + /** + * 获取岗位选择框列表 + */ + @Operation(summary = "获取岗位选择框列表") + @GetMapping("/optionselect") + public AjaxResult optionselect() { + List posts = postService.selectPostAll(); + return success(posts); + } +} diff --git a/boyue-admin/src/main/java/com/boyue/web/controller/system/SysProfileController.java b/boyue-admin/src/main/java/com/boyue/web/controller/system/SysProfileController.java new file mode 100644 index 0000000..e712933 --- /dev/null +++ b/boyue-admin/src/main/java/com/boyue/web/controller/system/SysProfileController.java @@ -0,0 +1,149 @@ +package com.boyue.web.controller.system; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.domain.entity.SysUser; +import com.boyue.common.core.domain.model.LoginUser; +import com.boyue.common.enums.BusinessType; +import com.boyue.common.utils.DateUtils; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.file.FileUtils; +import com.boyue.common.utils.file.MimeTypeUtils; +import com.boyue.file.utils.FileOperateUtils; +import com.boyue.framework.web.service.TokenService; +import com.boyue.system.service.ISysUserService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 个人信息 业务处理 + * + * @author boyue + */ +@Tag(name = "个人信息", description = "业务处理") +@RestController +@RequestMapping("/system/user/profile") +public class SysProfileController extends BaseController { + + @Autowired + private ISysUserService userService; + + @Autowired + private TokenService tokenService; + + /** + * 个人信息 + */ + @Operation(summary = "个人信息") + @GetMapping + public AjaxResult profile() { + LoginUser loginUser = getLoginUser(); + SysUser user = loginUser.getUser(); + if (user.getAvatar() != null) { + try { + user.setAvatar(FileOperateUtils.getURL(user.getAvatar())); + } catch (Exception e) { + } + } + AjaxResult ajax = AjaxResult.success(user); + ajax.put("roleGroup", userService.selectUserRoleGroup(loginUser.getUsername())); + ajax.put("postGroup", userService.selectUserPostGroup(loginUser.getUsername())); + return ajax; + } + + /** + * 修改用户 + */ + @Operation(summary = "修改用户") + @Log(title = "个人信息", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult updateProfile(@RequestBody SysUser user) { + LoginUser loginUser = getLoginUser(); + SysUser sysUser = loginUser.getUser(); + user.setUserName(sysUser.getUserName()); + if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) { + return error("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); + } + if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) { + return error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在"); + } + user.setUserId(sysUser.getUserId()); + user.setPassword(null); + user.setAvatar(null); + user.setDeptId(null); + if (userService.updateUserProfile(user) > 0) { + // 更新缓存用户信息 + sysUser.setNickName(user.getNickName()); + sysUser.setPhonenumber(user.getPhonenumber()); + sysUser.setEmail(user.getEmail()); + sysUser.setSex(user.getSex()); + tokenService.setLoginUser(loginUser); + return success(); + } + return error("修改个人信息异常,请联系管理员"); + } + + /** + * 重置密码 + */ + @Operation(summary = "重置密码") + @Log(title = "个人信息", businessType = BusinessType.UPDATE) + @PutMapping("/updatePwd") + public AjaxResult updatePwd(String oldPassword, String newPassword) { + LoginUser loginUser = getLoginUser(); + String userName = loginUser.getUsername(); + String password = loginUser.getPassword(); + if (!SecurityUtils.matchesPassword(oldPassword, password)) { + return error("修改密码失败,旧密码错误"); + } + if (SecurityUtils.matchesPassword(newPassword, password)) { + return error("新密码不能与旧密码相同"); + } + newPassword = SecurityUtils.encryptPassword(newPassword); + if (userService.resetUserPwd(userName, newPassword) > 0) { + // 更新缓存用户密码 + loginUser.getUser().setPassword(newPassword); + tokenService.setLoginUser(loginUser); + return success(); + } + return error("修改密码异常,请联系管理员"); + } + + /** + * 头像上传 + */ + @Operation(summary = "头像上传") + @Log(title = "用户头像", businessType = BusinessType.UPDATE) + @PostMapping("/avatar") + public AjaxResult avatar(@RequestParam("avatarfile") MultipartFile file) throws Exception { + if (!file.isEmpty()) { + LoginUser loginUser = getLoginUser(); + String extractPath = loginUser.getUsername() + "/" + loginUser.getUserId() + ""; + String fileName = DateUtils.dateTimeNow() + "-avatar." + FileUtils.getExtension(file); + String filePath = "avatar/" + extractPath + "/" + fileName; + String url = FileOperateUtils.upload(filePath, file, MimeTypeUtils.IMAGE_EXTENSION); + if (userService.updateUserAvatar(loginUser.getUsername(), filePath)) { + AjaxResult ajax = AjaxResult.success(); + ajax.put("imgUrl", url); + // 更新缓存用户头像 + loginUser.getUser().setAvatar(filePath); + tokenService.setLoginUser(loginUser); + return ajax; + } + } + return error("上传图片异常,请联系管理员"); + } +} diff --git a/boyue-admin/src/main/java/com/boyue/web/controller/system/SysRegisterController.java b/boyue-admin/src/main/java/com/boyue/web/controller/system/SysRegisterController.java new file mode 100644 index 0000000..f9ae8f4 --- /dev/null +++ b/boyue-admin/src/main/java/com/boyue/web/controller/system/SysRegisterController.java @@ -0,0 +1,42 @@ +package com.boyue.web.controller.system; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.domain.model.RegisterBody; +import com.boyue.common.utils.StringUtils; +import com.boyue.framework.web.service.SysRegisterService; +import com.boyue.system.service.ISysConfigService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 注册验证 + * + * @author boyue + */ +@Tag(name = "注册验证") +@RestController +public class SysRegisterController extends BaseController { + + @Autowired + private SysRegisterService registerService; + + @Autowired + private ISysConfigService configService; + + @Operation(summary = "注册方法") + @PostMapping("/register") + public AjaxResult register(@RequestBody RegisterBody user) { + if (!("true".equals(configService.selectConfigByKey("sys.account.registerUser")))) { + return error("当前系统没有开启注册功能!"); + } + String msg = registerService.register(user); + return StringUtils.isEmpty(msg) ? success() : error(msg); + } +} diff --git a/boyue-admin/src/main/java/com/boyue/web/controller/system/SysRoleController.java b/boyue-admin/src/main/java/com/boyue/web/controller/system/SysRoleController.java new file mode 100644 index 0000000..5ea5176 --- /dev/null +++ b/boyue-admin/src/main/java/com/boyue/web/controller/system/SysRoleController.java @@ -0,0 +1,260 @@ +package com.boyue.web.controller.system; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.domain.entity.SysDept; +import com.boyue.common.core.domain.entity.SysRole; +import com.boyue.common.core.domain.entity.SysUser; +import com.boyue.common.core.domain.model.LoginUser; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.enums.BusinessType; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.poi.ExcelUtil; +import com.boyue.framework.web.service.SysPermissionService; +import com.boyue.framework.web.service.TokenService; +import com.boyue.system.domain.SysUserRole; +import com.boyue.system.service.ISysDeptService; +import com.boyue.system.service.ISysRoleService; +import com.boyue.system.service.ISysUserService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 角色信息 + * + * @author boyue + */ +@Tag(name = "角色信息") +@RestController +@RequestMapping("/system/role") +public class SysRoleController extends BaseController { + + @Autowired + private ISysRoleService roleService; + + @Autowired + private TokenService tokenService; + + @Autowired + private SysPermissionService permissionService; + + @Autowired + private ISysUserService userService; + + @Autowired + private ISysDeptService deptService; + + @Operation(summary = "获取角色列表") + @PreAuthorize("@ss.hasPermi('system:role:list')") + @GetMapping("/list") + public TableDataInfo list(SysRole role) { + startPage(); + List list = roleService.selectRoleList(role); + return getDataTable(list); + } + + @Operation(summary = "导出角色列表") + @Log(title = "角色管理", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:role:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysRole role) { + List list = roleService.selectRoleList(role); + ExcelUtil util = new ExcelUtil(SysRole.class); + util.exportExcel(response, list, "角色数据"); + } + + /** + * 根据角色编号获取详细信息 + */ + @Operation(summary = "根据角色编号获取详细信息") + @PreAuthorize("@ss.hasPermi('system:role:query')") + @GetMapping(value = "/{roleId}") + public AjaxResult getInfo(@PathVariable(name = "roleId") Long roleId) { + roleService.checkRoleDataScope(roleId); + return success(roleService.selectRoleById(roleId)); + } + + /** + * 新增角色 + */ + @Operation(summary = "新增角色") + @PreAuthorize("@ss.hasPermi('system:role:add')") + @Log(title = "角色管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysRole role) { + if (!roleService.checkRoleNameUnique(role)) { + return error("新增角色'" + role.getRoleName() + "'失败,角色名称已存在"); + } else if (!roleService.checkRoleKeyUnique(role)) { + return error("新增角色'" + role.getRoleName() + "'失败,角色权限已存在"); + } + role.setCreateBy(getUsername()); + return toAjax(roleService.insertRole(role)); + + } + + /** + * 修改保存角色 + */ + @Operation(summary = "修改保存角色") + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysRole role) { + roleService.checkRoleAllowed(role); + roleService.checkRoleDataScope(role.getRoleId()); + if (!roleService.checkRoleNameUnique(role)) { + return error("修改角色'" + role.getRoleName() + "'失败,角色名称已存在"); + } else if (!roleService.checkRoleKeyUnique(role)) { + return error("修改角色'" + role.getRoleName() + "'失败,角色权限已存在"); + } + role.setUpdateBy(getUsername()); + + if (roleService.updateRole(role) > 0) { + // 更新缓存用户权限 + LoginUser loginUser = getLoginUser(); + if (StringUtils.isNotNull(loginUser.getUser()) && !loginUser.getUser().isAdmin()) { + loginUser.setUser(userService.selectUserByUserName(loginUser.getUser().getUserName())); + loginUser.setPermissions(permissionService.getMenuPermission(loginUser.getUser())); + tokenService.setLoginUser(loginUser); + } + return success(); + } + return error("修改角色'" + role.getRoleName() + "'失败,请联系管理员"); + } + + /** + * 修改保存数据权限 + */ + @Operation(summary = "修改保存数据权限") + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @PutMapping("/dataScope") + public AjaxResult dataScope(@RequestBody SysRole role) { + roleService.checkRoleAllowed(role); + roleService.checkRoleDataScope(role.getRoleId()); + return toAjax(roleService.authDataScope(role)); + } + + /** + * 状态修改 + */ + @Operation(summary = "状态修改") + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public AjaxResult changeStatus(@RequestBody SysRole role) { + roleService.checkRoleAllowed(role); + roleService.checkRoleDataScope(role.getRoleId()); + role.setUpdateBy(getUsername()); + return toAjax(roleService.updateRoleStatus(role)); + } + + /** + * 删除角色 + */ + @Operation(summary = "删除角色") + @PreAuthorize("@ss.hasPermi('system:role:remove')") + @Log(title = "角色管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{roleIds}") + public AjaxResult remove(@PathVariable(name = "roleIds") Long[] roleIds) { + return toAjax(roleService.deleteRoleByIds(roleIds)); + } + + /** + * 获取角色选择框列表 + */ + @Operation(summary = "获取角色选择框列表") + @PreAuthorize("@ss.hasPermi('system:role:query')") + @GetMapping("/optionselect") + public AjaxResult optionselect() { + return success(roleService.selectRoleAll()); + } + + /** + * 查询已分配用户角色列表 + */ + @Operation(summary = "查询已分配用户角色列表") + @PreAuthorize("@ss.hasPermi('system:role:list')") + @GetMapping("/authUser/allocatedList") + public TableDataInfo allocatedList(SysUser user) { + startPage(); + List list = userService.selectAllocatedList(user); + return getDataTable(list); + } + + /** + * 查询未分配用户角色列表 + */ + @Operation(summary = "查询未分配用户角色列表") + @PreAuthorize("@ss.hasPermi('system:role:list')") + @GetMapping("/authUser/unallocatedList") + public TableDataInfo unallocatedList(SysUser user) { + startPage(); + List list = userService.selectUnallocatedList(user); + return getDataTable(list); + } + + /** + * 取消授权用户 + */ + @Operation(summary = "取消授权用户") + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/cancel") + public AjaxResult cancelAuthUser(@RequestBody SysUserRole userRole) { + return toAjax(roleService.deleteAuthUser(userRole)); + } + + /** + * 批量取消授权用户 + */ + @Operation(summary = "批量取消授权用户") + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/cancelAll") + public AjaxResult cancelAuthUserAll(Long roleId, Long[] userIds) { + return toAjax(roleService.deleteAuthUsers(roleId, userIds)); + } + + /** + * 批量选择用户授权 + */ + @Operation(summary = "批量选择用户授权") + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/selectAll") + public AjaxResult selectAuthUserAll(Long roleId, Long[] userIds) { + roleService.checkRoleDataScope(roleId); + return toAjax(roleService.insertAuthUsers(roleId, userIds)); + } + + /** + * 获取对应角色部门树列表 + */ + @Operation(summary = "获取对应角色部门树列表") + @PreAuthorize("@ss.hasPermi('system:role:query')") + @GetMapping(value = "/deptTree/{roleId}") + public AjaxResult deptTree(@PathVariable("roleId") Long roleId) { + AjaxResult ajax = AjaxResult.success(); + ajax.put("checkedKeys", deptService.selectDeptListByRoleId(roleId)); + ajax.put("depts", deptService.selectDeptTreeList(new SysDept())); + return ajax; + } +} diff --git a/boyue-admin/src/main/java/com/boyue/web/controller/system/SysUserController.java b/boyue-admin/src/main/java/com/boyue/web/controller/system/SysUserController.java new file mode 100644 index 0000000..2b64476 --- /dev/null +++ b/boyue-admin/src/main/java/com/boyue/web/controller/system/SysUserController.java @@ -0,0 +1,247 @@ +package com.boyue.web.controller.system; + +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.ArrayUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.domain.entity.SysDept; +import com.boyue.common.core.domain.entity.SysRole; +import com.boyue.common.core.domain.entity.SysUser; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.enums.BusinessType; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.poi.ExcelUtil; +import com.boyue.system.service.ISysDeptService; +import com.boyue.system.service.ISysPostService; +import com.boyue.system.service.ISysRoleService; +import com.boyue.system.service.ISysUserService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 用户信息 + * + * @author boyue + */ +@Tag(name = "用户信息") +@RestController +@RequestMapping("/system/user") +public class SysUserController extends BaseController { + + @Autowired + private ISysUserService userService; + + @Autowired + private ISysRoleService roleService; + + @Autowired + private ISysDeptService deptService; + + @Autowired + private ISysPostService postService; + + /** + * 获取用户列表 + */ + @Operation(summary = "获取用户列表") + @PreAuthorize("@ss.hasPermi('system:user:list')") + @GetMapping("/list") + public TableDataInfo list(SysUser user) { + startPage(); + List list = userService.selectUserList(user); + return getDataTable(list); + } + + @Operation(summary = "导出用户列表") + @Log(title = "用户管理", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:user:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysUser user) { + List list = userService.selectUserList(user); + ExcelUtil util = new ExcelUtil(SysUser.class); + util.exportExcel(response, list, "用户数据"); + } + + @Operation(summary = "导入用户列表") + @Log(title = "用户管理", businessType = BusinessType.IMPORT) + @PreAuthorize("@ss.hasPermi('system:user:import')") + @PostMapping("/importData") + public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception { + ExcelUtil util = new ExcelUtil(SysUser.class); + List userList = util.importExcel(file.getInputStream()); + String operName = getUsername(); + String message = userService.importUser(userList, updateSupport, operName); + return success(message); + } + + @Operation(summary = "获取导入用户模板") + @PostMapping("/importTemplate") + public void importTemplate(HttpServletResponse response) { + ExcelUtil util = new ExcelUtil(SysUser.class); + util.importTemplateExcel(response, "用户数据"); + } + + /** + * 根据用户编号获取详细信息 + */ + @Operation(summary = "根据用户编号获取详细信息") + @PreAuthorize("@ss.hasPermi('system:user:query')") + @GetMapping(value = { "/", "/{userId}" }) + public AjaxResult getInfo(@PathVariable(value = "userId", required = false) Long userId) { + AjaxResult ajax = AjaxResult.success(); + if (StringUtils.isNotNull(userId)) { + userService.checkUserDataScope(userId); + SysUser sysUser = userService.selectUserById(userId); + ajax.put(AjaxResult.DATA_TAG, sysUser); + ajax.put("postIds", postService.selectPostListByUserId(userId)); + ajax.put("roleIds", sysUser.getRoles().stream().map(SysRole::getRoleId).collect(Collectors.toList())); + } + List roles = roleService.selectRoleAll(); + ajax.put("roles", SysUser.isAdmin(userId) ? roles + : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); + ajax.put("posts", postService.selectPostAll()); + return ajax; + } + + /** + * 新增用户 + */ + @Operation(summary = "新增用户") + @PreAuthorize("@ss.hasPermi('system:user:add')") + @Log(title = "用户管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysUser user) { + if (!userService.checkUserNameUnique(user)) { + return error("新增用户'" + user.getUserName() + "'失败,登录账号已存在"); + } else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) { + return error("新增用户'" + user.getUserName() + "'失败,手机号码已存在"); + } else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) { + return error("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在"); + } + user.setCreateBy(getUsername()); + user.setPassword(SecurityUtils.encryptPassword(user.getPassword())); + return toAjax(userService.insertUser(user)); + } + + /** + * 修改用户 + */ + @Operation(summary = "修改用户") + @PreAuthorize("@ss.hasPermi('system:user:edit')") + @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysUser user) { + userService.checkUserAllowed(user); + userService.checkUserDataScope(user.getUserId()); + if (!userService.checkUserNameUnique(user)) { + return error("修改用户'" + user.getUserName() + "'失败,登录账号已存在"); + } else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) { + return error("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); + } else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) { + return error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在"); + } + user.setUpdateBy(getUsername()); + return toAjax(userService.updateUser(user)); + } + + /** + * 删除用户 + */ + @Operation(summary = "删除用户") + @PreAuthorize("@ss.hasPermi('system:user:remove')") + @Log(title = "用户管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{userIds}") + public AjaxResult remove(@PathVariable(name = "userIds") Long[] userIds) { + if (ArrayUtils.contains(userIds, getUserId())) { + return error("当前用户不能删除"); + } + return toAjax(userService.deleteUserByIds(userIds)); + } + + /** + * 重置密码 + */ + @Operation(summary = "重置密码") + @PreAuthorize("@ss.hasPermi('system:user:resetPwd')") + @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @PutMapping("/resetPwd") + public AjaxResult resetPwd(@RequestBody SysUser user) { + userService.checkUserAllowed(user); + userService.checkUserDataScope(user.getUserId()); + user.setPassword(SecurityUtils.encryptPassword(user.getPassword())); + user.setUpdateBy(getUsername()); + return toAjax(userService.resetPwd(user)); + } + + /** + * 状态修改 + */ + @Operation(summary = "状态修改") + @PreAuthorize("@ss.hasPermi('system:user:edit')") + @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public AjaxResult changeStatus(@RequestBody SysUser user) { + userService.checkUserAllowed(user); + userService.checkUserDataScope(user.getUserId()); + user.setUpdateBy(getUsername()); + return toAjax(userService.updateUserStatus(user)); + } + + /** + * 根据用户编号获取授权角色 + */ + @Operation(summary = "根据用户编号获取授权角色") + @PreAuthorize("@ss.hasPermi('system:user:query')") + @GetMapping("/authRole/{userId}") + public AjaxResult authRole(@PathVariable("userId") Long userId) { + AjaxResult ajax = AjaxResult.success(); + SysUser user = userService.selectUserById(userId); + List roles = roleService.selectRolesByUserId(userId); + ajax.put("user", user); + ajax.put("roles", SysUser.isAdmin(userId) ? roles + : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); + return ajax; + } + + /** + * 用户授权角色 + */ + @Operation(summary = "用户授权角色") + @PreAuthorize("@ss.hasPermi('system:user:edit')") + @Log(title = "用户管理", businessType = BusinessType.GRANT) + @PutMapping("/authRole") + public AjaxResult insertAuthRole(Long userId, Long[] roleIds) { + userService.checkUserDataScope(userId); + userService.insertUserAuth(userId, roleIds); + return success(); + } + + /** + * 获取部门树列表 + */ + @Operation(summary = "获取部门树列表") + @PreAuthorize("@ss.hasPermi('system:user:list')") + @GetMapping("/deptTree") + public AjaxResult deptTree(SysDept dept) { + return success(deptService.selectDeptTreeList(dept)); + } +} diff --git a/boyue-admin/src/main/java/com/boyue/web/core/config/SwaggerConfig.java b/boyue-admin/src/main/java/com/boyue/web/core/config/SwaggerConfig.java new file mode 100644 index 0000000..66f8ddf --- /dev/null +++ b/boyue-admin/src/main/java/com/boyue/web/core/config/SwaggerConfig.java @@ -0,0 +1,104 @@ +package com.boyue.web.core.config; + +import org.springdoc.core.models.GroupedOpenApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.boyue.common.constant.HttpStatus; +import com.boyue.common.core.domain.AjaxResult; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.ExternalDocumentation; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.media.IntegerSchema; +import io.swagger.v3.oas.models.media.ObjectSchema; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.media.StringSchema; + +/** + * 验证码操作处理 + * + * @author Dftre + */ +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI springShopOpenApi() { + Schema codeSchema = new IntegerSchema().example(HttpStatus.SUCCESS); // 示例状态码 + Schema msgSchema = new StringSchema().example("操作成功"); // 示例消息 + ObjectSchema dataSchema = new ObjectSchema(); // 数据可以是任意类型,这里简单定义为ObjectSchema + + // 定义AjaxResult的Schema + ObjectSchema ajaxResultSchema = new ObjectSchema(); + ajaxResultSchema.addProperty(AjaxResult.CODE_TAG, codeSchema); + ajaxResultSchema.addProperty(AjaxResult.MSG_TAG, msgSchema); + ajaxResultSchema.addProperty(AjaxResult.DATA_TAG, dataSchema); + Components components = new Components(); + components.addSchemas("AjaxResult", ajaxResultSchema); + + return new OpenAPI() + .components(components) + .info(new Info().title("boyue 二开接口") + .description("boyue 二开接口 API文档") + .version("v1") + .license(new License().name("Apache 2.0").url("http://springdoc.org"))) + .externalDocs(new ExternalDocumentation() + .description("外部文档") + .url("/doc.html")); + } + + @Bean + public GroupedOpenApi sysApi() { + return GroupedOpenApi.builder() + .group("sys系统模块") + .packagesToScan("com.boyue.web.controller.system") + .build(); + } + + @Bean + public GroupedOpenApi commonApi() { + return GroupedOpenApi.builder() + .group("基础模块") + .packagesToScan("com.boyue.web.controller.common") + .build(); + } + + @Bean + public GroupedOpenApi payApi() { + return GroupedOpenApi.builder() + .group("支付模块") + .pathsToMatch("/pay/**") + .build(); + } + + @Bean + public GroupedOpenApi fileApi() { + return GroupedOpenApi.builder() + .group("文件模块") + .packagesToScan("com.boyue.file.controller") + .build(); + } + + @Bean + public GroupedOpenApi authApi() { + return GroupedOpenApi.builder() + .group("认证模块") + .packagesToScan("com.boyue.auth.controller") + .build(); + } + + /** + * 确保扫描到模块化后的司法局模块 + */ + @Bean + public GroupedOpenApi hasfjModelsApi() { + return GroupedOpenApi.builder() + .group("淮安市司法局模块(models)") + .packagesToScan("com.boyue.hasfj.controller") + .pathsToMatch("/hasfj/**") + .build(); + } +} diff --git a/boyue-admin/src/main/resources/META-INF/spring-devtools.properties b/boyue-admin/src/main/resources/META-INF/spring-devtools.properties new file mode 100644 index 0000000..37e7b58 --- /dev/null +++ b/boyue-admin/src/main/resources/META-INF/spring-devtools.properties @@ -0,0 +1 @@ +restart.include.json=/com.alibaba.fastjson2.*.jar \ No newline at end of file diff --git a/boyue-admin/src/main/resources/application-auth.yml b/boyue-admin/src/main/resources/application-auth.yml new file mode 100644 index 0000000..c9109b7 --- /dev/null +++ b/boyue-admin/src/main/resources/application-auth.yml @@ -0,0 +1,37 @@ +# 请输入自己的appid和appsecret +oauth: + wx: + miniapp: + appId: appId + appSecret: appSecret + url: https://api.weixin.qq.com/sns/jscode2session + pub: + appId: appId + appSecret: appSecret + url: https://api.weixin.qq.com/sns/oauth2/access_token + +tfa: + phone: + dysms: + # 阿里云 AccessKey ID + accessKeyId: appId + # 阿里云 AccessKey Secret + accessKeySecret: appSecret + # 短信模板 + template: + VerificationCode: + # 短信模板编码 + templateCode: SMS_123456789 + # 短信签名 + signName: 阿里云短信测试 + # 短信模板必需的数据名称,多个key以逗号分隔,此处配置作为校验 + keys: code + + +spring: + mail: + host: smtp.qq.com + # 邮箱地址 + username: email + # 授权码 + password: password diff --git a/boyue-admin/src/main/resources/application-druid.yml b/boyue-admin/src/main/resources/application-druid.yml new file mode 100644 index 0000000..4df2e13 --- /dev/null +++ b/boyue-admin/src/main/resources/application-druid.yml @@ -0,0 +1,91 @@ +# 数据源配置 +spring: + datasource: + type: com.alibaba.druid.pool.DruidDataSource + driverClassName: com.mysql.cj.jdbc.Driver + dynamic: + primary: MASTER + xa: true + datasource: +# # 主库数据源开发 +# MASTER: +# url: jdbc:mysql://127.0.0.1/boyuehasfj?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 +# username: root +# password: 123456 + # 主库数据源开发 + MASTER: + url: jdbc:mysql://222.184.49.22:3307/boyuehasfj?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + username: root + password: boyue1!Z +# MASTER: +# url: jdbc:mysql://127.0.0.1/boyuekfcode +# username: root +# password: 123456 + # 从库数据源 + # SLAVE: + # url: jdbc:mysql://127.0.0.1/boyue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + # username: root + # password: 123456 + druid: + # 初始连接数 + initialSize: 5 + # 最小连接池数量 + minIdle: 10 + # 最大连接池数量 + maxActive: 20 + # 配置获取连接等待超时的时间 + maxWait: 60000 + # 配置连接超时时间 + connectTimeout: 30000 + # 配置网络超时时间 + socketTimeout: 60000 + # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 + timeBetweenEvictionRunsMillis: 60000 + # 配置一个连接在池中最小生存的时间,单位是毫秒 + minEvictableIdleTimeMillis: 300000 + # 配置一个连接在池中最大生存的时间,单位是毫秒 + maxEvictableIdleTimeMillis: 900000 + # 配置检测连接是否有效 + validationQuery: SELECT 1 + testWhileIdle: true + testOnBorrow: false + testOnReturn: false + # 配置监控统计拦截的filters stat=>监控统计 wall=>防SQL注入 slf4j log4j2=>日志记录 + filters: stat,wall,slf4j,log4j2 + # 通过connectProperties属性来打开mergeSql功能;慢SQL记录 + connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 + + # 配置spring监控的匹配类 + aop-patterns: com.boyue.*.service.*,com.boyue.*.mapper.* + + + # Web应用 和 URL监控 配置 + web-stat-filter: + enabled: true + url-pattern: /* + exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" + session-stat-enable: false # 是否开启session统计功能 + + # 监控视图配置 + stat-view-servlet: + enabled: true + url-pattern: /druid/* + reset-enable: true # 是否可以重置日志 + login-username: boyue # 用户名 + login-password: boyue123 # 密码 + allow: "" # IP白名单 (没有配置或者为空,则允许所有访问) + deny: "" # IP黑名单 (存在共同时,deny优先于allow) + filter: + stat: + enabled: true + # 慢SQL记录 + log-slow-sql: true + slow-sql-millis: 1000 + merge-sql: true + wall: + config: + multi-statement-allow: true + +# 是否开启分布式事务,如不开启,请删除atomikos插件,否则atomikos相关驱动虽不生效但仍会启动 +atomikos: + enabled: false \ No newline at end of file diff --git a/boyue-admin/src/main/resources/application-file.yml b/boyue-admin/src/main/resources/application-file.yml new file mode 100644 index 0000000..173cccc --- /dev/null +++ b/boyue-admin/src/main/resources/application-file.yml @@ -0,0 +1,53 @@ +# local配置 +local: + enable: true + primary: MASTER + client: + MASTER: + permission: public + path: /data/files/master + # 建议public权限的api以 /profile开头就不需要再从SecurityConfig配置权限了 + api: /profile/files/master + # SLAVE: + # permission: private + # path: /data/files/slave + # private需要自己编写一个api例如FileController中的/file/resource + # api: /file/resource + +# Minio配置 +minio: + enable: false + primary: MASTER + client: + MASTER: + permission: public + url: http://localhost:9000 + accessKey: + secretKey: + bucketName: boyue + # SLAVE: + # permission: private + # url: http://localhost:9000 + # accessKey: minioadmin + # secretKey: minioadmin + # bucketName: ry + +# oss配置 +oss: + enable: false + primary: MASTER + client: + MASTER: + permission: public + accessKeyId: accessKeyId + accessKeySecret: accessKeySecret + bucketName: boyue + endpoint: oss-cn-beijing.aliyuncs.com + # SLAVE: + # permission: private + # accessKeyId: + # accessKeySecret: + # bucketName: + # endpoint: + + diff --git a/boyue-admin/src/main/resources/application-lp.yml b/boyue-admin/src/main/resources/application-lp.yml new file mode 100644 index 0000000..66ea810 --- /dev/null +++ b/boyue-admin/src/main/resources/application-lp.yml @@ -0,0 +1,7 @@ +netty: + websocket: + maxMessageSize: 65536 + bossThreads: 4 + workerThreads: 16 + port: 8081 + enable: true \ No newline at end of file diff --git a/boyue-admin/src/main/resources/application-middleware.yml b/boyue-admin/src/main/resources/application-middleware.yml new file mode 100644 index 0000000..f347d74 --- /dev/null +++ b/boyue-admin/src/main/resources/application-middleware.yml @@ -0,0 +1,39 @@ +spring: + data: + # redis 配置 + redis: + # 地址 开发环境 +# host: localhost + # 地址 生产环境 + host: 222.184.49.22 + + # 端口,默认为6379 + port: 6379 + # 数据库索引 + database: 0 + # 密码 开发环境 +# password: 123456 + # 密码 生产环境 + password: boyue123 + # 连接超时时间 + timeout: 10s + lettuce: + pool: + # 连接池中的最小空闲连接 + min-idle: 0 + # 连接池中的最大空闲连接 + max-idle: 8 + # 连接池的最大数据库连接数 + max-active: 8 + # #连接池最大阻塞等待时间(使用负值表示没有限制) + max-wait: -1ms + + #配置rabbitMq 服务器 + rabbitmq: + enable: false + host: 127.0.0.1 + port: 5672 + username: guest + password: guest + + diff --git a/boyue-admin/src/main/resources/application-mybatis.yml b/boyue-admin/src/main/resources/application-mybatis.yml new file mode 100644 index 0000000..111eced --- /dev/null +++ b/boyue-admin/src/main/resources/application-mybatis.yml @@ -0,0 +1,27 @@ +# PageHelper分页插件 +pagehelper: + helperDialect: mysql + supportMethodsArguments: true + params: count=countSql + +createSqlSessionFactory: + # 选择MyBatis配置方式,mybatis / mybatis-plus + use: mybatis-plus + +# MyBatis配置 +mybatis: + # 搜索指定包别名 + typeAliasesPackage: com.boyue.**.domain + # 配置mapper的扫描,找到所有的mapper.xml映射文件 + mapperLocations: classpath*:mapper/**/*Mapper.xml + # 加载全局的配置文件 + configLocation: classpath:mybatis/mybatis-config.xml + +# MyBatis Plus配置 +mybatis-plus: + # 搜索指定包别名 + typeAliasesPackage: com.boyue.**.domain + # 配置mapper的扫描,找到所有的mapper.xml映射文件 + mapperLocations: classpath*:mapper/**/*Mapper.xml + # 加载全局的配置文件 + configLocation: classpath:mybatis/mybatis-config.xml diff --git a/boyue-admin/src/main/resources/application-pay.yml b/boyue-admin/src/main/resources/application-pay.yml new file mode 100644 index 0000000..58a297e --- /dev/null +++ b/boyue-admin/src/main/resources/application-pay.yml @@ -0,0 +1,30 @@ +# 当前支付模块写的并不完善,请根据自己的业务需求进行修改 +# 回调地址使用的内网穿透http://e2vca6.natappfree.cc +pay: + # https://doc.shouqianba.com/zh-cn/ + sqb: + enabled: false + appId: "appId" + apiDomain: "apiDomain" + terminalSn: "terminalSn" + terminalKey: "terminalKey" + vendorSn: "vendorSn" + vendorKey: "vendorKey" + publicKey: classpath:pay/sqb/sqb_public_key.pem + notifyUrl: http://e2vca6.natappfree.cc/pay/sqb/notify + # https://opendocs.alipay.com/open/02np95 + alipay: + enabled: false + appId: appid + appPrivateKey: classpath:pay/alipay/alipay_private_key.pem + alipayPublicKey: classpath:pay/alipay/alipay_public_key.pem + notifyUrl: http://e2vca6.natappfree.cc/alipay/notify + # https://github.com/wechatpay-apiv3/wechatpay-java + wechat: + enabled: false + appId: appid + apiV3Key: apiV3Key + privateKeyPath: classpath:pay/wx/apiclient_key.pem + merchantId: merchantId + merchantSerialNumber: merchantSerialNumber + notifyUrl: http://e2vca6.natappfree.cc/pay/wechat/notify diff --git a/boyue-admin/src/main/resources/application.yml b/boyue-admin/src/main/resources/application.yml new file mode 100644 index 0000000..6a64fec --- /dev/null +++ b/boyue-admin/src/main/resources/application.yml @@ -0,0 +1,121 @@ +# 项目相关配置 +boyue: + # 名称 + name: boyue + # 版本 + version: 3.8.5 + # 版权年份 + copyrightYear: 2025 + # 文件路径 示例( Windows配置D:/boyue/uploadPath,Linux配置 /home/boyue/uploadPath) +# profile: D:/boyue/hasfj/uploadPath + profile: /home/boyue/hasfj/uploadPath + # 获取ip地址开关 + addressEnabled: false + # 验证码类型 math 数组计算 char 字符验证 + captchaType: math + # 指定默认文件服务类型(值为local代表使用本地作为文件操作服务,minio代表使用minio作为文件操作服务,oss代表使用oss作为文件操作服务) + fileServer: local + # 指定默认文件上传方法最大文件大小(MB) + fileMaxSize: 500 + +# 开发环境配置 +server: + # 服务器的HTTP端口,默认为8080 + port: 9799 + servlet: + # 应用的访问路径 + context-path: / + tomcat: + # tomcat的URI编码 + uri-encoding: UTF-8 + # 连接数满后的排队数,默认为100 + accept-count: 1000 + threads: + # tomcat最大线程数,默认为200 + max: 800 + # Tomcat启动初始化的线程数,默认值10 + min-spare: 100 + +# 日志配置 +logging: + level: + "[com.boyue]": DEBUG + "[org]": WARN + "[org.springframework]": WARN + "[org.apache]": WARN + "[org.springframework.context.support.PostProcessorRegistrationDelegate]": ERROR + "[com.alibaba.druid.spring.boot3.autoconfigure.stat.DruidSpringAopConfiguration]": ERROR + "[com.alibaba.druid.spring.boot3.autoconfigure.properties.DruidStatProperties]": ERROR + +# 用户配置 +user: + password: + # 密码最大错误次数 + maxRetryCount: 5 + # 密码锁定时间(默认10分钟) + lockTime: 10 + ip: + maxRetryCount: 15 + lockTime: 15 + +# Spring配置 +spring: + #给项目来个名字 + application: + name: boyue + cache: + # 指定缓存类型 jcache 本地缓存 redis 缓存 + type: redis + redis: + # 指定存活时间(ms) + time-to-live: 86400000 + # 指定前缀 + use-key-prefix: true + # 是否缓存空值,可以防止缓存穿透 + cache-null-values: true + mvc: + pathmatch: + matching-strategy: ANT_PATH_MATCHER + # 资源信息 + messages: + # 国际化资源文件路径 + basename: i18n/messages + profiles: + active: druid,mybatis,auth,pay,middleware,lp,file + # 文件上传 + servlet: + multipart: + # 单个文件大小 + max-file-size: 100MB + # 设置总上传的文件大小 + max-request-size: 200MB + # 服务模块 + devtools: + restart: + # 热部署开关 + enabled: true + +# token配置 +token: + # 令牌自定义标识 + header: Authorization + # 令牌密钥 + secret: abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz + # 令牌有效期(默认30分钟) + expireTime: 30 + +# Swagger配置 +swagger: + # 是否开启swagger + enabled: true + # 请求前缀 + pathMapping: /dev-api + +# 防止XSS攻击 +xss: + # 过滤开关 + enabled: true + # 排除链接(多个用逗号分隔) + excludes: /system/notice + # 匹配链接 + urlPatterns: /system/*,/monitor/*,/tool/* \ No newline at end of file diff --git a/boyue-admin/src/main/resources/banner.txt b/boyue-admin/src/main/resources/banner.txt new file mode 100644 index 0000000..a961e09 --- /dev/null +++ b/boyue-admin/src/main/resources/banner.txt @@ -0,0 +1,24 @@ +Application Version: ${boyue.version} +Spring Boot Version: ${spring-boot.version} +//////////////////////////////////////////////////////////////////// +// _ooOoo_ // +// o8888888o // +// 88" . "88 // +// (| ^_^ |) // +// O\ = /O // +// ____/`---'\____ // +// .' \\| |// `. // +// / \\||| : |||// \ // +// / _||||| -:- |||||- \ // +// | | \\\ - /// | | // +// | \_| ''\---/'' | | // +// \ .-\__ `-` ___/-. / // +// ___`. .' /--.--\ `. . ___ // +// ."" '< `.___\_<|>_/___.' >'"". // +// | | : `- \`.;`\ _ /`;.`/ - ` : | | // +// \ \ `-. \_ __\ /__ _/ .-` / / // +// ========`-.____`-.___\_____/___.-`____.-'======== // +// `=---=' // +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ // +// 佛祖保佑 永不宕机 永无BUG // +//////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/boyue-admin/src/main/resources/i18n/messages.properties b/boyue-admin/src/main/resources/i18n/messages.properties new file mode 100644 index 0000000..93de005 --- /dev/null +++ b/boyue-admin/src/main/resources/i18n/messages.properties @@ -0,0 +1,38 @@ +#错误消息 +not.null=* 必须填写 +user.jcaptcha.error=验证码错误 +user.jcaptcha.expire=验证码已失效 +user.not.exists=用户不存在/密码错误 +user.password.not.match=用户不存在/密码错误 +user.password.retry.limit.count=密码输入错误{0}次 +user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟 +user.password.delete=对不起,您的账号已被删除 +user.blocked=用户已封禁,请联系管理员 +role.blocked=角色已封禁,请联系管理员 +login.blocked=很遗憾,访问IP已被列入系统黑名单 +user.logout.success=退出成功 + +length.not.valid=长度必须在{min}到{max}个字符之间 + +user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头 +user.password.not.valid=* 5-50个字符 + +user.email.not.valid=邮箱格式错误 +user.mobile.phone.number.not.valid=手机号格式错误 +user.login.success=登录成功 +user.register.success=注册成功 +user.notfound=请重新登录 +user.forcelogout=管理员强制退出,请重新登录 +user.unknown.error=未知错误,请重新登录 + +##文件上传消息 +upload.exceed.maxSize=上传的文件大小超出限制的文件大小!
允许的文件最大大小是:{0}MB! +upload.filename.exceed.length=上传的文件名最长{0}个字符 + +##权限 +no.permission=您没有数据的权限,请联系管理员添加权限 [{0}] +no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}] +no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}] +no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}] +no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}] +no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}] diff --git a/boyue-admin/src/main/resources/logback.xml b/boyue-admin/src/main/resources/logback.xml new file mode 100644 index 0000000..33526c1 --- /dev/null +++ b/boyue-admin/src/main/resources/logback.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + ${log.pattern} + + + + + + ${log.path}/sys-info.log + + + + ${log.path}/sys-info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/sys-error.log + + + + ${log.path}/sys-error.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + ${log.path}/sys-user.log + + + ${log.path}/sys-user.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/boyue-admin/src/main/resources/mybatis/mybatis-config.xml b/boyue-admin/src/main/resources/mybatis/mybatis-config.xml new file mode 100644 index 0000000..d19ff94 --- /dev/null +++ b/boyue-admin/src/main/resources/mybatis/mybatis-config.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/boyue-admin/src/main/resources/templates/hasfj/case.html b/boyue-admin/src/main/resources/templates/hasfj/case.html new file mode 100644 index 0000000..667061a --- /dev/null +++ b/boyue-admin/src/main/resources/templates/hasfj/case.html @@ -0,0 +1,71 @@ + + + + + + 典型案例 + + + + + + + +
+
+
+

典型案例标题

+
+
+ 发布时间:2025-05-28 + | + 作者:管理员 + | + 浏览次数:0 +
+
+ 案例摘要:这是一个典型案例的摘要描述... +
+
+ +

典型案例内容将在这里显示...

+
+
+
+ + + + + \ No newline at end of file diff --git a/boyue-admin/src/main/resources/templates/hasfj/case_list.html b/boyue-admin/src/main/resources/templates/hasfj/case_list.html new file mode 100644 index 0000000..4e91f91 --- /dev/null +++ b/boyue-admin/src/main/resources/templates/hasfj/case_list.html @@ -0,0 +1,80 @@ + + + + + + 典型案例列表 - 淮安市司法局 + + + + + + + +
+
+ + +
+ +
+ + +
+
+ + + + + \ No newline at end of file diff --git a/boyue-admin/src/main/resources/templates/hasfj/form.html b/boyue-admin/src/main/resources/templates/hasfj/form.html new file mode 100644 index 0000000..7c087cd --- /dev/null +++ b/boyue-admin/src/main/resources/templates/hasfj/form.html @@ -0,0 +1,74 @@ + + + + + + 表单下载 + + + + + + + +
+
+
+

表单下载标题

+
+
+ 发布时间:2025-05-28 + | + 作者:管理员 + | + 浏览次数:0 +
+
+ +

表单说明内容将在这里显示...

+
+ +
+
+ + + + + \ No newline at end of file diff --git a/boyue-admin/src/main/resources/templates/hasfj/form_list.html b/boyue-admin/src/main/resources/templates/hasfj/form_list.html new file mode 100644 index 0000000..e90f73e --- /dev/null +++ b/boyue-admin/src/main/resources/templates/hasfj/form_list.html @@ -0,0 +1,86 @@ + + + + + + 表单下载列表 - 淮安市司法局 + + + + + + + +
+
+ + +
+ +
+ + +
+
+ + + + + \ No newline at end of file diff --git a/boyue-admin/src/main/resources/templates/hasfj/index.html b/boyue-admin/src/main/resources/templates/hasfj/index.html new file mode 100644 index 0000000..188e2f5 --- /dev/null +++ b/boyue-admin/src/main/resources/templates/hasfj/index.html @@ -0,0 +1,225 @@ + + + + + + 淮安市司法局 + + + + + + + +
+
+ + + +
+ +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+
+
+
+ +
+
+
+
+
法律规定
+

查看最新的法律法规和相关规定

+ 查看详情 +
+
+
+
+
+
+
典型案例
+

浏览各类典型法律案例分析

+ 查看详情 +
+
+
+
+
+
+
表单下载
+

下载各类法律相关表单

+ 查看详情 +
+
+
+
+ + + + + + \ No newline at end of file diff --git a/boyue-admin/src/main/resources/templates/hasfj/law.html b/boyue-admin/src/main/resources/templates/hasfj/law.html new file mode 100644 index 0000000..6ac8650 --- /dev/null +++ b/boyue-admin/src/main/resources/templates/hasfj/law.html @@ -0,0 +1,61 @@ + + + + + + 法律规定 + + + + + + + +
+
+
+

法律规定标题

+
+
+ 发布时间:2025-05-28 + | + 作者:管理员 + | + 浏览次数:0 +
+
+ +

法律规定内容将在这里显示...

+
+
+
+ + + + + \ No newline at end of file diff --git a/boyue-admin/src/main/resources/templates/hasfj/law_list.html b/boyue-admin/src/main/resources/templates/hasfj/law_list.html new file mode 100644 index 0000000..27f6706 --- /dev/null +++ b/boyue-admin/src/main/resources/templates/hasfj/law_list.html @@ -0,0 +1,80 @@ + + + + + + 法律规定列表 - 淮安市司法局 + + + + + + + +
+
+ + +
+ +
+ + +
+
+ + + + + \ No newline at end of file diff --git a/boyue-admin/src/main/resources/vite.config.ts b/boyue-admin/src/main/resources/vite.config.ts new file mode 100644 index 0000000..4a7ef67 --- /dev/null +++ b/boyue-admin/src/main/resources/vite.config.ts @@ -0,0 +1,32 @@ +import { fileURLToPath, URL } from 'node:url' + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueJsx from '@vitejs/plugin-vue-jsx' + +// https://vitejs.dev/config/ +export default defineConfig({ + server: { + port: 80, + proxy: { + '/dev-api': { + target: 'http://localhost:8080', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/dev-api/, '') + }, + '/profile': { + target: 'http://localhost:8080', + changeOrigin: true + } + } + }, + plugins: [ + vue(), + vueJsx(), + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + } + } +}) \ No newline at end of file diff --git a/boyue-auth/boyue-auth-common/pom.xml b/boyue-auth/boyue-auth-common/pom.xml new file mode 100644 index 0000000..b4220a6 --- /dev/null +++ b/boyue-auth/boyue-auth-common/pom.xml @@ -0,0 +1,33 @@ + + + + boyue-auth + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-auth-common + + + system系统模块 + + + + + + + com.boyue + boyue-framework + + + + + org.apache.httpcomponents + httpclient + + + + + \ No newline at end of file diff --git a/boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/domain/OauthUser.java b/boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/domain/OauthUser.java new file mode 100644 index 0000000..9a53fa7 --- /dev/null +++ b/boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/domain/OauthUser.java @@ -0,0 +1,293 @@ +package com.boyue.auth.common.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.boyue.common.annotation.Excel; +import com.boyue.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 第三方认证对象 oauth_user + * + * @author Dftre + * @date 2024-01-18 + */ +@Schema(description = "第三方认证对象") +public class OauthUser extends BaseEntity { + private static final long serialVersionUID = 1L; + + /** 主键 */ + @Schema(title = "主键") + private Long id; + + /** 第三方系统的唯一ID,详细解释请参考:名词解释 */ + @Schema(title = "第三方系统的唯一ID,详细解释请参考:名词解释") + @Excel(name = "第三方系统的唯一ID,详细解释请参考:名词解释") + private String uuid; + + /** 用户ID */ + @Schema(title = "用户ID") + @Excel(name = "用户ID") + private Long userId; + + /** + * 第三方用户来源,可选值:GITHUB、GITEE、QQ,更多请参考:AuthDefaultSource.java(opens new window) + */ + @Schema(title = "第三方用户来源,可选值:GITHUB、GITEE、QQ,更多请参考:AuthDefaultSource.java(opens new window)") + @Excel(name = "第三方用户来源,可选值:GITHUB、GITEE、QQ,更多请参考:AuthDefaultSource.java(opens new window)") + private String source; + + /** 用户的授权令牌 */ + @Schema(title = "用户的授权令牌") + @Excel(name = "用户的授权令牌") + private String accessToken; + + /** 第三方用户的授权令牌的有效期,部分平台可能没有 */ + @Schema(title = "第三方用户的授权令牌的有效期,部分平台可能没有") + @Excel(name = "第三方用户的授权令牌的有效期,部分平台可能没有") + private Long expireIn; + + /** 刷新令牌,部分平台可能没有 */ + @Schema(title = "刷新令牌,部分平台可能没有") + @Excel(name = "刷新令牌,部分平台可能没有") + private String refreshToken; + + /** 第三方用户的 open id,部分平台可能没有 */ + @Schema(title = "第三方用户的 open id,部分平台可能没有") + @Excel(name = "第三方用户的 open id,部分平台可能没有") + private String openId; + + /** 第三方用户的 ID,部分平台可能没有 */ + @Schema(title = "第三方用户的 ID,部分平台可能没有") + @Excel(name = "第三方用户的 ID,部分平台可能没有") + private String uid; + + /** 个别平台的授权信息,部分平台可能没有 */ + @Schema(title = "个别平台的授权信息,部分平台可能没有") + @Excel(name = "个别平台的授权信息,部分平台可能没有") + private String accessCode; + + /** 第三方用户的 union id,部分平台可能没有 */ + @Schema(title = "第三方用户的 union id,部分平台可能没有") + @Excel(name = "第三方用户的 union id,部分平台可能没有") + private String unionId; + + /** 第三方用户授予的权限,部分平台可能没有 */ + @Schema(title = "第三方用户授予的权限,部分平台可能没有") + @Excel(name = "第三方用户授予的权限,部分平台可能没有") + private String scope; + + /** 个别平台的授权信息,部分平台可能没有 */ + @Schema(title = "个别平台的授权信息,部分平台可能没有") + @Excel(name = "个别平台的授权信息,部分平台可能没有") + private String tokenType; + + /** id token,部分平台可能没有 */ + @Schema(title = "id token,部分平台可能没有") + @Excel(name = "id token,部分平台可能没有") + private String idToken; + + /** 小米平台用户的附带属性,部分平台可能没有 */ + @Schema(title = "小米平台用户的附带属性,部分平台可能没有") + @Excel(name = "小米平台用户的附带属性,部分平台可能没有") + private String macAlgorithm; + + /** 小米平台用户的附带属性,部分平台可能没有 */ + @Schema(title = "小米平台用户的附带属性,部分平台可能没有") + @Excel(name = "小米平台用户的附带属性,部分平台可能没有") + private String macKey; + + /** 用户的授权code,部分平台可能没有 */ + @Schema(title = "用户的授权code,部分平台可能没有") + @Excel(name = "用户的授权code,部分平台可能没有") + private String code; + + /** Twitter平台用户的附带属性,部分平台可能没有 */ + @Schema(title = "Twitter平台用户的附带属性,部分平台可能没有") + @Excel(name = "Twitter平台用户的附带属性,部分平台可能没有") + private String oauthToken; + + /** Twitter平台用户的附带属性,部分平台可能没有 */ + @Schema(title = "Twitter平台用户的附带属性,部分平台可能没有") + @Excel(name = "Twitter平台用户的附带属性,部分平台可能没有") + private String oauthTokenSecret; + + public void setId(Long id) { + this.id = id; + } + + public Long getId() { + return id; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getUuid() { + return uuid; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public Long getUserId() { + return userId; + } + + public void setSource(String source) { + this.source = source; + } + + public String getSource() { + return source; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public String getAccessToken() { + return accessToken; + } + + public void setExpireIn(Long expireIn) { + this.expireIn = expireIn; + } + + public Long getExpireIn() { + return expireIn; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public String getRefreshToken() { + return refreshToken; + } + + public void setOpenId(String openId) { + this.openId = openId; + } + + public String getOpenId() { + return openId; + } + + public void setUid(String uid) { + this.uid = uid; + } + + public String getUid() { + return uid; + } + + public void setAccessCode(String accessCode) { + this.accessCode = accessCode; + } + + public String getAccessCode() { + return accessCode; + } + + public void setUnionId(String unionId) { + this.unionId = unionId; + } + + public String getUnionId() { + return unionId; + } + + public void setScope(String scope) { + this.scope = scope; + } + + public String getScope() { + return scope; + } + + public void setTokenType(String tokenType) { + this.tokenType = tokenType; + } + + public String getTokenType() { + return tokenType; + } + + public void setIdToken(String idToken) { + this.idToken = idToken; + } + + public String getIdToken() { + return idToken; + } + + public void setMacAlgorithm(String macAlgorithm) { + this.macAlgorithm = macAlgorithm; + } + + public String getMacAlgorithm() { + return macAlgorithm; + } + + public void setMacKey(String macKey) { + this.macKey = macKey; + } + + public String getMacKey() { + return macKey; + } + + public void setCode(String code) { + this.code = code; + } + + public String getCode() { + return code; + } + + public void setOauthToken(String oauthToken) { + this.oauthToken = oauthToken; + } + + public String getOauthToken() { + return oauthToken; + } + + public void setOauthTokenSecret(String oauthTokenSecret) { + this.oauthTokenSecret = oauthTokenSecret; + } + + public String getOauthTokenSecret() { + return oauthTokenSecret; + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("uuid", getUuid()) + .append("userId", getUserId()) + .append("source", getSource()) + .append("accessToken", getAccessToken()) + .append("expireIn", getExpireIn()) + .append("refreshToken", getRefreshToken()) + .append("openId", getOpenId()) + .append("uid", getUid()) + .append("accessCode", getAccessCode()) + .append("unionId", getUnionId()) + .append("scope", getScope()) + .append("tokenType", getTokenType()) + .append("idToken", getIdToken()) + .append("macAlgorithm", getMacAlgorithm()) + .append("macKey", getMacKey()) + .append("code", getCode()) + .append("oauthToken", getOauthToken()) + .append("oauthTokenSecret", getOauthTokenSecret()) + .toString(); + } +} diff --git a/boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/enums/OauthVerificationUse.java b/boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/enums/OauthVerificationUse.java new file mode 100644 index 0000000..5b5131f --- /dev/null +++ b/boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/enums/OauthVerificationUse.java @@ -0,0 +1,59 @@ +package com.boyue.auth.common.enums; + +public enum OauthVerificationUse { + + /** 用于登录 */ + LOGIN("登录", "login"), + /** 用于注册 */ + REGISTER("注册", "register"), + /** 用于禁用 */ + DISABLE("禁用", "disable"), + /** 用于重置信息 */ + RESET("重置", "reset"), + /** 用于绑定信息 */ + BIND("绑定", "bind"), + /** 其他用途 */ + OTHER("其他", "other"); + + private String name; + private String value; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public static OauthVerificationUse getByValue(String value) { + for (OauthVerificationUse use : OauthVerificationUse.values()) { + if (use.getValue().equals(value)) { + return use; + } + } + return null; + } + + public static OauthVerificationUse getByName(String name) { + for (OauthVerificationUse use : OauthVerificationUse.values()) { + if (use.getName().equals(name)) { + return use; + } + } + return null; + } + + private OauthVerificationUse(String name, String value) { + this.name = name; + this.value = value; + } +} diff --git a/boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/mapper/OauthUserMapper.java b/boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/mapper/OauthUserMapper.java new file mode 100644 index 0000000..12aafe3 --- /dev/null +++ b/boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/mapper/OauthUserMapper.java @@ -0,0 +1,112 @@ +package com.boyue.auth.common.mapper; + +import java.util.List; + +import org.apache.ibatis.annotations.Param; + +import com.boyue.auth.common.domain.OauthUser; + +/** + * 第三方认证Mapper接口 + * + * @author Dftre + * @date 2024-01-18 + */ +public interface OauthUserMapper { + /** + * 查询第三方认证 + * + * @param id 第三方认证主键 + * @return 第三方认证 + */ + public OauthUser selectOauthUserById(Long id); + + public OauthUser selectOauthUserByUserId(Long userId); + + /** + * 查询第三方认证 + * 钉钉、抖音:uuid 为用户的 unionid + * 微信公众平台登录、京东、酷家乐、美团:uuid 为用户的 openId + * 微信开放平台登录、QQ:uuid 为用户的 openId,平台支持获取unionid, unionid 在 AuthToken + * 中(如果支持),在登录完成后,可以通过 response.getData().getToken().getUnionId() 获取 + * Google:uuid 为用户的 sub,sub为Google的所有账户体系中用户唯一的身份标识符,详见:OpenID Connect + * + * @param uuid + * @return + */ + public OauthUser selectOauthUserByUUID(String uuid); + + /** + * 查询第三方认证列表 + * + * @param oauthUser 第三方认证 + * @return 第三方认证集合 + */ + public List selectOauthUserList(OauthUser oauthUser); + + /** + * 新增第三方认证 + * + * @param oauthUser 第三方认证 + * @return 结果 + */ + public int insertOauthUser(OauthUser oauthUser); + + /** + * 修改第三方认证 + * + * @param oauthUser 第三方认证 + * @return 结果 + */ + public int updateOauthUser(OauthUser oauthUser); + + /** + * 删除第三方认证 + * + * @param id 第三方认证主键 + * @return 结果 + */ + public int deleteOauthUserById(Long id); + + /** + * 批量删除第三方认证 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteOauthUserByIds(Long[] ids); + + /** + * 校验source平台是否绑定 + * + * @param userId 用户编号 + * @param source 绑定平台 + * @return 结果 + */ + public int checkAuthUser(@Param("userId") Long userId, @Param("source") String source); + + /** + * 校验用户名称是否唯一 + * + * @param userName 用户名称 + * @return 结果 + */ + public int checkUserNameUnique(String userName); + + /** + * 校验手机号码是否唯一 + * + * @param phonenumber 手机号码 + * @return 结果 + */ + public int checkPhoneUnique(String phonenumber); + + /** + * 校验email是否唯一 + * + * @param email 用户邮箱 + * @return 结果 + */ + public int checkEmailUnique(String email); + +} diff --git a/boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/service/IOauthUserService.java b/boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/service/IOauthUserService.java new file mode 100644 index 0000000..8c86e9b --- /dev/null +++ b/boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/service/IOauthUserService.java @@ -0,0 +1,102 @@ +package com.boyue.auth.common.service; + +import java.util.List; + +import com.boyue.auth.common.domain.OauthUser; +import com.boyue.common.core.domain.entity.SysUser; + +/** + * 第三方认证Service接口 + * + * @author Dftre + * @date 2024-01-18 + */ +public interface IOauthUserService { + /** + * 查询第三方认证 + * + * @param id 第三方认证主键 + * @return 第三方认证 + */ + public OauthUser selectOauthUserById(Long id); + + public OauthUser selectOauthUserByUUID(String uuid); + + public OauthUser selectOauthUserByUserId(Long userId); + + public SysUser selectSysUserByUUID(String uuid); + + /** + * 查询第三方认证列表 + * + * @param oauthUser 第三方认证 + * @return 第三方认证集合 + */ + public List selectOauthUserList(OauthUser oauthUser); + + /** + * 新增第三方认证 + * + * @param oauthUser 第三方认证 + * @return 结果 + */ + public int insertOauthUser(OauthUser oauthUser); + + /** + * 修改第三方认证 + * + * @param oauthUser 第三方认证 + * @return 结果 + */ + public int updateOauthUser(OauthUser oauthUser); + + /** + * 批量删除第三方认证 + * + * @param ids 需要删除的第三方认证主键集合 + * @return 结果 + */ + public int deleteOauthUserByIds(Long[] ids); + + /** + * 删除第三方认证信息 + * + * @param id 第三方认证主键 + * @return 结果 + */ + public int deleteOauthUserById(Long id); + + /** + * 校验source平台是否绑定 + * + * @param userId 用户编号 + * @param source 绑定平台 + * @return 结果 + */ + public boolean checkAuthUser(Long userId, String source); + + /** + * 校验用户名称是否唯一 + * + * @param userName 用户名称 + * @return 结果 + */ + public boolean checkUserNameUnique(String userName); + + /** + * 校验手机号码是否唯一 + * + * @param phonenumber 手机号码 + * @return 结果 + */ + public boolean checkPhoneUnique(String phonenumber); + + /** + * 校验email是否唯一 + * + * @param email 用户邮箱 + * @return 结果 + */ + public boolean checkEmailUnique(String email); + +} diff --git a/boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/service/OauthVerificationCodeService.java b/boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/service/OauthVerificationCodeService.java new file mode 100644 index 0000000..880a3c5 --- /dev/null +++ b/boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/service/OauthVerificationCodeService.java @@ -0,0 +1,15 @@ +package com.boyue.auth.common.service; + +import com.boyue.auth.common.enums.OauthVerificationUse; + +/** + * code认证方式接口 + * + * @author zlh + * @date 2024-04-16 + */ +public interface OauthVerificationCodeService { + public boolean sendCode(String o, String code,OauthVerificationUse use) throws Exception; + public boolean checkCode(String o, String code,OauthVerificationUse use) throws Exception; + +} diff --git a/boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/service/TfaService.java b/boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/service/TfaService.java new file mode 100644 index 0000000..d66755b --- /dev/null +++ b/boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/service/TfaService.java @@ -0,0 +1,73 @@ +package com.boyue.auth.common.service; + +import com.boyue.common.core.domain.model.LoginBody; +import com.boyue.common.core.domain.model.RegisterBody; + +/** + * 双因素认证(TFA)操作的服务接口。 + * 该接口提供处理TFA绑定、注册和登录流程的方法, + * 包括它们的验证步骤。 + * + *

+ * 双因素认证通过要求用户提供两种不同的认证因素, + * 为认证过程增加了额外的安全层。 + *

+ */ +public interface TfaService { + /** + * 启动将TFA方法绑定到用户账户的流程。 + * + * @param loginBody 包含TFA绑定所需数据的登录信息 + */ + public void doBind(LoginBody loginBody); + + /** + * 使用验证码或其他确认方式验证TFA绑定流程。 + * + * @param loginBody 包含验证数据的登录信息 + */ + public void doBindVerify(LoginBody loginBody); + + /** + * 处理包含TFA设置的注册流程。 + * + * @param registerBody 包含用户详情和TFA设置的注册信息 + */ + public void doRegister(RegisterBody registerBody); + + /** + * 验证包含TFA设置的注册流程。 + * + * @param registerBody 包含验证数据的注册信息 + */ + public void doRegisterVerify(RegisterBody registerBody); + + /** + * 启动TFA登录流程的第一步。 + * + * @param loginBody 包含用户凭证的登录信息 + */ + public void doLogin(LoginBody loginBody); + + /** + * 验证TFA登录流程的第二步并完成认证。 + * + * @param loginBody 包含TFA验证码的登录信息 + * @return 已认证会话的字符串令牌或会话标识符 + */ + public String doLoginVerify(LoginBody loginBody); + + /** + * 启动TFA重置流程的第一步。 + * + * @param registerBody 包含用户凭证的注册信息 + */ + public void doReset(RegisterBody registerBody); + + /** + * 验证TFA重置流程的第二步并完成重置。 + * + * @param registerBody 包含TFA验证码的注册信息 + */ + public void doResetVerify(RegisterBody registerBody); +} diff --git a/boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/service/impl/OauthUserServiceImpl.java b/boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/service/impl/OauthUserServiceImpl.java new file mode 100644 index 0000000..f2d5e59 --- /dev/null +++ b/boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/service/impl/OauthUserServiceImpl.java @@ -0,0 +1,149 @@ +package com.boyue.auth.common.service.impl; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.boyue.auth.common.domain.OauthUser; +import com.boyue.auth.common.mapper.OauthUserMapper; +import com.boyue.auth.common.service.IOauthUserService; +import com.boyue.common.core.domain.entity.SysUser; +import com.boyue.system.mapper.SysUserMapper; + +/** + * 第三方认证Service业务层处理 + * + * @author Dftre + * @date 2024-01-18 + */ +@Service +public class OauthUserServiceImpl implements IOauthUserService { + @Autowired + private OauthUserMapper oauthUserMapper; + + @Autowired + private SysUserMapper sysUserMapper; + + /** + * 查询第三方认证 + * + * @param id 第三方认证主键 + * @return 第三方认证 + */ + @Override + public OauthUser selectOauthUserById(Long id) { + return oauthUserMapper.selectOauthUserById(id); + } + + @Override + public OauthUser selectOauthUserByUUID(String uuid) { + return oauthUserMapper.selectOauthUserByUUID(uuid); + } + + @Override + public OauthUser selectOauthUserByUserId(Long userId) { + return oauthUserMapper.selectOauthUserByUserId(userId); + } + + /** + * 查询第三方认证列表 + * + * @param oauthUser 第三方认证 + * @return 第三方认证 + */ + @Override + public List selectOauthUserList(OauthUser oauthUser) { + return oauthUserMapper.selectOauthUserList(oauthUser); + } + + /** + * 新增第三方认证 + * + * @param oauthUser 第三方认证 + * @return 结果 + */ + @Override + public int insertOauthUser(OauthUser oauthUser) { + return oauthUserMapper.insertOauthUser(oauthUser); + } + + /** + * 修改第三方认证 + * + * @param oauthUser 第三方认证 + * @return 结果 + */ + @Override + public int updateOauthUser(OauthUser oauthUser) { + return oauthUserMapper.updateOauthUser(oauthUser); + } + + /** + * 批量删除第三方认证 + * + * @param ids 需要删除的第三方认证主键 + * @return 结果 + */ + @Override + public int deleteOauthUserByIds(Long[] ids) { + return oauthUserMapper.deleteOauthUserByIds(ids); + } + + /** + * 删除第三方认证信息 + * + * @param id 第三方认证主键 + * @return 结果 + */ + @Override + public int deleteOauthUserById(Long id) { + return oauthUserMapper.deleteOauthUserById(id); + } + + public SysUser selectSysUserByUUID(String uuid) { + OauthUser oauthUser = oauthUserMapper.selectOauthUserByUUID(uuid); + return sysUserMapper.selectUserById(oauthUser.getUserId()); + } + + /** + * 校验source平台是否绑定 + * + * @param userId 用户编号 + * @param source 绑定平台 + * @return 结果 + */ + public boolean checkAuthUser(Long userId, String source) { + return oauthUserMapper.checkAuthUser(userId, source) > 0; + }; + + /** + * 校验用户名称是否唯一 + * + * @param userName 用户名称 + * @return 结果 + */ + public boolean checkUserNameUnique(String userName) { + return oauthUserMapper.checkUserNameUnique(userName) > 0; + }; + + /** + * 校验手机号码是否唯一 + * + * @param phonenumber 手机号码 + * @return 结果 + */ + public boolean checkPhoneUnique(String phonenumber) { + return oauthUserMapper.checkPhoneUnique(phonenumber) > 0; + }; + + /** + * 校验email是否唯一 + * + * @param email 用户邮箱 + * @return 结果 + */ + public boolean checkEmailUnique(String email) { + return oauthUserMapper.checkEmailUnique(email) > 0; + }; +} diff --git a/boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/utils/RandomCodeUtil.java b/boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/utils/RandomCodeUtil.java new file mode 100644 index 0000000..8e09667 --- /dev/null +++ b/boyue-auth/boyue-auth-common/src/main/java/com/boyue/auth/common/utils/RandomCodeUtil.java @@ -0,0 +1,28 @@ +package com.boyue.auth.common.utils; + +import java.security.SecureRandom; + +public class RandomCodeUtil { + + public static String randomString(String characters, int length) { + StringBuilder result = new StringBuilder(); + SecureRandom random = new SecureRandom(); + + for (int i = 0; i < length; i++) { + int index = random.nextInt(characters.length()); + result.append(characters.charAt(index)); + } + + return result.toString(); + } + + public static String numberCode(int length) { + String characters = "0123456789"; + return randomString(characters, length); + } + + public static String code(int length) { + String characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + return randomString(characters, length); + } +} diff --git a/boyue-auth/boyue-auth-common/src/main/resources/mapper/common/OauthUserMapper.xml b/boyue-auth/boyue-auth-common/src/main/resources/mapper/common/OauthUserMapper.xml new file mode 100644 index 0000000..bbf64ad --- /dev/null +++ b/boyue-auth/boyue-auth-common/src/main/resources/mapper/common/OauthUserMapper.xml @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + select id, uuid, user_id, source, access_token, expire_in, refresh_token, open_id, uid, access_code, union_id, scope, token_type, id_token, mac_algorithm, mac_key, code, oauth_token, oauth_token_secret from oauth_user + + + + + + + + + + + + + + + + + + + + insert into oauth_user + + id, + uuid, + user_id, + source, + access_token, + expire_in, + refresh_token, + open_id, + uid, + access_code, + union_id, + scope, + token_type, + id_token, + mac_algorithm, + mac_key, + code, + oauth_token, + oauth_token_secret, + + + #{id}, + #{uuid}, + #{userId}, + #{source}, + #{accessToken}, + #{expireIn}, + #{refreshToken}, + #{openId}, + #{uid}, + #{accessCode}, + #{unionId}, + #{scope}, + #{tokenType}, + #{idToken}, + #{macAlgorithm}, + #{macKey}, + #{code}, + #{oauthToken}, + #{oauthTokenSecret}, + + + + + update oauth_user + + uuid = #{uuid}, + user_id = #{userId}, + source = #{source}, + access_token = #{accessToken}, + expire_in = #{expireIn}, + refresh_token = #{refreshToken}, + open_id = #{openId}, + uid = #{uid}, + access_code = #{accessCode}, + union_id = #{unionId}, + scope = #{scope}, + token_type = #{tokenType}, + id_token = #{idToken}, + mac_algorithm = #{macAlgorithm}, + mac_key = #{macKey}, + code = #{code}, + oauth_token = #{oauthToken}, + oauth_token_secret = #{oauthTokenSecret}, + + where oauth_user.id = #{id} + + + + delete from oauth_user where id = #{id} + + + + delete from oauth_user where id in + + #{id} + + + \ No newline at end of file diff --git a/boyue-auth/boyue-auth-starter/pom.xml b/boyue-auth/boyue-auth-starter/pom.xml new file mode 100644 index 0000000..d46344a --- /dev/null +++ b/boyue-auth/boyue-auth-starter/pom.xml @@ -0,0 +1,50 @@ + + + + boyue-auth + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-auth-starter + + + 第三方认证模块 + + + + + + com.boyue + boyue-auth-common + + + + + com.boyue + boyue-oauth-justauth + + + + + com.boyue + boyue-oauth-wx + + + + + com.boyue + boyue-tfa-phone + + + + + com.boyue + boyue-tfa-email + + + + \ No newline at end of file diff --git a/boyue-auth/boyue-auth-starter/src/main/java/com/boyue/auth/controller/OauthUserController.java b/boyue-auth/boyue-auth-starter/src/main/java/com/boyue/auth/controller/OauthUserController.java new file mode 100644 index 0000000..30e7530 --- /dev/null +++ b/boyue-auth/boyue-auth-starter/src/main/java/com/boyue/auth/controller/OauthUserController.java @@ -0,0 +1,109 @@ +package com.boyue.auth.controller; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.auth.common.domain.OauthUser; +import com.boyue.auth.common.service.IOauthUserService; +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.enums.BusinessType; +import com.boyue.common.utils.poi.ExcelUtil; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 第三方认证Controller + * + * @author Dftre + * @date 2024-01-18 + */ +@RestController +@RequestMapping("/system/oauth") +@Tag(name = "【第三方认证】管理") +public class OauthUserController extends BaseController { + @Autowired + private IOauthUserService oauthUserService; + + /** + * 查询第三方认证列表 + */ + @Operation(summary = "查询第三方认证列表") + @PreAuthorize("@ss.hasPermi('system:oauth:list')") + @GetMapping("/list") + public TableDataInfo list(OauthUser oauthUser) { + startPage(); + List list = oauthUserService.selectOauthUserList(oauthUser); + return getDataTable(list); + } + + /** + * 导出第三方认证列表 + */ + @Operation(summary = "导出第三方认证列表") + @PreAuthorize("@ss.hasPermi('system:oauth:export')") + @Log(title = "第三方认证", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, OauthUser oauthUser) { + List list = oauthUserService.selectOauthUserList(oauthUser); + ExcelUtil util = new ExcelUtil(OauthUser.class); + util.exportExcel(response, list, "第三方认证数据"); + } + + /** + * 获取第三方认证详细信息 + */ + @Operation(summary = "获取第三方认证详细信息") + @PreAuthorize("@ss.hasPermi('system:oauth:query')") + @GetMapping(value = "/{id}") + public AjaxResult getInfo(@PathVariable("id") Long id) { + return success(oauthUserService.selectOauthUserById(id)); + } + + /** + * 新增第三方认证 + */ + @Operation(summary = "新增第三方认证") + @PreAuthorize("@ss.hasPermi('system:oauth:add')") + @Log(title = "第三方认证", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody OauthUser oauthUser) { + return toAjax(oauthUserService.insertOauthUser(oauthUser)); + } + + /** + * 修改第三方认证 + */ + @Operation(summary = "修改第三方认证") + @PreAuthorize("@ss.hasPermi('system:oauth:edit')") + @Log(title = "第三方认证", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody OauthUser oauthUser) { + return toAjax(oauthUserService.updateOauthUser(oauthUser)); + } + + /** + * 删除第三方认证 + */ + @Operation(summary = "删除第三方认证") + @PreAuthorize("@ss.hasPermi('system:oauth:remove')") + @Log(title = "第三方认证", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable(name = "ids") Long[] ids) { + return toAjax(oauthUserService.deleteOauthUserByIds(ids)); + } +} diff --git a/boyue-auth/boyue-auth-starter/src/main/java/com/boyue/auth/controller/TfaController.java b/boyue-auth/boyue-auth-starter/src/main/java/com/boyue/auth/controller/TfaController.java new file mode 100644 index 0000000..dd8fd86 --- /dev/null +++ b/boyue-auth/boyue-auth-starter/src/main/java/com/boyue/auth/controller/TfaController.java @@ -0,0 +1,110 @@ +package com.boyue.auth.controller; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.auth.common.service.TfaService; +import com.boyue.common.annotation.Anonymous; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.domain.model.LoginBody; +import com.boyue.common.core.domain.model.RegisterBody; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.PostConstruct; + +@RestController +@RequestMapping("/auth/{channel}") // dySms mail +@Tag(name = "【第三方认证】双因素认证") +public class TfaController extends BaseController { + + @Autowired(required = false) + Map tfaServiceMap; + + @PostConstruct + public void init() { + if (tfaServiceMap == null) { + tfaServiceMap = new HashMap<>(); + logger.warn("请注意,没有加载任何双认证服务"); + } else { + tfaServiceMap.forEach((k, v) -> { + logger.info("已加载双认证服务 {}", k); + }); + } + } + + @Operation(summary = "发送注册验证码") + @PostMapping("/send/register") + @Anonymous + public AjaxResult sendRegister(@PathVariable String channel, @RequestBody RegisterBody registerBody) { + TfaService tfaService = tfaServiceMap.get("auth:service:" + channel); + tfaService.doRegister(registerBody); + return success(); + } + + @Operation(summary = "验证注册验证码") + @PostMapping("/verify/register") + @Anonymous + public AjaxResult verifyRegister(@PathVariable String channel, @RequestBody RegisterBody registerBody) { + TfaService tfaService = tfaServiceMap.get("auth:service:" + channel); + tfaService.doRegisterVerify(registerBody); + return success(); + } + + @Operation(summary = "发送登录验证码") + @PostMapping("/send/login") + @Anonymous + public AjaxResult sendLogin(@PathVariable String channel, @RequestBody LoginBody loginBody) { + TfaService tfaService = tfaServiceMap.get("auth:service:" + channel); + tfaService.doLogin(loginBody); + return success(); + } + + @Operation(summary = "验证登录验证码") + @PostMapping("/verify/login") + @Anonymous + public AjaxResult verifyLogin(@PathVariable String channel, @RequestBody LoginBody loginBody) { + TfaService tfaService = tfaServiceMap.get("auth:service:" + channel); + return success(tfaService.doLoginVerify(loginBody)); + } + + @Operation(summary = "发送绑定验证码") + @PostMapping("/send/bind") + public AjaxResult send(@PathVariable String channel, @RequestBody LoginBody loginBody) { + TfaService tfaService = tfaServiceMap.get("auth:service:" + channel); + tfaService.doBind(loginBody); + return success(); + } + + @Operation(summary = "验证绑定验证码") + @PostMapping("/verify/bind") // 发送验证码 + public AjaxResult verify(@PathVariable String channel, @RequestBody LoginBody loginBody) { + TfaService tfaService = tfaServiceMap.get("auth:service:" + channel); + tfaService.doBindVerify(loginBody); + return success(); + } + + @Operation(summary = "发送重置验证码") + @PostMapping("/send/reset") + public AjaxResult sendReset(@PathVariable String channel, @RequestBody RegisterBody registerBody) { + TfaService tfaService = tfaServiceMap.get("auth:service:" + channel); + tfaService.doReset(registerBody); + return success(); + } + + @Operation(summary = "验证重置验证码") + @PostMapping("/verify/reset") + public AjaxResult verifyReset(@PathVariable String channel, @RequestBody RegisterBody registerBody) { + TfaService tfaService = tfaServiceMap.get("auth:service:" + channel); + tfaService.doReset(registerBody); + return success(); + } +} diff --git a/boyue-auth/boyue-oauth-justauth/pom.xml b/boyue-auth/boyue-oauth-justauth/pom.xml new file mode 100644 index 0000000..7a7dc98 --- /dev/null +++ b/boyue-auth/boyue-oauth-justauth/pom.xml @@ -0,0 +1,37 @@ + + + + boyue-auth + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-oauth-justauth + + + justauth框架认证模块 + + + + + + + com.boyue + boyue-auth-common + + + + me.zhyd.oauth + JustAuth + + + + com.alipay.sdk + alipay-sdk-java + + + + + \ No newline at end of file diff --git a/boyue-auth/boyue-oauth-justauth/src/main/java/com/boyue/oauth/justauth/controller/SysAuthController.java b/boyue-auth/boyue-oauth-justauth/src/main/java/com/boyue/oauth/justauth/controller/SysAuthController.java new file mode 100644 index 0000000..fc6b583 --- /dev/null +++ b/boyue-auth/boyue-oauth-justauth/src/main/java/com/boyue/oauth/justauth/controller/SysAuthController.java @@ -0,0 +1,180 @@ +package com.boyue.oauth.justauth.controller; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import com.alibaba.fastjson2.JSONObject; +import com.boyue.auth.common.domain.OauthUser; +import com.boyue.auth.common.service.IOauthUserService; +import com.boyue.common.annotation.Anonymous; +import com.boyue.common.constant.Constants; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.domain.entity.SysUser; +import com.boyue.common.core.domain.model.LoginUser; +import com.boyue.common.enums.UserStatus; +import com.boyue.common.exception.ServiceException; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.framework.web.service.SysPermissionService; +import com.boyue.framework.web.service.TokenService; +import com.boyue.oauth.justauth.utils.JustAuthUtils; +import com.boyue.system.service.ISysUserService; + +import jakarta.servlet.http.HttpServletRequest; +import me.zhyd.oauth.cache.AuthDefaultStateCache; +import me.zhyd.oauth.cache.AuthStateCache; +import me.zhyd.oauth.model.AuthCallback; +import me.zhyd.oauth.model.AuthResponse; +import me.zhyd.oauth.model.AuthUser; +import me.zhyd.oauth.request.AuthRequest; +import me.zhyd.oauth.utils.AuthStateUtils; + +/** + * 第三方认证授权处理 + * + * @author boyue + */ +@RestController +@RequestMapping("/system/auth") +public class SysAuthController extends BaseController +{ + private AuthStateCache authStateCache; + + @Autowired + private ISysUserService userService; + + @Autowired + private SysPermissionService permissionService; + + @Autowired + private TokenService tokenService; + + @Autowired + private IOauthUserService oauthUserService; + + private final static Map auths = new HashMap(); + { + auths.put("gitee", "{\"clientId\":\"d83265831bf1b09765c27a9fd9860a9fbb2d939b13460454e85d3d551eebb157\",\"clientSecret\":\"23e3d066c85fc5ee6eaad91067ad006bb5475796f9b9c024eaf5bd26acadd7a3\",\"redirectUri\":\"http://127.0.0.1:80/social-login?source=gitee\"}"); + auths.put("github", "{\"clientId\":\"Iv1.1be0cdcd71aca63b\",\"clientSecret\":\"0d59d28b43152bc8906011624db37b0fed88d154\",\"redirectUri\":\"http://127.0.0.1:80/social-login?source=github\"}"); + authStateCache = AuthDefaultStateCache.INSTANCE; + } + + /** + * 认证授权 + * + * @param source + * @throws IOException + */ + @GetMapping("/binding/{source}") + @ResponseBody + public AjaxResult authBinding(@PathVariable("source") String source, HttpServletRequest request) throws IOException + { + LoginUser tokenUser = tokenService.getLoginUser(request); + if (StringUtils.isNotNull(tokenUser) && oauthUserService.checkAuthUser(tokenUser.getUserId(), source)) + { + return error(source + "平台账号已经绑定"); + } + + String obj = auths.get(source); + if (StringUtils.isEmpty(obj)) + { + return error(source + "平台账号暂不支持"); + } + JSONObject json = JSONObject.parseObject(obj); + AuthRequest authRequest = JustAuthUtils.getAuthRequest(source, json.getString("clientId"), json.getString("clientSecret"), json.getString("redirectUri"), authStateCache); + String authorizeUrl = authRequest.authorize(AuthStateUtils.createState()); + return success(authorizeUrl); + } + + /** + * 第三方登录回调 + * @param source + * @param callback + * @param request + * @return + */ + @Anonymous + @GetMapping("/social-login/{source}") + public AjaxResult socialLogin(@PathVariable("source") String source, AuthCallback callback, HttpServletRequest request) + { + String obj = auths.get(source); + if (StringUtils.isEmpty(obj)) + { + return AjaxResult.error(10002, "第三方平台系统不支持或未提供来源"); + } + JSONObject json = JSONObject.parseObject(obj); + AuthRequest authRequest = JustAuthUtils.getAuthRequest(source, json.getString("clientId"), json.getString("clientSecret"), json.getString("redirectUri"), authStateCache); + AuthResponse response = authRequest.login(callback); + if (response.ok()) + { + LoginUser tokenUser = tokenService.getLoginUser(request); + if (StringUtils.isNotNull(tokenUser)) + { + SysUser user = oauthUserService.selectSysUserByUUID(source + response.getData().getUuid()); + if (StringUtils.isNotNull(user)) + { + String token = tokenService.createToken(SecurityUtils.getLoginUser()); + return success().put(Constants.TOKEN, token); + } + // 若已经登录则直接绑定系统账号 + OauthUser authUser = new OauthUser(); + // SysUser sysUser = new SysUser(); + // sysUser.setAvatar(response.getData().getAvatar()); + authUser.setUuid(source + response.getData().getUuid()); + authUser.setUserId(SecurityUtils.getUserId()); + // sysUser.setUserName(response.getData().getUsername()); + // sysUser.setNickName(response.getData().getNickname()); + // sysUser.setEmail(response.getData().getEmail()); + authUser.setSource(source); + oauthUserService.insertOauthUser(authUser); + // userService.insertUser(sysUser); + String token = tokenService.createToken(SecurityUtils.getLoginUser()); + return success().put(Constants.TOKEN, token); + } + SysUser authUser = oauthUserService.selectSysUserByUUID(source + response.getData().getUuid()); + if (StringUtils.isNotNull(authUser)) + { + SysUser user = userService.selectUserByUserName(authUser.getUserName()); + if (StringUtils.isNull(user)) + { + throw new ServiceException("登录用户:" + user.getUserName() + " 不存在"); + } + else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) + { + throw new ServiceException("对不起,您的账号:" + user.getUserName() + " 已被删除"); + } + else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) + { + throw new ServiceException("对不起,您的账号:" + user.getUserName() + " 已停用"); + } + LoginUser loginUser = new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user)); + String token = tokenService.createToken(loginUser); + return success().put(Constants.TOKEN, token); + } + else + { + return AjaxResult.error(10002, "对不起,您没有绑定注册用户,请先注册后在个人中心绑定第三方授权信息!"); + } + } + return AjaxResult.error(10002, "对不起,授权信息验证不通过,请联系管理员"); + } + + /** + * 取消授权 + */ + @DeleteMapping(value = "/unlock/{authId}") + public AjaxResult unlockAuth(@PathVariable Long authId) + { + return toAjax(oauthUserService.deleteOauthUserById(authId)); + } +} diff --git a/boyue-auth/boyue-oauth-justauth/src/main/java/com/boyue/oauth/justauth/utils/JustAuthUtils.java b/boyue-auth/boyue-oauth-justauth/src/main/java/com/boyue/oauth/justauth/utils/JustAuthUtils.java new file mode 100644 index 0000000..66bee70 --- /dev/null +++ b/boyue-auth/boyue-oauth-justauth/src/main/java/com/boyue/oauth/justauth/utils/JustAuthUtils.java @@ -0,0 +1,177 @@ +package com.boyue.oauth.justauth.utils; + +import me.zhyd.oauth.cache.AuthStateCache; +import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.exception.AuthException; +import me.zhyd.oauth.request.AuthAlipayRequest; +import me.zhyd.oauth.request.AuthAliyunRequest; +import me.zhyd.oauth.request.AuthBaiduRequest; +import me.zhyd.oauth.request.AuthCodingRequest; +// import me.zhyd.oauth.request.AuthCsdnRequest; +import me.zhyd.oauth.request.AuthDingTalkRequest; +import me.zhyd.oauth.request.AuthDouyinRequest; +import me.zhyd.oauth.request.AuthElemeRequest; +import me.zhyd.oauth.request.AuthGiteeRequest; +import me.zhyd.oauth.request.AuthGithubRequest; +import me.zhyd.oauth.request.AuthGitlabRequest; +import me.zhyd.oauth.request.AuthHuaweiRequest; +import me.zhyd.oauth.request.AuthKujialeRequest; +import me.zhyd.oauth.request.AuthLinkedinRequest; +import me.zhyd.oauth.request.AuthMeituanRequest; +import me.zhyd.oauth.request.AuthMiRequest; +import me.zhyd.oauth.request.AuthMicrosoftRequest; +import me.zhyd.oauth.request.AuthOschinaRequest; +import me.zhyd.oauth.request.AuthPinterestRequest; +import me.zhyd.oauth.request.AuthQqRequest; +import me.zhyd.oauth.request.AuthRenrenRequest; +import me.zhyd.oauth.request.AuthRequest; +import me.zhyd.oauth.request.AuthStackOverflowRequest; +import me.zhyd.oauth.request.AuthTaobaoRequest; +import me.zhyd.oauth.request.AuthTeambitionRequest; +import me.zhyd.oauth.request.AuthToutiaoRequest; +import me.zhyd.oauth.request.AuthWeChatMpRequest; +import me.zhyd.oauth.request.AuthWeChatOpenRequest; +import me.zhyd.oauth.request.AuthWeiboRequest; + +/** + * 认证授权工具类 + * + * @author boyue + */ +public class JustAuthUtils +{ + @SuppressWarnings("deprecation") + public static AuthRequest getAuthRequest(String source, String clientId, String clientSecret, String redirectUri, + AuthStateCache authStateCache) + { + AuthRequest authRequest = null; + switch (source.toLowerCase()) + { + case "dingtalk": + authRequest = new AuthDingTalkRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "baidu": + authRequest = new AuthBaiduRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "github": + authRequest = new AuthGithubRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "gitee": + authRequest = new AuthGiteeRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "weibo": + authRequest = new AuthWeiboRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "coding": + authRequest = new AuthCodingRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "oschina": + authRequest = new AuthOschinaRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "alipay": + // 支付宝在创建回调地址时,不允许使用localhost或者127.0.0.1,所以这儿的回调地址使用的局域网内的ip + authRequest = new AuthAlipayRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .alipayPublicKey("").redirectUri(redirectUri).build(), authStateCache); + break; + case "qq": + authRequest = new AuthQqRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "wechat_open": + authRequest = new AuthWeChatOpenRequest(AuthConfig.builder().clientId(clientId) + .clientSecret(clientSecret).redirectUri(redirectUri).build(), authStateCache); + break; + // case "csdn": + // authRequest = new AuthCsdnRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + // .redirectUri(redirectUri).build(), authStateCache); + // break; + case "taobao": + authRequest = new AuthTaobaoRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "douyin": + authRequest = new AuthDouyinRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "linkedin": + authRequest = new AuthLinkedinRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "microsoft": + authRequest = new AuthMicrosoftRequest(AuthConfig.builder().clientId(clientId) + .clientSecret(clientSecret).redirectUri(redirectUri).build(), authStateCache); + break; + case "mi": + authRequest = new AuthMiRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "toutiao": + authRequest = new AuthToutiaoRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "teambition": + authRequest = new AuthTeambitionRequest(AuthConfig.builder().clientId(clientId) + .clientSecret(clientSecret).redirectUri(redirectUri).build(), authStateCache); + break; + case "pinterest": + authRequest = new AuthPinterestRequest(AuthConfig.builder().clientId(clientId) + .clientSecret(clientSecret).redirectUri(redirectUri).build(), authStateCache); + break; + case "renren": + authRequest = new AuthRenrenRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "stack_overflow": + authRequest = new AuthStackOverflowRequest(AuthConfig.builder().clientId(clientId) + .clientSecret(clientSecret).redirectUri(redirectUri).stackOverflowKey("").build(), + authStateCache); + break; + case "huawei": + authRequest = new AuthHuaweiRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + // case "wechat_enterprise": + // authRequest = new AuthWeChatEnterpriseRequest(AuthConfig.builder().clientId(clientId) + // .clientSecret(clientSecret).redirectUri(redirectUri).agentId("").build(), authStateCache); + // break; + case "kujiale": + authRequest = new AuthKujialeRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "gitlab": + authRequest = new AuthGitlabRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "meituan": + authRequest = new AuthMeituanRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "eleme": + authRequest = new AuthElemeRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build()); + break; + case "wechat_mp": + authRequest = new AuthWeChatMpRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + case "aliyun": + authRequest = new AuthAliyunRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret) + .redirectUri(redirectUri).build(), authStateCache); + break; + default: + break; + } + if (null == authRequest) + { + throw new AuthException("未获取到有效的Auth配置"); + } + return authRequest; + } +} diff --git a/boyue-auth/boyue-oauth-wx/pom.xml b/boyue-auth/boyue-oauth-wx/pom.xml new file mode 100644 index 0000000..8ca9b11 --- /dev/null +++ b/boyue-auth/boyue-oauth-wx/pom.xml @@ -0,0 +1,29 @@ + + + + boyue-auth + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-oauth-wx + + + 微信认证模块 + + + + + + + com.boyue + boyue-auth-common + + + + + + \ No newline at end of file diff --git a/boyue-auth/boyue-oauth-wx/src/main/java/com/boyue/oauth/wx/constant/WxMiniAppConstant.java b/boyue-auth/boyue-oauth-wx/src/main/java/com/boyue/oauth/wx/constant/WxMiniAppConstant.java new file mode 100644 index 0000000..6b3ff48 --- /dev/null +++ b/boyue-auth/boyue-oauth-wx/src/main/java/com/boyue/oauth/wx/constant/WxMiniAppConstant.java @@ -0,0 +1,42 @@ +package com.boyue.oauth.wx.constant; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class WxMiniAppConstant { + @Value("${oauth.wx.miniapp.appId}") + private String appId; + + @Value("${oauth.wx.miniapp.appSecret}") + private String appSecret; + + @Value("${oauth.wx.miniapp.url}") + private String url; + + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getAppSecret() { + return appSecret; + } + + public void setAppSecret(String appSecret) { + this.appSecret = appSecret; + } + +} diff --git a/boyue-auth/boyue-oauth-wx/src/main/java/com/boyue/oauth/wx/constant/WxPubConstant.java b/boyue-auth/boyue-oauth-wx/src/main/java/com/boyue/oauth/wx/constant/WxPubConstant.java new file mode 100644 index 0000000..6f7694d --- /dev/null +++ b/boyue-auth/boyue-oauth-wx/src/main/java/com/boyue/oauth/wx/constant/WxPubConstant.java @@ -0,0 +1,41 @@ +package com.boyue.oauth.wx.constant; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class WxPubConstant { + @Value("${oauth.wx.pub.appId}") + private String appId; + + @Value("${oauth.wx.pub.appSecret}") + private String appSecret; + + @Value("${oauth.wx.pub.url}") + private String url; + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getAppSecret() { + return appSecret; + } + + public void setAppSecret(String appSecret) { + this.appSecret = appSecret; + } + +} diff --git a/boyue-auth/boyue-oauth-wx/src/main/java/com/boyue/oauth/wx/controller/WxLoginController.java b/boyue-auth/boyue-oauth-wx/src/main/java/com/boyue/oauth/wx/controller/WxLoginController.java new file mode 100644 index 0000000..5e683c7 --- /dev/null +++ b/boyue-auth/boyue-oauth-wx/src/main/java/com/boyue/oauth/wx/controller/WxLoginController.java @@ -0,0 +1,69 @@ +package com.boyue.oauth.wx.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.auth.common.domain.OauthUser; +import com.boyue.auth.common.service.IOauthUserService; +import com.boyue.common.annotation.Anonymous; +import com.boyue.common.constant.Constants; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.utils.StringUtils; +import com.boyue.oauth.wx.service.Impl.WxMiniAppLoginServiceImpl; +import com.boyue.oauth.wx.service.Impl.WxPubLoginServiceImpl; + +@RestController +@RequestMapping("/oauth/wx") +public class WxLoginController extends BaseController { + + @Autowired + private IOauthUserService oauthUserService; + + @Autowired + private WxMiniAppLoginServiceImpl wxMiniAppLoginServiceImpl; + + @Autowired + private WxPubLoginServiceImpl wxPubLoginServiceImpl; + + @Anonymous + @PostMapping("/login/{source}/{code}") + public AjaxResult loginMiniApp(@PathVariable("source") String source, @PathVariable("code") String code) { + String token = null; + AjaxResult ajax = AjaxResult.success(); + if ("miniapp".equals(source)) { + token = wxMiniAppLoginServiceImpl.doLogin(code); + } else if ("pub".equals(source)) { + token = wxPubLoginServiceImpl.doLogin(code); + } else { + return error("错误的登录方式"); + } + ajax.put(Constants.TOKEN, token); + return ajax; + } + + @PostMapping("/register/{source}/{code}") + public AjaxResult register(@PathVariable("source") String source, @PathVariable("code") String code) { + OauthUser oauthUser = oauthUserService.selectOauthUserByUserId(getUserId()); + if (oauthUser != null) { + return error("不可以重复绑定"); + } else { + String msg = ""; + oauthUser = new OauthUser(); + oauthUser.setUserId(getUserId()); + oauthUser.setCode(code); + if ("miniapp".equals(source)) { + msg = wxMiniAppLoginServiceImpl.doRegister(oauthUser); + } else if ("pub".equals(source)) { + msg = wxPubLoginServiceImpl.doRegister(oauthUser); + } else { + return error("错误的注册方式"); + } + return StringUtils.isEmpty(msg) ? success() : error(msg); + } + } + +} diff --git a/boyue-auth/boyue-oauth-wx/src/main/java/com/boyue/oauth/wx/service/Impl/WxMiniAppLoginServiceImpl.java b/boyue-auth/boyue-oauth-wx/src/main/java/com/boyue/oauth/wx/service/Impl/WxMiniAppLoginServiceImpl.java new file mode 100644 index 0000000..9445769 --- /dev/null +++ b/boyue-auth/boyue-oauth-wx/src/main/java/com/boyue/oauth/wx/service/Impl/WxMiniAppLoginServiceImpl.java @@ -0,0 +1,76 @@ +package com.boyue.oauth.wx.service.Impl; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.alibaba.fastjson2.JSONObject; +import com.boyue.auth.common.domain.OauthUser; +import com.boyue.auth.common.service.IOauthUserService; +import com.boyue.common.core.domain.entity.SysUser; +import com.boyue.common.core.domain.model.LoginUser; +import com.boyue.common.exception.ServiceException; +import com.boyue.common.utils.StringUtils; +import com.boyue.framework.web.service.TokenService; +import com.boyue.framework.web.service.UserDetailsServiceImpl; +import com.boyue.oauth.wx.constant.WxMiniAppConstant; +import com.boyue.oauth.wx.service.WxLoginService; +import com.boyue.system.service.ISysUserService; + +@Service +public class WxMiniAppLoginServiceImpl implements WxLoginService { + @Autowired + private WxMiniAppConstant wxAppConstant; + + @Autowired + private TokenService tokenService; + + @Autowired + private UserDetailsServiceImpl userDetailsServiceImpl; + + @Autowired + private ISysUserService userService; + + @Autowired + private IOauthUserService oauthUserService; + + @Override + public String doLogin(String code) { + String openid = doAuth( + wxAppConstant.getUrl(), + wxAppConstant.getAppId(), + wxAppConstant.getAppSecret(), + code).getString("openid"); + OauthUser selectOauthUser = oauthUserService.selectOauthUserByUUID(openid); + if (selectOauthUser == null) { + return null; + } + SysUser sysUser = userService.selectUserById(selectOauthUser.getUserId()); + if (sysUser == null) { + throw new ServiceException("该微信未绑定用户"); + } + LoginUser loginUser = (LoginUser) userDetailsServiceImpl.createLoginUser(sysUser); + return tokenService.createToken(loginUser); + } + + @Override + public String doRegister(OauthUser oauthUser) { + if (StringUtils.isEmpty(oauthUser.getCode())) { + return "没有凭证"; + } + if (oauthUser.getUserId() == null) { + return "请先注册账号"; + } + JSONObject doAuth = doAuth( + wxAppConstant.getUrl(), + wxAppConstant.getAppId(), + wxAppConstant.getAppSecret(), + oauthUser.getCode()); + oauthUser.setOpenId(doAuth.getString("openid")); + oauthUser.setUuid(doAuth.getString("openid")); + oauthUser.setSource("WXMiniApp"); + oauthUser.setAccessToken(doAuth.getString("sessionKey")); + oauthUserService.insertOauthUser(oauthUser); + return ""; + } + +} diff --git a/boyue-auth/boyue-oauth-wx/src/main/java/com/boyue/oauth/wx/service/Impl/WxPubLoginServiceImpl.java b/boyue-auth/boyue-oauth-wx/src/main/java/com/boyue/oauth/wx/service/Impl/WxPubLoginServiceImpl.java new file mode 100644 index 0000000..5efa898 --- /dev/null +++ b/boyue-auth/boyue-oauth-wx/src/main/java/com/boyue/oauth/wx/service/Impl/WxPubLoginServiceImpl.java @@ -0,0 +1,77 @@ +package com.boyue.oauth.wx.service.Impl; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.alibaba.fastjson2.JSONObject; +import com.boyue.auth.common.domain.OauthUser; +import com.boyue.auth.common.service.IOauthUserService; +import com.boyue.common.core.domain.entity.SysUser; +import com.boyue.common.core.domain.model.LoginUser; +import com.boyue.common.exception.ServiceException; +import com.boyue.common.utils.StringUtils; +import com.boyue.framework.web.service.TokenService; +import com.boyue.framework.web.service.UserDetailsServiceImpl; +import com.boyue.oauth.wx.constant.WxPubConstant; +import com.boyue.oauth.wx.service.WxLoginService; +import com.boyue.system.service.ISysUserService; + +@Service +public class WxPubLoginServiceImpl implements WxLoginService { + + @Autowired + private WxPubConstant wxH5Constant; + + @Autowired + private TokenService tokenService; + + @Autowired + private UserDetailsServiceImpl userDetailsServiceImpl; + + @Autowired + private ISysUserService userService; + + @Autowired + private IOauthUserService oauthUserService; + + @Override + public String doLogin(String code) { + String openid = doAuth( + wxH5Constant.getUrl(), + wxH5Constant.getAppId(), + wxH5Constant.getAppSecret(), + code).getString("openid"); + OauthUser selectOauthUser = oauthUserService.selectOauthUserByUUID(openid); + if (selectOauthUser == null) { + return null; + } + SysUser sysUser = userService.selectUserById(selectOauthUser.getUserId()); + if (sysUser == null) { + throw new ServiceException("该微信未绑定用户"); + } + LoginUser loginUser = (LoginUser) userDetailsServiceImpl.createLoginUser(sysUser); + return tokenService.createToken(loginUser); + } + + @Override + public String doRegister(OauthUser oauthUser) { + if (StringUtils.isEmpty(oauthUser.getCode())) { + return "没有凭证"; + } + if (oauthUser.getUserId() == null) { + return "请先注册账号"; + } + JSONObject doAuth = doAuth( + wxH5Constant.getUrl(), + wxH5Constant.getAppId(), + wxH5Constant.getAppSecret(), + oauthUser.getCode()); + oauthUser.setOpenId(doAuth.getString("openid")); + oauthUser.setUuid(doAuth.getString("openid")); + oauthUser.setSource("WXPub"); + oauthUser.setAccessToken(doAuth.getString("sessionKey")); + oauthUserService.insertOauthUser(oauthUser); + return ""; + } + +} diff --git a/boyue-auth/boyue-oauth-wx/src/main/java/com/boyue/oauth/wx/service/WxLoginService.java b/boyue-auth/boyue-oauth-wx/src/main/java/com/boyue/oauth/wx/service/WxLoginService.java new file mode 100644 index 0000000..6d4915c --- /dev/null +++ b/boyue-auth/boyue-oauth-wx/src/main/java/com/boyue/oauth/wx/service/WxLoginService.java @@ -0,0 +1,34 @@ +package com.boyue.oauth.wx.service; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.boyue.auth.common.domain.OauthUser; +import com.boyue.common.exception.ServiceException; +import com.boyue.common.utils.http.HttpClientUtil; + +public interface WxLoginService { + + public String doLogin(String code); + + public String doRegister(OauthUser oauthUser); + + public default JSONObject doAuth(String url, String appid, String secret, String code) { + StringBuilder builder = new StringBuilder(url); + builder.append("?appid=").append(appid) + .append("&secret=").append(secret) + .append("&js_code=").append(code) + .append("&grant_type=").append("authorization_code"); + String getMessageUrl = builder.toString(); + String result = HttpClientUtil.sendHttpGet(getMessageUrl); + JSONObject jsonObject = JSON.parseObject(result); + if (jsonObject.containsKey("openid")) { + String openid = jsonObject.getString("openid"); + String sessionKey = jsonObject.getString("session_key"); + System.out.println("openid:" + openid); + System.out.println("sessionKey:" + sessionKey); + return jsonObject; + } else { + throw new ServiceException(jsonObject.getString("errmsg"), jsonObject.getIntValue("errcode")); + } + } +} diff --git a/boyue-auth/boyue-oauth-wx/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/boyue-auth/boyue-oauth-wx/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000..8af98a8 --- /dev/null +++ b/boyue-auth/boyue-oauth-wx/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,34 @@ +{ + "properties": [ + { + "name": "oauth.wx.miniapp.app-id", + "type": "java.lang.String", + "description": "微信小程序appid" + }, + { + "name": "oauth.wx.miniapp.app-secret", + "type": "java.lang.String", + "description": "微信小程序appSecret" + }, + { + "name": "oauth.wx.miniapp.url", + "type": "java.lang.String", + "description": "微信小程序认证地址" + }, + { + "name": "oauth.wx.pub.app-id", + "type": "java.lang.String", + "description": "微信公众号appid" + }, + { + "name": "oauth.wx.pub.app-secret", + "type": "java.lang.String", + "description": "微信公众号appSecret" + }, + { + "name": "oauth.wx.pub.url", + "type": "java.lang.String", + "description": "微信公众号认证地址" + } + ] +} \ No newline at end of file diff --git a/boyue-auth/boyue-tfa-email/pom.xml b/boyue-auth/boyue-tfa-email/pom.xml new file mode 100644 index 0000000..2a87986 --- /dev/null +++ b/boyue-auth/boyue-tfa-email/pom.xml @@ -0,0 +1,41 @@ + + + + boyue-auth + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-tfa-email + + + 邮箱认证模块 + + + + + + + com.boyue + boyue-auth-common + + + org.springframework.boot + spring-boot-starter-mail + + + + jakarta.mail + jakarta.mail-api + + + + com.sun.mail + jakarta.mail + + + + + \ No newline at end of file diff --git a/boyue-auth/boyue-tfa-email/src/main/java/com/boyue/tfa/email/service/IMailService.java b/boyue-auth/boyue-tfa-email/src/main/java/com/boyue/tfa/email/service/IMailService.java new file mode 100644 index 0000000..4829b0d --- /dev/null +++ b/boyue-auth/boyue-tfa-email/src/main/java/com/boyue/tfa/email/service/IMailService.java @@ -0,0 +1,7 @@ +package com.boyue.tfa.email.service; + +import com.boyue.auth.common.service.OauthVerificationCodeService; +import com.boyue.auth.common.service.TfaService; + +public interface IMailService extends OauthVerificationCodeService,TfaService { +} diff --git a/boyue-auth/boyue-tfa-email/src/main/java/com/boyue/tfa/email/service/impl/MailServiceImpl.java b/boyue-auth/boyue-tfa-email/src/main/java/com/boyue/tfa/email/service/impl/MailServiceImpl.java new file mode 100644 index 0000000..cedd68a --- /dev/null +++ b/boyue-auth/boyue-tfa-email/src/main/java/com/boyue/tfa/email/service/impl/MailServiceImpl.java @@ -0,0 +1,174 @@ +package com.boyue.tfa.email.service.impl; + +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.boyue.auth.common.enums.OauthVerificationUse; +import com.boyue.auth.common.utils.RandomCodeUtil; +import com.boyue.common.constant.CacheConstants; +import com.boyue.common.constant.Constants; +import com.boyue.common.core.domain.entity.SysUser; +import com.boyue.common.core.domain.model.LoginBody; +import com.boyue.common.core.domain.model.LoginUser; +import com.boyue.common.core.domain.model.RegisterBody; +import com.boyue.common.exception.ServiceException; +import com.boyue.common.utils.CacheUtils; +import com.boyue.common.utils.MessageUtils; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.framework.manager.AsyncManager; +import com.boyue.framework.manager.factory.AsyncFactory; +import com.boyue.framework.web.service.SysLoginService; +import com.boyue.framework.web.service.TokenService; +import com.boyue.framework.web.service.UserDetailsServiceImpl; +import com.boyue.system.service.ISysUserService; +import com.boyue.tfa.email.service.IMailService; +import com.boyue.tfa.email.utils.EmailUtil; + +@Service("auth:service:mail") +public class MailServiceImpl implements IMailService { + + @Autowired + private ISysUserService userService; + @Autowired + private TokenService tokenService; + @Autowired + private UserDetailsServiceImpl userDetailsServiceImpl; + @Autowired + private SysLoginService sysLoginService; + + private static final Logger log = LoggerFactory.getLogger(MailServiceImpl.class); + + @Override + public boolean sendCode(String email, String code, OauthVerificationUse use) { + if (CacheUtils.hasKey(CacheConstants.EMAIL_CODES, use.getValue() + email)) { + throw new ServiceException("当前验证码未失效,请在1分钟后再发送"); + } + + try { + EmailUtil.sendMessage(email, "验证码邮件", "您收到的验证码是:" + code); + CacheUtils.put(CacheConstants.EMAIL_CODES, use.getValue() + email, code, 1, TimeUnit.MINUTES); + log.info("发送邮箱验证码成功:{ email: " + email + " code:" + code + "}"); + return true; + } catch (Exception e) { + throw new ServiceException("发送邮箱验证码异常:" + email); + } + } + + @Override + public boolean checkCode(String email, String code, OauthVerificationUse use) { + if (StringUtils.isEmpty(code)) { + return false; + } + String cachedCode = CacheUtils.get(CacheConstants.EMAIL_CODES, use.getValue() + email, String.class); // 从缓存中获取验证码 + boolean isValid = code.equals(cachedCode); + if (isValid) { + CacheUtils.remove(CacheConstants.EMAIL_CODES, use.getValue() + email); + } + return isValid; + } + + @Override + public void doLogin(LoginBody loginBody) { + SysUser sysUser = userService.selectUserByEmail(loginBody.getEmail()); + if (sysUser == null) { + throw new ServiceException("该邮箱未绑定用户"); + } else { + sendCode(loginBody.getEmail(), RandomCodeUtil.numberCode(6), OauthVerificationUse.LOGIN); + } + } + + @Override + public String doLoginVerify(LoginBody loginBody) { + if (checkCode(loginBody.getEmail(), loginBody.getCode(), OauthVerificationUse.LOGIN)) { + SysUser sysUser = userService.selectUserByEmail(loginBody.getEmail()); + if (sysUser == null) { + throw new ServiceException("该邮箱未绑定用户"); + } + AsyncManager.me().execute(AsyncFactory.recordLogininfor(sysUser.getUserName(), Constants.LOGIN_SUCCESS, + MessageUtils.message("user.login.success"))); + LoginUser loginUser = (LoginUser) userDetailsServiceImpl.createLoginUser(sysUser); + sysLoginService.recordLoginInfo(loginUser.getUserId()); + return tokenService.createToken(loginUser); + } else { + throw new ServiceException("验证码错误"); + } + } + + @Override + public void doRegister(RegisterBody registerBody) { + SysUser sysUser = userService.selectUserByEmail(registerBody.getEmail()); + if (sysUser != null) { + throw new ServiceException("该邮箱已绑定用户"); + } else { + sendCode(registerBody.getEmail(), RandomCodeUtil.numberCode(6), OauthVerificationUse.REGISTER); + } + } + + @Override + public void doRegisterVerify(RegisterBody registerBody) { + if (checkCode(registerBody.getEmail(), registerBody.getCode(), OauthVerificationUse.REGISTER)) { + SysUser sysUser = new SysUser(); + sysUser.setUserName(registerBody.getEmail()); + sysUser.setNickName(registerBody.getEmail()); + sysUser.setPassword(SecurityUtils.encryptPassword(registerBody.getPassword())); + sysUser.setEmail(registerBody.getEmail()); + userService.registerUser(sysUser); + AsyncManager.me().execute(AsyncFactory.recordLogininfor(sysUser.getUserName(), Constants.REGISTER, + MessageUtils.message("user.register.success"))); + } else { + throw new ServiceException("验证码错误"); + } + } + + public void doReset(RegisterBody registerBody) { + SysUser sysUser = userService.selectUserByEmail(registerBody.getEmail()); + if (sysUser == null) { + throw new ServiceException("该邮箱未绑定用户"); + } else { + sendCode(registerBody.getEmail(), RandomCodeUtil.numberCode(6), OauthVerificationUse.RESET); + } + } + + public void doResetVerify(RegisterBody registerBody) { + if (checkCode(registerBody.getEmail(), registerBody.getCode(), OauthVerificationUse.RESET)) { + SysUser sysUser = userService.selectUserById(SecurityUtils.getUserId()); + sysUser.setEmail(registerBody.getEmail()); + userService.updateUser(sysUser); + } else { + throw new ServiceException("验证码错误"); + } + } + + @Override + public void doBind(LoginBody loginBody) { + SysUser sysUser = userService.selectUserByEmail(loginBody.getEmail()); + if (sysUser != null) { + throw new ServiceException("该邮箱已绑定用户"); + } + sysUser = userService.selectUserById(SecurityUtils.getUserId()); + if (!SecurityUtils.matchesPassword(loginBody.getPassword(), sysUser.getPassword())) { + throw new ServiceException("密码错误"); + } + sendCode(loginBody.getEmail(), RandomCodeUtil.numberCode(6), OauthVerificationUse.BIND); + } + + @Override + public void doBindVerify(LoginBody loginBody) { + if (checkCode(loginBody.getEmail(), loginBody.getCode(), OauthVerificationUse.BIND)) { + SysUser sysUser = userService.selectUserById(SecurityUtils.getUserId()); + if (!SecurityUtils.matchesPassword(loginBody.getPassword(), sysUser.getPassword())) { + throw new ServiceException("密码错误"); + } + sysUser.setEmail(loginBody.getEmail()); + userService.updateUser(sysUser); + } else { + throw new ServiceException("验证码错误"); + } + } + +} diff --git a/boyue-auth/boyue-tfa-email/src/main/java/com/boyue/tfa/email/utils/EmailUtil.java b/boyue-auth/boyue-tfa-email/src/main/java/com/boyue/tfa/email/utils/EmailUtil.java new file mode 100644 index 0000000..2c0afa0 --- /dev/null +++ b/boyue-auth/boyue-tfa-email/src/main/java/com/boyue/tfa/email/utils/EmailUtil.java @@ -0,0 +1,25 @@ +package com.boyue.tfa.email.utils; + +import org.springframework.boot.autoconfigure.mail.MailProperties; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSenderImpl; + +import com.boyue.common.exception.ServiceException; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.spring.SpringUtils; + +public class EmailUtil { + public static void sendMessage(String email, String title, String message) { + if (StringUtils.isEmpty(email)) { + throw new ServiceException("邮箱不能为空"); + } + MailProperties mailProperties = SpringUtils.getBean(MailProperties.class); + JavaMailSenderImpl mailSender = SpringUtils.getBean(JavaMailSenderImpl.class); + SimpleMailMessage simpleMailMessage = new SimpleMailMessage(); + simpleMailMessage.setSubject(title); + simpleMailMessage.setText(message); + simpleMailMessage.setFrom(mailProperties.getUsername()); + simpleMailMessage.setTo(email); + mailSender.send(simpleMailMessage); + } +} diff --git a/boyue-auth/boyue-tfa-phone/pom.xml b/boyue-auth/boyue-tfa-phone/pom.xml new file mode 100644 index 0000000..2f416ea --- /dev/null +++ b/boyue-auth/boyue-tfa-phone/pom.xml @@ -0,0 +1,33 @@ + + + + boyue-auth + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-tfa-phone + + + 手机号认证模块 + + + + + + + com.boyue + boyue-auth-common + + + + com.aliyun + dysmsapi20170525 + + + + + \ No newline at end of file diff --git a/boyue-auth/boyue-tfa-phone/src/main/java/com/boyue/tfa/phone/config/DySmsConfig.java b/boyue-auth/boyue-tfa-phone/src/main/java/com/boyue/tfa/phone/config/DySmsConfig.java new file mode 100644 index 0000000..48f5b7d --- /dev/null +++ b/boyue-auth/boyue-tfa-phone/src/main/java/com/boyue/tfa/phone/config/DySmsConfig.java @@ -0,0 +1,47 @@ +package com.boyue.tfa.phone.config; + +import java.util.Map; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import com.boyue.tfa.phone.domain.DySmsTemplate; + +/** + * 手机号认证数据 + * + * @author Dftre + * @date 2024-04-16 + */ +@Configuration +@ConfigurationProperties("tfa.phone.dysms") +public class DySmsConfig { + private String accessKeyId; + private String accessKeySecret; + private Map template; + + public String getAccessKeyId() { + return accessKeyId; + } + + public void setAccessKeyId(String accessKeyId) { + this.accessKeyId = accessKeyId; + } + + public String getAccessKeySecret() { + return accessKeySecret; + } + + public void setAccessKeySecret(String accessKeySecret) { + this.accessKeySecret = accessKeySecret; + } + + public Map getTemplate() { + return template; + } + + public void setTemplate(Map template) { + this.template = template; + } + +} \ No newline at end of file diff --git a/boyue-auth/boyue-tfa-phone/src/main/java/com/boyue/tfa/phone/domain/DySmsTemplate.java b/boyue-auth/boyue-tfa-phone/src/main/java/com/boyue/tfa/phone/domain/DySmsTemplate.java new file mode 100644 index 0000000..a4c0b88 --- /dev/null +++ b/boyue-auth/boyue-tfa-phone/src/main/java/com/boyue/tfa/phone/domain/DySmsTemplate.java @@ -0,0 +1,46 @@ +package com.boyue.tfa.phone.domain; + +/** + * 手机号认证短信模板 + * + * @author Dftre + * @date 2024-04-16 + */ +public class DySmsTemplate { + /** + * 短信模板编码 + */ + private String templateCode; + /** + * 签名 + */ + private String signName; + /** + * 短信模板必需的数据名称,多个key以逗号分隔,此处配置作为校验 + */ + private String keys; + + public String getTemplateCode() { + return templateCode; + } + + public void setTemplateCode(String templateCode) { + this.templateCode = templateCode; + } + + public String getSignName() { + return signName; + } + + public void setSignName(String signName) { + this.signName = signName; + } + + public String getKeys() { + return keys; + } + + public void setKeys(String keys) { + this.keys = keys; + } +} diff --git a/boyue-auth/boyue-tfa-phone/src/main/java/com/boyue/tfa/phone/service/DySmsService.java b/boyue-auth/boyue-tfa-phone/src/main/java/com/boyue/tfa/phone/service/DySmsService.java new file mode 100644 index 0000000..3c1f6aa --- /dev/null +++ b/boyue-auth/boyue-tfa-phone/src/main/java/com/boyue/tfa/phone/service/DySmsService.java @@ -0,0 +1,14 @@ +package com.boyue.tfa.phone.service; + +import com.boyue.auth.common.service.OauthVerificationCodeService; +import com.boyue.auth.common.service.TfaService; + +/** + * 手机号认证Servcie + * + * @author zlh + * @date 2024-04-16 + */ +public interface DySmsService extends OauthVerificationCodeService, TfaService { + +} diff --git a/boyue-auth/boyue-tfa-phone/src/main/java/com/boyue/tfa/phone/service/Impl/DySmsServiceImpl.java b/boyue-auth/boyue-tfa-phone/src/main/java/com/boyue/tfa/phone/service/Impl/DySmsServiceImpl.java new file mode 100644 index 0000000..5a514fe --- /dev/null +++ b/boyue-auth/boyue-tfa-phone/src/main/java/com/boyue/tfa/phone/service/Impl/DySmsServiceImpl.java @@ -0,0 +1,185 @@ +package com.boyue.tfa.phone.service.Impl; + +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.alibaba.fastjson2.JSONObject; +import com.boyue.auth.common.enums.OauthVerificationUse; +import com.boyue.auth.common.utils.RandomCodeUtil; +import com.boyue.common.constant.CacheConstants; +import com.boyue.common.constant.Constants; +import com.boyue.common.core.domain.entity.SysUser; +import com.boyue.common.core.domain.model.LoginBody; +import com.boyue.common.core.domain.model.LoginUser; +import com.boyue.common.core.domain.model.RegisterBody; +import com.boyue.common.exception.ServiceException; +import com.boyue.common.utils.CacheUtils; +import com.boyue.common.utils.MessageUtils; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.framework.manager.AsyncManager; +import com.boyue.framework.manager.factory.AsyncFactory; +import com.boyue.framework.web.service.SysLoginService; +import com.boyue.framework.web.service.TokenService; +import com.boyue.framework.web.service.UserDetailsServiceImpl; +import com.boyue.system.service.ISysUserService; +import com.boyue.tfa.phone.config.DySmsConfig; +import com.boyue.tfa.phone.service.DySmsService; +import com.boyue.tfa.phone.utils.DySmsUtil; + +/** + * 手机号认证Servcie + * + * @author zlh + * @date 2024-04-16 + */ +@Service("auth:service:dySms") +public class DySmsServiceImpl implements DySmsService { + + @Autowired + private ISysUserService userService; + @Autowired + private UserDetailsServiceImpl userDetailsServiceImpl; + @Autowired + private TokenService tokenService; + @Autowired + private SysLoginService sysLoginService; + @Autowired + private DySmsConfig dySmsConfig; + + private static final Logger log = LoggerFactory.getLogger(DySmsServiceImpl.class); + + @Override + public boolean sendCode(String phone, String code, OauthVerificationUse use) { + if (CacheUtils.hasKey(CacheConstants.PHONE_CODES, use.getValue() + phone)) { + throw new ServiceException("当前验证码未失效,请在1分钟后再发送"); + } + + try { + JSONObject templateParams = new JSONObject(); + templateParams.put("code", code); + DySmsUtil.sendSms(phone, dySmsConfig.getTemplate().get("VerificationCode"), templateParams); + CacheUtils.put(CacheConstants.PHONE_CODES, use.getValue() + phone, code, 1, TimeUnit.MINUTES); + log.info("发送手机验证码成功:{ phone: " + phone + " code:" + code + "}"); + return true; + } catch (Exception e) { + throw new ServiceException("发送手机验证码异常:" + phone); + } + } + + @Override + public boolean checkCode(String phone, String code, OauthVerificationUse use) { + if (StringUtils.isEmpty(code)) { + return false; + } + String cachedCode = CacheUtils.get(CacheConstants.PHONE_CODES, use.getValue() + phone, String.class); // 从缓存中获取验证码 + boolean isValid = code.equals(cachedCode); + if (isValid) { + CacheUtils.remove(CacheConstants.PHONE_CODES, use.getValue() + phone); + } + return isValid; + } + + @Override + public void doLogin(LoginBody loginBody) { + SysUser sysUser = userService.selectUserByPhone(loginBody.getPhonenumber()); + if (sysUser == null) { + throw new ServiceException("该手机号未绑定用户"); + } else { + sendCode(loginBody.getPhonenumber(), RandomCodeUtil.numberCode(6), OauthVerificationUse.LOGIN); + } + } + + @Override + public String doLoginVerify(LoginBody loginBody) { + if (checkCode(loginBody.getPhonenumber(), loginBody.getCode(), OauthVerificationUse.LOGIN)) { + SysUser sysUser = userService.selectUserByPhone(loginBody.getPhonenumber()); + if (sysUser == null) { + throw new ServiceException("该手机号未绑定用户"); + } + AsyncManager.me().execute(AsyncFactory.recordLogininfor(sysUser.getUserName(), Constants.LOGIN_SUCCESS, + MessageUtils.message("user.login.success"))); + LoginUser loginUser = (LoginUser) userDetailsServiceImpl.createLoginUser(sysUser); + sysLoginService.recordLoginInfo(loginUser.getUserId()); + return tokenService.createToken(loginUser); + } else { + throw new ServiceException("验证码错误"); + } + } + + @Override + public void doRegister(RegisterBody registerBody) { + SysUser sysUser = userService.selectUserByPhone(registerBody.getPhonenumber()); + if (sysUser != null) { + throw new ServiceException("该手机号已绑定用户"); + } else { + sendCode(registerBody.getPhonenumber(), RandomCodeUtil.numberCode(6), OauthVerificationUse.REGISTER); + } + } + + @Override + public void doRegisterVerify(RegisterBody registerBody) { + if (checkCode(registerBody.getPhonenumber(), registerBody.getCode(), OauthVerificationUse.REGISTER)) { + SysUser sysUser = new SysUser(); + sysUser.setUserName(registerBody.getPhonenumber()); + sysUser.setNickName(registerBody.getUsername()); + sysUser.setPassword(SecurityUtils.encryptPassword(registerBody.getPassword())); + sysUser.setPhonenumber(registerBody.getPhonenumber()); + userService.registerUser(sysUser); + AsyncManager.me().execute(AsyncFactory.recordLogininfor(sysUser.getUserName(), Constants.REGISTER, + MessageUtils.message("user.register.success"))); + } else { + throw new ServiceException("验证码错误"); + } + } + + public void doReset(RegisterBody registerBody) { + SysUser sysUser = userService.selectUserByPhone(registerBody.getPhonenumber()); + if (sysUser == null) { + throw new ServiceException("该手机号未绑定用户"); + } else { + sendCode(registerBody.getPhonenumber(), RandomCodeUtil.numberCode(6), OauthVerificationUse.RESET); + } + } + + public void doResetVerify(RegisterBody registerBody) { + if (checkCode(registerBody.getPhonenumber(), registerBody.getCode(), OauthVerificationUse.RESET)) { + SysUser sysUser = userService.selectUserById(SecurityUtils.getUserId()); + sysUser.setPhonenumber(registerBody.getPhonenumber()); + userService.updateUser(sysUser); + } else { + throw new ServiceException("验证码错误"); + } + } + + @Override + public void doBind(LoginBody loginBody) { + SysUser sysUser = userService.selectUserByPhone(loginBody.getPhonenumber()); + if (sysUser != null) { + throw new ServiceException("该手机号已绑定用户"); + } + sysUser = userService.selectUserById(SecurityUtils.getUserId()); + if (!SecurityUtils.matchesPassword(loginBody.getPassword(), sysUser.getPassword())) { + throw new ServiceException("密码错误"); + } + sendCode(loginBody.getPhonenumber(), RandomCodeUtil.numberCode(6), OauthVerificationUse.BIND); + } + + @Override + public void doBindVerify(LoginBody loginBody) { + if (checkCode(loginBody.getPhonenumber(), loginBody.getCode(), OauthVerificationUse.BIND)) { + SysUser sysUser = userService.selectUserById(SecurityUtils.getUserId()); + if (!SecurityUtils.matchesPassword(loginBody.getPassword(), sysUser.getPassword())) { + throw new ServiceException("密码错误"); + } + sysUser.setPhonenumber(loginBody.getPhonenumber()); + userService.updateUser(sysUser); + } else { + throw new ServiceException("验证码错误"); + } + } +} diff --git a/boyue-auth/boyue-tfa-phone/src/main/java/com/boyue/tfa/phone/utils/DySmsUtil.java b/boyue-auth/boyue-tfa-phone/src/main/java/com/boyue/tfa/phone/utils/DySmsUtil.java new file mode 100644 index 0000000..b3a2455 --- /dev/null +++ b/boyue-auth/boyue-tfa-phone/src/main/java/com/boyue/tfa/phone/utils/DySmsUtil.java @@ -0,0 +1,88 @@ +package com.boyue.tfa.phone.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.fastjson2.JSONObject; +import com.aliyun.dysmsapi20170525.Client; +import com.aliyun.dysmsapi20170525.models.SendSmsRequest; +import com.aliyun.dysmsapi20170525.models.SendSmsResponse; +import com.aliyun.tea.TeaException; +import com.aliyun.teaopenapi.models.Config; +import com.aliyun.teautil.Common; +import com.aliyun.teautil.models.RuntimeOptions; +import com.boyue.common.exception.ServiceException; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.spring.SpringUtils; +import com.boyue.tfa.phone.config.DySmsConfig; +import com.boyue.tfa.phone.domain.DySmsTemplate; + +public class DySmsUtil { + protected final static Logger logger = LoggerFactory.getLogger(DySmsUtil.class); + + /** + * 使用AK&SK初始化账号Client + * + * @param accessKeyId + * @param accessKeySecret + * @return Client + * @throws Exception + */ + private static Client createClient() throws Exception { + DySmsConfig dySmsConfig = SpringUtils.getBean(DySmsConfig.class); + Config config = new Config() + // 必填,您的 AccessKey ID + .setAccessKeyId(dySmsConfig.getAccessKeyId()) + // 必填,您的 AccessKey Secret + .setAccessKeySecret(dySmsConfig.getAccessKeySecret()); + config.endpoint = "dysmsapi.aliyuncs.com"; + return new Client(config); + } + + /** + * 验证参数 + * + * @param templateParamJson + * @param dySmsTemplate + * @throws Exception + */ + private static void validateParam(JSONObject templateParamJson, DySmsTemplate dySmsTemplate) { + String keys = dySmsTemplate.getKeys(); + String[] keyArr = keys.split(","); + for (String item : keyArr) { + if (!templateParamJson.containsKey(item)) { + throw new RuntimeException("模板缺少参数:" + item); + } + } + } + + public static void sendSms(String phone, DySmsTemplate dySmsTemplate, JSONObject templateParamJson) + throws Exception { + if (StringUtils.isEmpty(phone)) { + throw new ServiceException("手机号不能为空"); + } + validateParam(templateParamJson, dySmsTemplate); + Client client = createClient(); + SendSmsRequest sendSmsRequest = new SendSmsRequest() + .setPhoneNumbers(phone) + .setSignName(dySmsTemplate.getSignName()) + .setTemplateCode(dySmsTemplate.getTemplateCode()) + .setTemplateParam(templateParamJson.toJSONString()); + try { + // 复制代码运行请自行打印 API 的返回值 + SendSmsResponse sendSmsResponse = client.sendSmsWithOptions(sendSmsRequest, new RuntimeOptions()); + if ("OK".equals(sendSmsResponse.getBody().getCode())) { + logger.info("短信接口返回的数据--- {}", sendSmsResponse.getBody().getMessage()); + } else { + logger.error("短信接口返回的数据--- {}", sendSmsResponse.getBody().getMessage()); + throw new ServiceException(sendSmsResponse.getBody().getMessage()); + } + } catch (TeaException error) { + // 错误 message + System.out.println(error.getMessage()); + // 诊断地址 + System.out.println(error.getData().get("Recommend")); + Common.assertAsString(error.message); + } + } +} \ No newline at end of file diff --git a/boyue-auth/boyue-tfa-phone/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/boyue-auth/boyue-tfa-phone/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000..349dd9c --- /dev/null +++ b/boyue-auth/boyue-tfa-phone/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,19 @@ +{ + "properties": [ + { + "name": "tfa.phone.dysms.access-key-id", + "type": "java.lang.String", + "description": "阿里云短信 AccessKey ID" + }, + { + "name": "tfa.phone.dysms.access-key-secret", + "type": "java.lang.String", + "description": "阿里云短信 AccessKey Secret" + }, + { + "name": "tfa.phone.dysms.template", + "type": "java.util.Map", + "description": "短信模板" + } + ] +} \ No newline at end of file diff --git a/boyue-auth/pom.xml b/boyue-auth/pom.xml new file mode 100644 index 0000000..c19aefc --- /dev/null +++ b/boyue-auth/pom.xml @@ -0,0 +1,118 @@ + + + + boyuehasfj-java + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-auth + + + 1.16.7 + 3.7.4.ALL + 3.1.1 + 2.0.1 + + + + 第三方认证模块 + + + + + + com.boyue + boyue-auth-common + ${boyue.version} + + + + jakarta.mail + jakarta.mail-api + ${mail.version} + + + + com.sun.mail + jakarta.mail + ${mail.version} + + + + + me.zhyd.oauth + JustAuth + ${justauth.version} + + + + + com.alipay.sdk + alipay-sdk-java + ${alipay.version} + + + commons-logging + commons-logging + + + + + + + com.aliyun + dysmsapi20170525 + ${dysmsapi.version} + + + + + com.boyue + boyue-oauth-justauth + ${boyue.version} + + + + + com.boyue + boyue-oauth-wx + ${boyue.version} + + + + + com.boyue + boyue-tfa-phone + ${boyue.version} + + + + + com.boyue + boyue-tfa-email + ${boyue.version} + + + + + com.boyue + boyue-auth-starter + ${boyue.version} + + + + + + + boyue-auth-common + boyue-oauth-justauth + boyue-oauth-wx + boyue-tfa-phone + boyue-tfa-email + boyue-auth-starter + + pom + \ No newline at end of file diff --git a/boyue-common/pom.xml b/boyue-common/pom.xml new file mode 100644 index 0000000..d1c753f --- /dev/null +++ b/boyue-common/pom.xml @@ -0,0 +1,146 @@ + + + + boyuehasfj-java + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-common + + + common通用工具 + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-security + + + + + com.github.pagehelper + pagehelper-spring-boot-starter + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + org.apache.commons + commons-lang3 + + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + + + com.alibaba.fastjson2 + fastjson2 + + + + + commons-io + commons-io + + + + + org.apache.poi + poi-ooxml + + + + + io.jsonwebtoken + jjwt-api + + + + io.jsonwebtoken + jjwt-impl + + + + io.jsonwebtoken + jjwt-jackson + + + + + jakarta.xml.bind + jakarta.xml.bind-api + + + com.sun.xml.bind + jaxb-core + + + + + org.apache.commons + commons-pool2 + + + + + org.apache.httpcomponents + httpclient + + + + + eu.bitwalker + UserAgentUtils + + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + + + + + org.projectlombok + lombok + + + + + org.springframework.boot + spring-boot-starter-cache + + + + javax.cache + cache-api + + + + diff --git a/boyue-common/src/main/java/com/boyue/common/annotation/Anonymous.java b/boyue-common/src/main/java/com/boyue/common/annotation/Anonymous.java new file mode 100644 index 0000000..ccadce6 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/annotation/Anonymous.java @@ -0,0 +1,19 @@ +package com.boyue.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 匿名访问不鉴权注解 + * + * @author boyue + */ +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Anonymous +{ +} diff --git a/boyue-common/src/main/java/com/boyue/common/annotation/DataScope.java b/boyue-common/src/main/java/com/boyue/common/annotation/DataScope.java new file mode 100644 index 0000000..5307a25 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/annotation/DataScope.java @@ -0,0 +1,33 @@ +package com.boyue.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 数据权限过滤注解 + * + * @author boyue + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DataScope +{ + /** + * 部门表的别名 + */ + public String deptAlias() default ""; + + /** + * 用户表的别名 + */ + public String userAlias() default ""; + + /** + * 权限字符(用于多个角色匹配符合要求的权限)默认根据权限注解@ss获取,多个权限用逗号分隔开来 + */ + public String permission() default ""; +} diff --git a/boyue-common/src/main/java/com/boyue/common/annotation/DataSource.java b/boyue-common/src/main/java/com/boyue/common/annotation/DataSource.java new file mode 100644 index 0000000..fc9e348 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/annotation/DataSource.java @@ -0,0 +1,34 @@ +package com.boyue.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.boyue.common.enums.DataSourceType; + +/** + * 自定义多数据源切换注解 + * + * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准 + * + * @author boyue + */ +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface DataSource { + + /** + * 切换数据源名称 - 枚举方式 + */ + public DataSourceType value() default DataSourceType.MASTER; + + /** + * 切换数据源名称 - 字符串方式 + */ + public String name() default ""; +} diff --git a/boyue-common/src/main/java/com/boyue/common/annotation/Excel.java b/boyue-common/src/main/java/com/boyue/common/annotation/Excel.java new file mode 100644 index 0000000..57652e4 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/annotation/Excel.java @@ -0,0 +1,206 @@ +package com.boyue.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.math.RoundingMode; + +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IndexedColors; + +import com.boyue.common.utils.poi.ExcelHandlerAdapter; + +/** + * 自定义导出Excel数据注解 + * + * @author boyue + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Excel { + /** + * 导出时在excel中排序 + */ + public int sort() default Integer.MAX_VALUE; + + /** + * 导出到Excel中的名字. + */ + public String name() default ""; + + /** + * 日期格式, 如: yyyy-MM-dd + */ + public String dateFormat() default ""; + + /** + * 如果是字典类型,请设置字典的type值 (如: sys_user_sex) + */ + public String dictType() default ""; + + /** + * 读取内容转表达式 (如: 0=男,1=女,2=未知) + */ + public String readConverterExp() default ""; + + /** + * 分隔符,读取字符串组内容 + */ + public String separator() default ","; + + /** + * BigDecimal 精度 默认:-1(默认不开启BigDecimal格式化) + */ + public int scale() default -1; + + /** + * BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN + */ + public RoundingMode roundingMode() default RoundingMode.HALF_EVEN; + + /** + * 导出时在excel中每个列的高度 + */ + public double height() default 14; + + /** + * 导出时在excel中每个列的宽度 + */ + public double width() default 16; + + /** + * 文字后缀,如% 90 变成90% + */ + public String suffix() default ""; + + /** + * 当值为空时,字段的默认值 + */ + public String defaultValue() default ""; + + /** + * 提示信息 + */ + public String prompt() default ""; + + /** + * 是否允许内容换行 + */ + public boolean wrapText() default false; + + /** + * 设置只能选择不能输入的列内容. + */ + public String[] combo() default {}; + + /** + * 是否从字典读数据到combo,默认不读取,如读取需要设置dictType注解. + */ + public boolean comboReadDict() default false; + + /** + * 是否需要纵向合并单元格,应对需求:含有list集合单元格) + */ + public boolean needMerge() default false; + + /** + * 是否导出数据,应对需求:有时我们需要导出一份模板,这是标题需要但内容需要用户手工填写. + */ + public boolean isExport() default true; + + /** + * 另一个类中的属性名称,支持多级获取,以小数点隔开 + */ + public String targetAttr() default ""; + + /** + * 是否自动统计数据,在最后追加一行统计数据总和 + */ + public boolean isStatistics() default false; + + /** + * 导出类型(0数字 1字符串 2图片) + */ + public ColumnType cellType() default ColumnType.STRING; + + /** + * 导出列头背景颜色 + */ + public IndexedColors headerBackgroundColor() default IndexedColors.GREY_50_PERCENT; + + /** + * 导出列头字体颜色 + */ + public IndexedColors headerColor() default IndexedColors.WHITE; + + /** + * 导出单元格背景颜色 + */ + public IndexedColors backgroundColor() default IndexedColors.WHITE; + + /** + * 导出单元格字体颜色 + */ + public IndexedColors color() default IndexedColors.BLACK; + + /** + * 导出字段对齐方式 + */ + public HorizontalAlignment align() default HorizontalAlignment.CENTER; + + /** + * 自定义数据处理器 + */ + public Class handler() default ExcelHandlerAdapter.class; + + /** + * 自定义数据处理器参数 + */ + public String[] args() default {}; + + /** + * 字段类型(0:导出导入;1:仅导出;2:仅导入) + */ + Type type() default Type.ALL; + + public enum Type { + /** 导出或导入 */ + ALL(0), + /** 仅导出 */ + EXPORT(1), + /** 仅导入 */ + IMPORT(2); + + private final int value; + + Type(int value) { + this.value = value; + } + + public int value() { + return this.value; + } + } + + public enum ColumnType { + /** 数字 */ + NUMERIC(0), + /** 字符串 */ + STRING(1), + /** 图片 */ + IMAGE(2), + /** 文本 */ + TEXT(3); + + private final int value; + + ColumnType(int value) { + this.value = value; + } + + public int value() { + return this.value; + } + } +} \ No newline at end of file diff --git a/boyue-common/src/main/java/com/boyue/common/annotation/Excels.java b/boyue-common/src/main/java/com/boyue/common/annotation/Excels.java new file mode 100644 index 0000000..c21585a --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/annotation/Excels.java @@ -0,0 +1,18 @@ +package com.boyue.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Excel注解集 + * + * @author boyue + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Excels +{ + public Excel[] value(); +} diff --git a/boyue-common/src/main/java/com/boyue/common/annotation/Log.java b/boyue-common/src/main/java/com/boyue/common/annotation/Log.java new file mode 100644 index 0000000..f603617 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/annotation/Log.java @@ -0,0 +1,51 @@ +package com.boyue.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import com.boyue.common.enums.BusinessType; +import com.boyue.common.enums.OperatorType; + +/** + * 自定义操作日志记录注解 + * + * @author boyue + * + */ +@Target({ ElementType.PARAMETER, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Log +{ + /** + * 模块 + */ + public String title() default ""; + + /** + * 功能 + */ + public BusinessType businessType() default BusinessType.OTHER; + + /** + * 操作人类别 + */ + public OperatorType operatorType() default OperatorType.MANAGE; + + /** + * 是否保存请求的参数 + */ + public boolean isSaveRequestData() default true; + + /** + * 是否保存响应的参数 + */ + public boolean isSaveResponseData() default true; + + /** + * 排除指定的请求参数 + */ + public String[] excludeParamNames() default {}; +} diff --git a/boyue-common/src/main/java/com/boyue/common/annotation/RateLimiter.java b/boyue-common/src/main/java/com/boyue/common/annotation/RateLimiter.java new file mode 100644 index 0000000..e1457d7 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/annotation/RateLimiter.java @@ -0,0 +1,41 @@ +package com.boyue.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.boyue.common.constant.CacheConstants; +import com.boyue.common.enums.LimitType; + +/** + * 限流注解 + * + * @author boyue + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RateLimiter +{ + /** + * 限流key + */ + public String key() default CacheConstants.RATE_LIMIT_KEY; + + /** + * 限流时间,单位秒 + */ + public int time() default 60; + + /** + * 限流次数 + */ + public int count() default 100; + + /** + * 限流类型 + */ + public LimitType limitType() default LimitType.DEFAULT; +} diff --git a/boyue-common/src/main/java/com/boyue/common/annotation/RepeatSubmit.java b/boyue-common/src/main/java/com/boyue/common/annotation/RepeatSubmit.java new file mode 100644 index 0000000..90edfa3 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/annotation/RepeatSubmit.java @@ -0,0 +1,31 @@ +package com.boyue.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义注解防止表单重复提交 + * + * @author boyue + * + */ +@Inherited +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RepeatSubmit +{ + /** + * 间隔时间(ms),小于此时间视为重复提交 + */ + public int interval() default 5000; + + /** + * 提示消息 + */ + public String message() default "不允许重复提交,请稍候再试"; +} diff --git a/boyue-common/src/main/java/com/boyue/common/annotation/Sensitive.java b/boyue-common/src/main/java/com/boyue/common/annotation/Sensitive.java new file mode 100644 index 0000000..e166ce6 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/annotation/Sensitive.java @@ -0,0 +1,25 @@ +package com.boyue.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.boyue.common.config.serializer.SensitiveJsonSerializer; +import com.boyue.common.enums.DesensitizedType; + +/** + * 数据脱敏注解 + * + * @author boyue + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +@JacksonAnnotationsInside +@JsonSerialize(using = SensitiveJsonSerializer.class) +public @interface Sensitive +{ + DesensitizedType desensitizedType(); +} \ No newline at end of file diff --git a/boyue-common/src/main/java/com/boyue/common/config/BoYueConfig.java b/boyue-common/src/main/java/com/boyue/common/config/BoYueConfig.java new file mode 100644 index 0000000..5a55d34 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/config/BoYueConfig.java @@ -0,0 +1,135 @@ +package com.boyue.common.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 读取项目相关配置 + * + * @author boyue + */ +@Component +@ConfigurationProperties(prefix = "boyue") +public class BoYueConfig +{ + /** 项目名称 */ + private String name; + + /** 版本 */ + private String version; + + /** 版权年份 */ + private String copyrightYear; + + /** 上传路径 */ + private static String profile; + + + + private static String fileServer; + + /** 获取地址开关 */ + private static boolean addressEnabled; + + /** 验证码类型 */ + private static String captchaType; + + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + public String getVersion() + { + return version; + } + + public void setVersion(String version) + { + this.version = version; + } + + public String getCopyrightYear() + { + return copyrightYear; + } + + public void setCopyrightYear(String copyrightYear) + { + this.copyrightYear = copyrightYear; + } + + public static String getProfile() + { + return profile; + } + + public void setProfile(String profile) + { + BoYueConfig.profile = profile; + } + + public static boolean isAddressEnabled() + { + return addressEnabled; + } + + public void setAddressEnabled(boolean addressEnabled) + { + BoYueConfig.addressEnabled = addressEnabled; + } + + public static String getCaptchaType() { + return captchaType; + } + + public void setCaptchaType(String captchaType) { + BoYueConfig.captchaType = captchaType; + } + + public static String getFileServer() { + return fileServer; + } + + public void setFileServer(String fileServer) { + BoYueConfig.fileServer = fileServer; + } + + /** + * 获取导入上传路径 + */ + public static String getImportPath() + { + return getProfile() + "/import"; + } + + /** + * 获取头像上传路径 + */ + public static String getAvatarPath() + { + return getProfile() + "/avatar"; + } + + /** + * 获取下载路径 + */ + public static String getDownloadPath() + { + return getProfile() + "/download/"; + } + + /** + * 获取上传路径 + */ + public static String getUploadPath() + { + return getProfile() + "/upload"; + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/config/serializer/SensitiveJsonSerializer.java b/boyue-common/src/main/java/com/boyue/common/config/serializer/SensitiveJsonSerializer.java new file mode 100644 index 0000000..43f0ad2 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/config/serializer/SensitiveJsonSerializer.java @@ -0,0 +1,68 @@ +package com.boyue.common.config.serializer; + +import java.io.IOException; +import java.util.Objects; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.ContextualSerializer; +import com.boyue.common.annotation.Sensitive; +import com.boyue.common.core.domain.model.LoginUser; +import com.boyue.common.enums.DesensitizedType; +import com.boyue.common.utils.SecurityUtils; + +/** + * 数据脱敏序列化过滤 + * + * @author boyue + */ +public class SensitiveJsonSerializer extends JsonSerializer implements ContextualSerializer +{ + private DesensitizedType desensitizedType; + + @Override + public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException + { + if (desensitization()) + { + gen.writeString(desensitizedType.desensitizer().apply(value)); + } + else + { + gen.writeString(value); + } + } + + @Override + public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) + throws JsonMappingException + { + Sensitive annotation = property.getAnnotation(Sensitive.class); + if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) + { + this.desensitizedType = annotation.desensitizedType(); + return this; + } + return prov.findValueSerializer(property.getType(), property); + } + + /** + * 是否需要脱敏处理 + */ + private boolean desensitization() + { + try + { + LoginUser securityUser = SecurityUtils.getLoginUser(); + // 管理员不脱敏 + return !securityUser.getUser().isAdmin(); + } + catch (Exception e) + { + return true; + } + } +} \ No newline at end of file diff --git a/boyue-common/src/main/java/com/boyue/common/constant/CacheConstants.java b/boyue-common/src/main/java/com/boyue/common/constant/CacheConstants.java new file mode 100644 index 0000000..9e2cbfd --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/constant/CacheConstants.java @@ -0,0 +1,68 @@ +package com.boyue.common.constant; + +/** + * 缓存的key 常量 + * + * @author boyue + */ +public class CacheConstants { + /** + * 登录用户 redis key + */ + public static final String LOGIN_TOKEN_KEY = "login_tokens"; + + /** + * 验证码 redis key + */ + public static final String CAPTCHA_CODE_KEY = "captcha_codes"; + + /** + * 参数管理 cache key + */ + public static final String SYS_CONFIG_KEY = "sys_config"; + + /** + * 字典管理 cache key + */ + public static final String SYS_DICT_KEY = "sys_dict"; + + /** + * 防重提交 redis key + */ + public static final String REPEAT_SUBMIT_KEY = "repeat_submit"; + + /** + * 限流 redis key + */ + public static final String RATE_LIMIT_KEY = "rate_limit"; + + /** + * 登录账户密码错误次数 redis key + */ + public static final String PWD_ERR_CNT_KEY = "pwd_err_cnt"; + + /** + * 登录ip错误次数 redis key + */ + public static final String IP_ERR_CNT_KEY = "ip_err_cnt_key"; + + /** + * 手机号验证码 phone codes + */ + public static final String PHONE_CODES = "phone_codes"; + + /** + * 邮箱验证码 + */ + public static final String EMAIL_CODES = "email_codes"; + + /** + * 文件的md5 redis key + */ + public static final String FILE_MD5_PATH_KEY = "file_md5_path"; + + /** + * 文件路径 redis key + */ + public static final String FILE_PATH_MD5_KEY = "file_path_md5"; +} diff --git a/boyue-common/src/main/java/com/boyue/common/constant/Constants.java b/boyue-common/src/main/java/com/boyue/common/constant/Constants.java new file mode 100644 index 0000000..f606c92 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/constant/Constants.java @@ -0,0 +1,175 @@ +package com.boyue.common.constant; + +import java.util.Locale; + +import io.jsonwebtoken.Claims; + +/** + * 通用常量信息 + * + * @author boyue + */ +public class Constants { + /** + * UTF-8 字符集 + */ + public static final String UTF8 = "UTF-8"; + + /** + * GBK 字符集 + */ + public static final String GBK = "GBK"; + + /** + * 系统语言 + */ + public static final Locale DEFAULT_LOCALE = Locale.SIMPLIFIED_CHINESE; + + /** + * www主域 + */ + public static final String WWW = "www."; + + /** + * http请求 + */ + public static final String HTTP = "http://"; + + /** + * https请求 + */ + public static final String HTTPS = "https://"; + + /** + * 通用成功标识 + */ + public static final String SUCCESS = "0"; + + /** + * 通用失败标识 + */ + public static final String FAIL = "1"; + + /** + * 登录成功 + */ + public static final String LOGIN_SUCCESS = "Success"; + + /** + * 注销 + */ + public static final String LOGOUT = "Logout"; + + /** + * 注册 + */ + public static final String REGISTER = "Register"; + + /** + * 登录失败 + */ + public static final String LOGIN_FAIL = "Error"; + + /** + * 所有权限标识 + */ + public static final String ALL_PERMISSION = "*:*:*"; + + /** + * 管理员角色权限标识 + */ + public static final String SUPER_ADMIN = "admin"; + + /** + * 角色权限分隔符 + */ + public static final String ROLE_DELIMETER = ","; + + /** + * 权限标识分隔符 + */ + public static final String PERMISSION_DELIMETER = ","; + + /** + * 验证码有效期(分钟) + */ + public static final Integer CAPTCHA_EXPIRATION = 2; + + /** + * 令牌 + */ + public static final String TOKEN = "token"; + + /** + * 令牌前缀 + */ + public static final String TOKEN_PREFIX = "Bearer "; + + /** + * 令牌前缀 + */ + public static final String LOGIN_USER_KEY = "login_user_key"; + + /** + * 用户ID + */ + public static final String JWT_USERID = "userid"; + + /** + * 用户名称 + */ + public static final String JWT_USERNAME = Claims.SUBJECT; + + /** + * 用户头像 + */ + public static final String JWT_AVATAR = "avatar"; + + /** + * 创建时间 + */ + public static final String JWT_CREATED = "created"; + + /** + * 用户权限 + */ + public static final String JWT_AUTHORITIES = "authorities"; + + /** + * 资源映射路径 前缀 + */ + public static final String RESOURCE_PREFIX = "/profile"; + + /** + * RMI 远程方法调用 + */ + public static final String LOOKUP_RMI = "rmi:"; + + /** + * LDAP 远程方法调用 + */ + public static final String LOOKUP_LDAP = "ldap:"; + + /** + * LDAPS 远程方法调用 + */ + public static final String LOOKUP_LDAPS = "ldaps:"; + + /** + * 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全) + */ + public static final String[] JSON_WHITELIST_STR = { "org.springframework", "com.boyue" }; + + /** + * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加) + */ + public static final String[] JOB_WHITELIST_STR = { "com.boyue.quartz.task" }; + + /** + * 定时任务违规的字符 + */ + public static final String[] JOB_ERROR_STR = { + "java.net.URL", "javax.naming.InitialContext", + "org.yaml.snakeyaml", "org.springframework", "org.apache", + "com.boyue.common.utils.file", "com.boyue.common.config", "com.boyue.generator" }; +} diff --git a/boyue-common/src/main/java/com/boyue/common/constant/HttpStatus.java b/boyue-common/src/main/java/com/boyue/common/constant/HttpStatus.java new file mode 100644 index 0000000..6170da0 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/constant/HttpStatus.java @@ -0,0 +1,94 @@ +package com.boyue.common.constant; + +/** + * 返回状态码 + * + * @author boyue + */ +public class HttpStatus +{ + /** + * 操作成功 + */ + public static final int SUCCESS = 200; + + /** + * 对象创建成功 + */ + public static final int CREATED = 201; + + /** + * 请求已经被接受 + */ + public static final int ACCEPTED = 202; + + /** + * 操作已经执行成功,但是没有返回数据 + */ + public static final int NO_CONTENT = 204; + + /** + * 资源已被移除 + */ + public static final int MOVED_PERM = 301; + + /** + * 重定向 + */ + public static final int SEE_OTHER = 303; + + /** + * 资源没有被修改 + */ + public static final int NOT_MODIFIED = 304; + + /** + * 参数列表错误(缺少,格式不匹配) + */ + public static final int BAD_REQUEST = 400; + + /** + * 未授权 + */ + public static final int UNAUTHORIZED = 401; + + /** + * 访问受限,授权过期 + */ + public static final int FORBIDDEN = 403; + + /** + * 资源,服务未找到 + */ + public static final int NOT_FOUND = 404; + + /** + * 不允许的http方法 + */ + public static final int BAD_METHOD = 405; + + /** + * 资源冲突,或者资源被锁 + */ + public static final int CONFLICT = 409; + + /** + * 不支持的数据,媒体类型 + */ + public static final int UNSUPPORTED_TYPE = 415; + + /** + * 系统内部错误 + */ + public static final int ERROR = 500; + + /** + * 接口未实现 + */ + public static final int NOT_IMPLEMENTED = 501; + + /** + * 系统警告消息 + */ + public static final int WARN = 601; +} diff --git a/boyue-common/src/main/java/com/boyue/common/constant/ScheduleConstants.java b/boyue-common/src/main/java/com/boyue/common/constant/ScheduleConstants.java new file mode 100644 index 0000000..ff4f7b6 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/constant/ScheduleConstants.java @@ -0,0 +1,50 @@ +package com.boyue.common.constant; + +/** + * 任务调度通用常量 + * + * @author boyue + */ +public class ScheduleConstants +{ + public static final String TASK_CLASS_NAME = "TASK_CLASS_NAME"; + + /** 执行目标key */ + public static final String TASK_PROPERTIES = "TASK_PROPERTIES"; + + /** 默认 */ + public static final String MISFIRE_DEFAULT = "0"; + + /** 立即触发执行 */ + public static final String MISFIRE_IGNORE_MISFIRES = "1"; + + /** 触发一次执行 */ + public static final String MISFIRE_FIRE_AND_PROCEED = "2"; + + /** 不触发立即执行 */ + public static final String MISFIRE_DO_NOTHING = "3"; + + public enum Status + { + /** + * 正常 + */ + NORMAL("0"), + /** + * 暂停 + */ + PAUSE("1"); + + private String value; + + private Status(String value) + { + this.value = value; + } + + public String getValue() + { + return value; + } + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/constant/UserConstants.java b/boyue-common/src/main/java/com/boyue/common/constant/UserConstants.java new file mode 100644 index 0000000..f23c38a --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/constant/UserConstants.java @@ -0,0 +1,80 @@ +package com.boyue.common.constant; + +/** + * 用户常量信息 + * + * @author boyue + */ +public class UserConstants { + /** + * 平台内系统用户的唯一标志 + */ + public static final String SYS_USER = "SYS_USER"; + + /** 正常状态 */ + public static final String NORMAL = "0"; + + /** 异常状态 */ + public static final String EXCEPTION = "1"; + + /** 用户封禁状态 */ + public static final String USER_DISABLE = "1"; + + /** 角色正常状态 */ + public static final String ROLE_NORMAL = "0"; + + /** 角色封禁状态 */ + public static final String ROLE_DISABLE = "1"; + + /** 部门正常状态 */ + public static final String DEPT_NORMAL = "0"; + + /** 部门停用状态 */ + public static final String DEPT_DISABLE = "1"; + + /** 字典正常状态 */ + public static final String DICT_NORMAL = "0"; + + /** 是否为系统默认(是) */ + public static final String YES = "Y"; + + /** 是否菜单外链(是) */ + public static final String YES_FRAME = "0"; + + /** 是否菜单外链(否) */ + public static final String NO_FRAME = "1"; + + /** 菜单类型(目录) */ + public static final String TYPE_DIR = "M"; + + /** 菜单类型(菜单) */ + public static final String TYPE_MENU = "C"; + + /** 菜单类型(按钮) */ + public static final String TYPE_BUTTON = "F"; + + /** Layout组件标识 */ + public final static String LAYOUT = "Layout"; + + /** ParentView组件标识 */ + public final static String PARENT_VIEW = "ParentView"; + + /** InnerLink组件标识 */ + public final static String INNER_LINK = "InnerLink"; + + /** 校验是否唯一的返回标识 */ + public final static boolean UNIQUE = true; + public final static boolean NOT_UNIQUE = false; + + /** + * 用户名长度限制 + */ + public static final int USERNAME_MIN_LENGTH = 2; + public static final int USERNAME_MAX_LENGTH = 20; + + /** + * 密码长度限制 + */ + public static final int PASSWORD_MIN_LENGTH = 5; + public static final int PASSWORD_MAX_LENGTH = 20; +} diff --git a/boyue-common/src/main/java/com/boyue/common/core/controller/BaseController.java b/boyue-common/src/main/java/com/boyue/common/core/controller/BaseController.java new file mode 100644 index 0000000..a7cdec0 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/core/controller/BaseController.java @@ -0,0 +1,202 @@ +package com.boyue.common.core.controller; + +import java.beans.PropertyEditorSupport; +import java.util.Date; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.InitBinder; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import com.boyue.common.constant.HttpStatus; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.domain.model.LoginUser; +import com.boyue.common.core.page.PageDomain; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.core.page.TableSupport; +import com.boyue.common.utils.DateUtils; +import com.boyue.common.utils.PageUtils; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.sql.SqlUtil; + +/** + * web层通用数据处理 + * + * @author boyue + */ +public class BaseController +{ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * 将前台传递过来的日期格式的字符串,自动转化为Date类型 + */ + @InitBinder + public void initBinder(WebDataBinder binder) + { + // Date 类型转换 + binder.registerCustomEditor(Date.class, new PropertyEditorSupport() + { + @Override + public void setAsText(String text) + { + setValue(DateUtils.parseDate(text)); + } + }); + } + + /** + * 设置请求分页数据 + */ + protected void startPage() + { + PageUtils.startPage(); + } + + /** + * 设置请求排序数据 + */ + protected void startOrderBy() + { + PageDomain pageDomain = TableSupport.buildPageRequest(); + if (StringUtils.isNotEmpty(pageDomain.getOrderBy())) + { + String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy()); + PageHelper.orderBy(orderBy); + } + } + + /** + * 清理分页的线程变量 + */ + protected void clearPage() + { + PageUtils.clearPage(); + } + + /** + * 响应请求分页数据 + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + protected TableDataInfo getDataTable(List list) + { + TableDataInfo rspData = new TableDataInfo(); + rspData.setCode(HttpStatus.SUCCESS); + rspData.setMsg("查询成功"); + rspData.setRows(list); + rspData.setTotal(new PageInfo(list).getTotal()); + return rspData; + } + + /** + * 返回成功 + */ + public AjaxResult success() + { + return AjaxResult.success(); + } + + /** + * 返回失败消息 + */ + public AjaxResult error() + { + return AjaxResult.error(); + } + + /** + * 返回成功消息 + */ + public AjaxResult success(String message) + { + return AjaxResult.success(message); + } + + /** + * 返回成功消息 + */ + public AjaxResult success(Object data) + { + return AjaxResult.success(data); + } + + /** + * 返回失败消息 + */ + public AjaxResult error(String message) + { + return AjaxResult.error(message); + } + + /** + * 返回警告消息 + */ + public AjaxResult warn(String message) + { + return AjaxResult.warn(message); + } + + /** + * 响应返回结果 + * + * @param rows 影响行数 + * @return 操作结果 + */ + protected AjaxResult toAjax(int rows) + { + return rows > 0 ? AjaxResult.success() : AjaxResult.error(); + } + + /** + * 响应返回结果 + * + * @param result 结果 + * @return 操作结果 + */ + protected AjaxResult toAjax(boolean result) + { + return result ? success() : error(); + } + + /** + * 页面跳转 + */ + public String redirect(String url) + { + return StringUtils.format("redirect:{}", url); + } + + /** + * 获取用户缓存信息 + */ + public LoginUser getLoginUser() + { + return SecurityUtils.getLoginUser(); + } + + /** + * 获取登录用户id + */ + public Long getUserId() + { + return getLoginUser().getUserId(); + } + + /** + * 获取登录部门id + */ + public Long getDeptId() + { + return getLoginUser().getDeptId(); + } + + /** + * 获取登录用户名 + */ + public String getUsername() + { + return getLoginUser().getUsername(); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/core/domain/AjaxResult.java b/boyue-common/src/main/java/com/boyue/common/core/domain/AjaxResult.java new file mode 100644 index 0000000..ebba77d --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/core/domain/AjaxResult.java @@ -0,0 +1,185 @@ +package com.boyue.common.core.domain; + +import java.util.HashMap; +import com.boyue.common.constant.HttpStatus; +import com.boyue.common.utils.StringUtils; + +/** + * 操作消息提醒 + * + * @author boyue + */ +public class AjaxResult extends HashMap +{ + private static final long serialVersionUID = 1L; + + /** 状态码 */ + public static final String CODE_TAG = "code"; + + /** 返回内容 */ + public static final String MSG_TAG = "msg"; + + /** 数据对象 */ + public static final String DATA_TAG = "data"; + + /** + * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。 + */ + public AjaxResult() + { + } + + /** + * 初始化一个新创建的 AjaxResult 对象 + * + * @param code 状态码 + * @param msg 返回内容 + */ + public AjaxResult(int code, String msg) + { + super.put(CODE_TAG, code); + super.put(MSG_TAG, msg); + } + + /** + * 初始化一个新创建的 AjaxResult 对象 + * + * @param code 状态码 + * @param msg 返回内容 + * @param data 数据对象 + */ + public AjaxResult(int code, String msg, Object data) + { + super.put(CODE_TAG, code); + super.put(MSG_TAG, msg); + if (StringUtils.isNotNull(data)) + { + super.put(DATA_TAG, data); + } + } + + /** + * 返回成功消息 + * + * @return 成功消息 + */ + public static AjaxResult success() + { + return AjaxResult.success("操作成功"); + } + + /** + * 返回成功数据 + * + * @return 成功消息 + */ + public static AjaxResult success(Object data) + { + return AjaxResult.success("操作成功", data); + } + + /** + * 返回成功消息 + * + * @param msg 返回内容 + * @return 成功消息 + */ + public static AjaxResult success(String msg) + { + return AjaxResult.success(msg, null); + } + + /** + * 返回成功消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 成功消息 + */ + public static AjaxResult success(String msg, Object data) + { + return new AjaxResult(HttpStatus.SUCCESS, msg, data); + } + + /** + * 返回警告消息 + * + * @param msg 返回内容 + * @return 警告消息 + */ + public static AjaxResult warn(String msg) + { + return AjaxResult.warn(msg, null); + } + + /** + * 返回警告消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 警告消息 + */ + public static AjaxResult warn(String msg, Object data) + { + return new AjaxResult(HttpStatus.WARN, msg, data); + } + + /** + * 返回错误消息 + * + * @return 错误消息 + */ + public static AjaxResult error() + { + return AjaxResult.error("操作失败"); + } + + /** + * 返回错误消息 + * + * @param msg 返回内容 + * @return 错误消息 + */ + public static AjaxResult error(String msg) + { + return AjaxResult.error(msg, null); + } + + /** + * 返回错误消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 错误消息 + */ + public static AjaxResult error(String msg, Object data) + { + return new AjaxResult(HttpStatus.ERROR, msg, data); + } + + /** + * 返回错误消息 + * + * @param code 状态码 + * @param msg 返回内容 + * @return 错误消息 + */ + public static AjaxResult error(int code, String msg) + { + return new AjaxResult(code, msg, null); + } + + /** + * 方便链式调用 + * + * @param key 键 + * @param value 值 + * @return 数据对象 + */ + @Override + public AjaxResult put(String key, Object value) + { + super.put(key, value); + return this; + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/core/domain/BaseEntity.java b/boyue-common/src/main/java/com/boyue/common/core/domain/BaseEntity.java new file mode 100644 index 0000000..9d84ed5 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/core/domain/BaseEntity.java @@ -0,0 +1,129 @@ +package com.boyue.common.core.domain; + +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * Entity基类 + * + * @author boyue + */ +@Schema(title = "基类") +public class BaseEntity implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 搜索值 */ + @Schema(title = "搜索值") + @JsonIgnore + private String searchValue; + + /** 创建者 */ + @Schema(title = "创建者") + private String createBy; + + /** 创建时间 */ + @Schema(title = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + /** 更新者 */ + @Schema(title = "更新者") + private String updateBy; + + /** 更新时间 */ + @Schema(title = "更新时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + + /** 备注 */ + @Schema(title = "备注") + private String remark; + + /** 请求参数 */ + @Schema(title = "请求参数") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Map params; + + public String getSearchValue() + { + return searchValue; + } + + public void setSearchValue(String searchValue) + { + this.searchValue = searchValue; + } + + public String getCreateBy() + { + return createBy; + } + + public void setCreateBy(String createBy) + { + this.createBy = createBy; + } + + public Date getCreateTime() + { + return createTime; + } + + public void setCreateTime(Date createTime) + { + this.createTime = createTime; + } + + public String getUpdateBy() + { + return updateBy; + } + + public void setUpdateBy(String updateBy) + { + this.updateBy = updateBy; + } + + public Date getUpdateTime() + { + return updateTime; + } + + public void setUpdateTime(Date updateTime) + { + this.updateTime = updateTime; + } + + public String getRemark() + { + return remark; + } + + public void setRemark(String remark) + { + this.remark = remark; + } + + public Map getParams() + { + if (params == null) + { + params = new HashMap<>(); + } + return params; + } + + public void setParams(Map params) + { + this.params = params; + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/core/domain/Message.java b/boyue-common/src/main/java/com/boyue/common/core/domain/Message.java new file mode 100644 index 0000000..27dafc7 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/core/domain/Message.java @@ -0,0 +1,172 @@ +package com.boyue.common.core.domain; + +import java.time.Instant; +import java.util.Map; +import java.util.UUID; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +public class Message { + + /** 消息唯一标识符 */ + private String messageId; + /** 发送者标识 */ + private String sender; + /** 接收者标识 */ + private String receiver; + /** 消息时间戳 */ + private Instant timestamp; + /** 消息类型(如命令、聊天、日志、事件等) */ + private String type; + /** 消息主题或事件名称 */ + private String subject; + /** 消息数据负载 */ + private Map payload; + /** 元数据,用于存储额外的信息 */ + private Map metadata; + /** 消息状态(如成功、失败、重试等) */ + private String status; + /** 重试次数 */ + private int retryCount; + /** 最大重试次数 */ + private int maxRetries; + /** 重试间隔 */ + private String retryInterval; + + // 构造函数 + public Message() { + this.messageId = UUID.randomUUID().toString(); + this.timestamp = Instant.now(); + } + + public static Message create() { + return new Message(); + } + + public String getMessageId() { + return messageId; + } + + public Message setMessageId(String messageId) { + this.messageId = messageId; + return this; + } + + public String getSender() { + return sender; + } + + public Message setSender(String sender) { + this.sender = sender; + return this; + } + + public String getReceiver() { + return receiver; + } + + public Message setReceiver(String receiver) { + this.receiver = receiver; + return this; + } + + public Instant getTimestamp() { + return timestamp; + } + + public Message setTimestamp(Instant timestamp) { + this.timestamp = timestamp; + return this; + } + + public String getType() { + return type; + } + + public Message setType(String type) { + this.type = type; + return this; + } + + public String getSubject() { + return subject; + } + + public Message setSubject(String subject) { + this.subject = subject; + return this; + } + + public Map getPayload() { + return payload; + } + + public Message setPayload(Map payload) { + this.payload = payload; + return this; + } + + public Map getMetadata() { + return metadata; + } + + public Message setMetadata(Map metadata) { + this.metadata = metadata; + return this; + } + + public String getStatus() { + return status; + } + + public Message setStatus(String status) { + this.status = status; + return this; + } + + public int getRetryCount() { + return retryCount; + } + + public Message setRetryCount(int retryCount) { + this.retryCount = retryCount; + return this; + } + + public int getMaxRetries() { + return maxRetries; + } + + public Message setMaxRetries(int maxRetries) { + this.maxRetries = maxRetries; + return this; + } + + public String getRetryInterval() { + return retryInterval; + } + + public Message setRetryInterval(String retryInterval) { + this.retryInterval = retryInterval; + return this; + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("messageId", getMessageId()) + .append("sender", getSender()) + .append("receiver", getReceiver()) + .append("timestamp", getTimestamp()) + .append("type", getType()) + .append("subject", getSubject()) + .append("payload", getPayload()) + .append("metadata", getMetadata()) + .append("status", getStatus()) + .append("retryCount", getRetryCount()) + .append("maxRetries", getMaxRetries()) + .append("retryInterval", getRetryInterval()) + .toString(); + } +} \ No newline at end of file diff --git a/boyue-common/src/main/java/com/boyue/common/core/domain/R.java b/boyue-common/src/main/java/com/boyue/common/core/domain/R.java new file mode 100644 index 0000000..50abf0d --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/core/domain/R.java @@ -0,0 +1,122 @@ +package com.boyue.common.core.domain; + +import java.io.Serializable; + +import com.boyue.common.constant.HttpStatus; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 响应信息主体 + * + * @author boyue + */ +@Schema(title = "响应信息主体") +public class R implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 成功 */ + public static final int SUCCESS = HttpStatus.SUCCESS; + + /** 失败 */ + public static final int FAIL = HttpStatus.ERROR; + + @Schema(title = "响应码") + private int code; + + @Schema(title = "响应信息") + private String msg; + + @Schema(title = "响应数据") + private T data; + + public static R ok() + { + return restResult(null, SUCCESS, "操作成功"); + } + + public static R ok(T data) + { + return restResult(data, SUCCESS, "操作成功"); + } + + public static R ok(T data, String msg) + { + return restResult(data, SUCCESS, msg); + } + + public static R fail() + { + return restResult(null, FAIL, "操作失败"); + } + + public static R fail(String msg) + { + return restResult(null, FAIL, msg); + } + + public static R fail(T data) + { + return restResult(data, FAIL, "操作失败"); + } + + public static R fail(T data, String msg) + { + return restResult(data, FAIL, msg); + } + + public static R fail(int code, String msg) + { + return restResult(null, code, msg); + } + + private static R restResult(T data, int code, String msg) + { + R apiResult = new R<>(); + apiResult.setCode(code); + apiResult.setData(data); + apiResult.setMsg(msg); + return apiResult; + } + + public int getCode() + { + return code; + } + + public void setCode(int code) + { + this.code = code; + } + + public String getMsg() + { + return msg; + } + + public void setMsg(String msg) + { + this.msg = msg; + } + + public T getData() + { + return data; + } + + public void setData(T data) + { + this.data = data; + } + + public static Boolean isError(R ret) + { + return !isSuccess(ret); + } + + public static Boolean isSuccess(R ret) + { + return R.SUCCESS == ret.getCode(); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/core/domain/TreeEntity.java b/boyue-common/src/main/java/com/boyue/common/core/domain/TreeEntity.java new file mode 100644 index 0000000..102e8e6 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/core/domain/TreeEntity.java @@ -0,0 +1,79 @@ +package com.boyue.common.core.domain; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tree基类 + * + * @author boyue + */ +public class TreeEntity extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 父菜单名称 */ + private String parentName; + + /** 父菜单ID */ + private Long parentId; + + /** 显示顺序 */ + private Integer orderNum; + + /** 祖级列表 */ + private String ancestors; + + /** 子部门 */ + private List children = new ArrayList<>(); + + public String getParentName() + { + return parentName; + } + + public void setParentName(String parentName) + { + this.parentName = parentName; + } + + public Long getParentId() + { + return parentId; + } + + public void setParentId(Long parentId) + { + this.parentId = parentId; + } + + public Integer getOrderNum() + { + return orderNum; + } + + public void setOrderNum(Integer orderNum) + { + this.orderNum = orderNum; + } + + public String getAncestors() + { + return ancestors; + } + + public void setAncestors(String ancestors) + { + this.ancestors = ancestors; + } + + public List getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/core/domain/TreeSelect.java b/boyue-common/src/main/java/com/boyue/common/core/domain/TreeSelect.java new file mode 100644 index 0000000..d5b3052 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/core/domain/TreeSelect.java @@ -0,0 +1,77 @@ +package com.boyue.common.core.domain; + +import java.io.Serializable; +import java.util.List; +import java.util.stream.Collectors; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.boyue.common.core.domain.entity.SysDept; +import com.boyue.common.core.domain.entity.SysMenu; + +/** + * Treeselect树结构实体类 + * + * @author boyue + */ +public class TreeSelect implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 节点ID */ + private Long id; + + /** 节点名称 */ + private String label; + + /** 子节点 */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List children; + + public TreeSelect() + { + + } + + public TreeSelect(SysDept dept) + { + this.id = dept.getDeptId(); + this.label = dept.getDeptName(); + this.children = dept.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + public TreeSelect(SysMenu menu) + { + this.id = menu.getMenuId(); + this.label = menu.getMenuName(); + this.children = menu.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + public Long getId() + { + return id; + } + + public void setId(Long id) + { + this.id = id; + } + + public String getLabel() + { + return label; + } + + public void setLabel(String label) + { + this.label = label; + } + + public List getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/core/domain/entity/SysDept.java b/boyue-common/src/main/java/com/boyue/common/core/domain/entity/SysDept.java new file mode 100644 index 0000000..c2d63ef --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/core/domain/entity/SysDept.java @@ -0,0 +1,220 @@ +package com.boyue.common.core.domain.entity; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.boyue.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +/** + * 部门表 sys_dept + * + * @author boyue + */ +@Schema(title = "部门") +public class SysDept extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 部门ID */ + @Schema(title = "部门ID") + private Long deptId; + + /** 父部门ID */ + @Schema(title = "父部门ID") + private Long parentId; + + /** 祖级列表 */ + @Schema(title = "祖级列表") + private String ancestors; + + /** 部门名称 */ + @Schema(title = "部门名称") + private String deptName; + + /** 显示顺序 */ + @Schema(title = "显示顺序") + private Integer orderNum; + + /** 负责人 */ + @Schema(title = "负责人") + private String leader; + + /** 联系电话 */ + @Schema(title = "联系电话") + private String phone; + + /** 邮箱 */ + @Schema(title = "邮箱") + private String email; + + /** 部门状态:0正常,1停用 */ + @Schema(title = "部门表",description = "0正常,1停用") + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + @Schema(title = "删除标志",description = "0代表存在 2代表删除") + private String delFlag; + + /** 父部门名称 */ + @Schema(title = "父部门名称") + private String parentName; + + /** 子部门 */ + @Schema(title = "子部门") + private List children = new ArrayList(); + + public Long getDeptId() + { + return deptId; + } + + public void setDeptId(Long deptId) + { + this.deptId = deptId; + } + + public Long getParentId() + { + return parentId; + } + + public void setParentId(Long parentId) + { + this.parentId = parentId; + } + + public String getAncestors() + { + return ancestors; + } + + public void setAncestors(String ancestors) + { + this.ancestors = ancestors; + } + + @NotBlank(message = "部门名称不能为空") + @Size(min = 0, max = 30, message = "部门名称长度不能超过30个字符") + public String getDeptName() + { + return deptName; + } + + public void setDeptName(String deptName) + { + this.deptName = deptName; + } + + @NotNull(message = "显示顺序不能为空") + public Integer getOrderNum() + { + return orderNum; + } + + public void setOrderNum(Integer orderNum) + { + this.orderNum = orderNum; + } + + public String getLeader() + { + return leader; + } + + public void setLeader(String leader) + { + this.leader = leader; + } + + @Size(min = 0, max = 11, message = "联系电话长度不能超过11个字符") + public String getPhone() + { + return phone; + } + + public void setPhone(String phone) + { + this.phone = phone; + } + + @Email(message = "邮箱格式不正确") + @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符") + public String getEmail() + { + return email; + } + + public void setEmail(String email) + { + this.email = email; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getDelFlag() + { + return delFlag; + } + + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public String getParentName() + { + return parentName; + } + + public void setParentName(String parentName) + { + this.parentName = parentName; + } + + public List getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("deptId", getDeptId()) + .append("parentId", getParentId()) + .append("ancestors", getAncestors()) + .append("deptName", getDeptName()) + .append("orderNum", getOrderNum()) + .append("leader", getLeader()) + .append("phone", getPhone()) + .append("email", getEmail()) + .append("status", getStatus()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .toString(); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/core/domain/entity/SysDictData.java b/boyue-common/src/main/java/com/boyue/common/core/domain/entity/SysDictData.java new file mode 100644 index 0000000..18e1d77 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/core/domain/entity/SysDictData.java @@ -0,0 +1,189 @@ +package com.boyue.common.core.domain.entity; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.boyue.common.annotation.Excel; +import com.boyue.common.annotation.Excel.ColumnType; +import com.boyue.common.constant.UserConstants; +import com.boyue.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +/** + * 字典数据表 sys_dict_data + * + * @author boyue + */ +@Schema(title = "字典数据") +public class SysDictData extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 字典编码 */ + @Schema(title = "字典编码") + @Excel(name = "字典编码", cellType = ColumnType.NUMERIC) + private Long dictCode; + + /** 字典排序 */ + @Schema(title = "字典排序") + @Excel(name = "字典排序", cellType = ColumnType.NUMERIC) + private Long dictSort; + + /** 字典标签 */ + @Schema(title = "字典标签") + @Excel(name = "字典标签") + private String dictLabel; + + /** 字典键值 */ + @Schema(title = "字典键值") + @Excel(name = "字典键值") + private String dictValue; + + /** 字典类型 */ + @Schema(title = "字典类型") + @Excel(name = "字典类型") + private String dictType; + + /** 样式属性(其他样式扩展) */ + @Schema(title = "样式属性", description = "其他样式扩展") + private String cssClass; + + /** 表格字典样式 */ + @Schema(title = "表格字典样式") + private String listClass; + + /** 是否默认(Y是 N否) */ + @Schema(title = "是否默认", description = "Y=是,N=否") + @Excel(name = "是否默认", readConverterExp = "Y=是,N=否") + private String isDefault; + + /** 状态(0正常 1停用) */ + @Schema(title = "状态", description = "0=正常,1=停用") + @Excel(name = "状态", readConverterExp = "0=正常,1=停用") + private String status; + + public Long getDictCode() + { + return dictCode; + } + + public void setDictCode(Long dictCode) + { + this.dictCode = dictCode; + } + + public Long getDictSort() + { + return dictSort; + } + + public void setDictSort(Long dictSort) + { + this.dictSort = dictSort; + } + + @NotBlank(message = "字典标签不能为空") + @Size(min = 0, max = 100, message = "字典标签长度不能超过100个字符") + public String getDictLabel() + { + return dictLabel; + } + + public void setDictLabel(String dictLabel) + { + this.dictLabel = dictLabel; + } + + @NotBlank(message = "字典键值不能为空") + @Size(min = 0, max = 100, message = "字典键值长度不能超过100个字符") + public String getDictValue() + { + return dictValue; + } + + public void setDictValue(String dictValue) + { + this.dictValue = dictValue; + } + + @NotBlank(message = "字典类型不能为空") + @Size(min = 0, max = 100, message = "字典类型长度不能超过100个字符") + public String getDictType() + { + return dictType; + } + + public void setDictType(String dictType) + { + this.dictType = dictType; + } + + @Size(min = 0, max = 100, message = "样式属性长度不能超过100个字符") + public String getCssClass() + { + return cssClass; + } + + public void setCssClass(String cssClass) + { + this.cssClass = cssClass; + } + + public String getListClass() + { + return listClass; + } + + public void setListClass(String listClass) + { + this.listClass = listClass; + } + + public boolean getDefault() + { + return UserConstants.YES.equals(this.isDefault); + } + + public String getIsDefault() + { + return isDefault; + } + + public void setIsDefault(String isDefault) + { + this.isDefault = isDefault; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("dictCode", getDictCode()) + .append("dictSort", getDictSort()) + .append("dictLabel", getDictLabel()) + .append("dictValue", getDictValue()) + .append("dictType", getDictType()) + .append("cssClass", getCssClass()) + .append("listClass", getListClass()) + .append("isDefault", getIsDefault()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/core/domain/entity/SysDictType.java b/boyue-common/src/main/java/com/boyue/common/core/domain/entity/SysDictType.java new file mode 100644 index 0000000..0847495 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/core/domain/entity/SysDictType.java @@ -0,0 +1,104 @@ +package com.boyue.common.core.domain.entity; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.boyue.common.annotation.Excel; +import com.boyue.common.annotation.Excel.ColumnType; +import com.boyue.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; + +/** + * 字典类型表 sys_dict_type + * + * @author boyue + */ +@Schema(title = "字典类型") +public class SysDictType extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 字典主键 */ + @Schema(title = "字典主键") + @Excel(name = "字典主键", cellType = ColumnType.NUMERIC) + private Long dictId; + + /** 字典名称 */ + @Schema(title = "字典名称") + @Excel(name = "字典名称") + private String dictName; + + /** 字典类型 */ + @Schema(title = "字典类型") + @Excel(name = "字典类型") + private String dictType; + + /** 状态(0正常 1停用) */ + @Schema(title = "状态", description = "0正常 1停用") + @Excel(name = "状态", readConverterExp = "0=正常,1=停用") + private String status; + + public Long getDictId() + { + return dictId; + } + + public void setDictId(Long dictId) + { + this.dictId = dictId; + } + + @NotBlank(message = "字典名称不能为空") + @Size(min = 0, max = 100, message = "字典类型名称长度不能超过100个字符") + public String getDictName() + { + return dictName; + } + + public void setDictName(String dictName) + { + this.dictName = dictName; + } + + @NotBlank(message = "字典类型不能为空") + @Size(min = 0, max = 100, message = "字典类型类型长度不能超过100个字符") + @Pattern(regexp = "^[a-z][a-z0-9_]*$", message = "字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)") + public String getDictType() + { + return dictType; + } + + public void setDictType(String dictType) + { + this.dictType = dictType; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("dictId", getDictId()) + .append("dictName", getDictName()) + .append("dictType", getDictType()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/core/domain/entity/SysMenu.java b/boyue-common/src/main/java/com/boyue/common/core/domain/entity/SysMenu.java new file mode 100644 index 0000000..9d909bf --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/core/domain/entity/SysMenu.java @@ -0,0 +1,260 @@ +package com.boyue.common.core.domain.entity; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.boyue.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +/** + * 菜单权限表 sys_menu + * + * @author boyue + */ +@Schema(title = "菜单权限") +public class SysMenu extends BaseEntity { + private static final long serialVersionUID = 1L; + + /** 菜单ID */ + @Schema(title = "菜单ID") + private Long menuId; + + /** 菜单名称 */ + @Schema(title = "菜单名称") + private String menuName; + + /** 父菜单名称 */ + @Schema(title = "父菜单名称") + private String parentName; + + /** 父菜单ID */ + @Schema(title = "父菜单ID") + private Long parentId; + + /** 显示顺序 */ + @Schema(title = "显示顺序") + private Integer orderNum; + + /** 路由地址 */ + @Schema(title = "路由地址") + private String path; + + /** 组件路径 */ + @Schema(title = "组件路径") + private String component; + + /** 路由参数 */ + @Schema(title = "路由参数") + private String query; + + /** 路由名称,默认和路由地址相同的驼峰格式(注意:因为vue3版本的router会删除名称相同路由,为避免名字的冲突,特殊情况可以自定义) */ + private String routeName; + + /** 是否为外链(0是 1否) */ + @Schema(title = "是否为外链", description = "0是 1否") + private String isFrame; + + /** 是否缓存(0缓存 1不缓存) */ + @Schema(title = "是否缓存", description = "0缓存 1不缓存") + private String isCache; + + /** 类型(M目录 C菜单 F按钮) */ + @Schema(title = "类型", description = "M目录 C菜单 F按钮") + private String menuType; + + /** 显示状态(0显示 1隐藏) */ + @Schema(title = "显示状态", description = "0显示 1隐藏") + private String visible; + + /** 菜单状态(0正常 1停用) */ + @Schema(title = "菜单状态", description = "0正常 1停用") + private String status; + + /** 权限字符串 */ + @Schema(title = "权限字符串") + private String perms; + + /** 菜单图标 */ + @Schema(title = "菜单图标") + private String icon; + + /** 子菜单 */ + @Schema(title = "子菜单") + private List children = new ArrayList(); + + public Long getMenuId() { + return menuId; + } + + public void setMenuId(Long menuId) { + this.menuId = menuId; + } + + @NotBlank(message = "菜单名称不能为空") + @Size(min = 0, max = 50, message = "菜单名称长度不能超过50个字符") + public String getMenuName() { + return menuName; + } + + public void setMenuName(String menuName) { + this.menuName = menuName; + } + + public String getParentName() { + return parentName; + } + + public void setParentName(String parentName) { + this.parentName = parentName; + } + + public Long getParentId() { + return parentId; + } + + public void setParentId(Long parentId) { + this.parentId = parentId; + } + + @NotNull(message = "显示顺序不能为空") + public Integer getOrderNum() { + return orderNum; + } + + public void setOrderNum(Integer orderNum) { + this.orderNum = orderNum; + } + + @Size(min = 0, max = 200, message = "路由地址不能超过200个字符") + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + @Size(min = 0, max = 200, message = "组件路径不能超过255个字符") + public String getComponent() { + return component; + } + + public void setComponent(String component) { + this.component = component; + } + + public String getQuery() { + return query; + } + + public void setQuery(String query) { + this.query = query; + } + + public String getRouteName() { + return routeName; + } + + public void setRouteName(String routeName) { + this.routeName = routeName; + } + + public String getIsFrame() { + return isFrame; + } + + public void setIsFrame(String isFrame) { + this.isFrame = isFrame; + } + + public String getIsCache() { + return isCache; + } + + public void setIsCache(String isCache) { + this.isCache = isCache; + } + + @NotBlank(message = "菜单类型不能为空") + public String getMenuType() { + return menuType; + } + + public void setMenuType(String menuType) { + this.menuType = menuType; + } + + public String getVisible() { + return visible; + } + + public void setVisible(String visible) { + this.visible = visible; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + @Size(min = 0, max = 100, message = "权限标识长度不能超过100个字符") + public String getPerms() { + return perms; + } + + public void setPerms(String perms) { + this.perms = perms; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("menuId", getMenuId()) + .append("menuName", getMenuName()) + .append("parentId", getParentId()) + .append("orderNum", getOrderNum()) + .append("path", getPath()) + .append("component", getComponent()) + .append("query", getQuery()) + .append("routeName", getRouteName()) + .append("isFrame", getIsFrame()) + .append("IsCache", getIsCache()) + .append("menuType", getMenuType()) + .append("visible", getVisible()) + .append("status ", getStatus()) + .append("perms", getPerms()) + .append("icon", getIcon()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/core/domain/entity/SysRole.java b/boyue-common/src/main/java/com/boyue/common/core/domain/entity/SysRole.java new file mode 100644 index 0000000..66fc321 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/core/domain/entity/SysRole.java @@ -0,0 +1,259 @@ +package com.boyue.common.core.domain.entity; + +import java.util.Set; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.boyue.common.annotation.Excel; +import com.boyue.common.annotation.Excel.ColumnType; +import com.boyue.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +/** + * 角色表 sys_role + * + * @author boyue + */ +@Schema(title = "角色表") +public class SysRole extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 角色ID */ + @Schema(title = "角色ID") + @Excel(name = "角色序号", cellType = ColumnType.NUMERIC) + private Long roleId; + + /** 角色名称 */ + @Schema(title = "角色名称") + @Excel(name = "角色名称") + private String roleName; + + /** 角色权限 */ + @Schema(title = "角色权限") + @Excel(name = "角色权限") + private String roleKey; + + /** 角色排序 */ + @Schema(title = "角色排序") + @Excel(name = "角色排序") + private Integer roleSort; + + /** 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限) */ + @Schema(title = "数据范围", description = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限,5=仅本人数据权限") + @Excel(name = "数据范围", readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限,5=仅本人数据权限") + private String dataScope; + + /** 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示) */ + @Schema(title = "菜单树选择项是否关联显示", description = "0:父子不互相关联显示 1:父子互相关联显示") + private boolean menuCheckStrictly; + + /** 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示 ) */ + @Schema(title = "部门树选择项是否关联显示", description = "0:父子不互相关联显示 1:父子互相关联显示 ") + private boolean deptCheckStrictly; + + /** 角色状态(0正常 1停用) */ + @Schema(title = "角色状态", description = "0正常 1停用") + @Excel(name = "角色状态", readConverterExp = "0=正常,1=停用") + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + @Schema(title = "删除标志", description = "0代表存在 2代表删除") + private String delFlag; + + /** 用户是否存在此角色标识 默认不存在 */ + @Schema(title = "用户是否存在此角色标识", description = "默认不存在") + private boolean flag = false; + + /** 菜单组 */ + @Schema(title = "菜单组") + private Long[] menuIds; + + /** 部门组(数据权限) */ + @Schema(title = "部门组", description = "数据权限") + private Long[] deptIds; + + /** 角色菜单权限 */ + @Schema(title = "角色菜单权限") + private Set permissions; + + public SysRole() + { + + } + + public SysRole(Long roleId) + { + this.roleId = roleId; + } + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + public boolean isAdmin() + { + return isAdmin(this.roleId); + } + + public static boolean isAdmin(Long roleId) + { + return roleId != null && 1L == roleId; + } + + @NotBlank(message = "角色名称不能为空") + @Size(min = 0, max = 30, message = "角色名称长度不能超过30个字符") + public String getRoleName() + { + return roleName; + } + + public void setRoleName(String roleName) + { + this.roleName = roleName; + } + + @NotBlank(message = "权限字符不能为空") + @Size(min = 0, max = 100, message = "权限字符长度不能超过100个字符") + public String getRoleKey() + { + return roleKey; + } + + public void setRoleKey(String roleKey) + { + this.roleKey = roleKey; + } + + @NotNull(message = "显示顺序不能为空") + public Integer getRoleSort() + { + return roleSort; + } + + public void setRoleSort(Integer roleSort) + { + this.roleSort = roleSort; + } + + public String getDataScope() + { + return dataScope; + } + + public void setDataScope(String dataScope) + { + this.dataScope = dataScope; + } + + public boolean isMenuCheckStrictly() + { + return menuCheckStrictly; + } + + public void setMenuCheckStrictly(boolean menuCheckStrictly) + { + this.menuCheckStrictly = menuCheckStrictly; + } + + public boolean isDeptCheckStrictly() + { + return deptCheckStrictly; + } + + public void setDeptCheckStrictly(boolean deptCheckStrictly) + { + this.deptCheckStrictly = deptCheckStrictly; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getDelFlag() + { + return delFlag; + } + + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public boolean isFlag() + { + return flag; + } + + public void setFlag(boolean flag) + { + this.flag = flag; + } + + public Long[] getMenuIds() + { + return menuIds; + } + + public void setMenuIds(Long[] menuIds) + { + this.menuIds = menuIds; + } + + public Long[] getDeptIds() + { + return deptIds; + } + + public void setDeptIds(Long[] deptIds) + { + this.deptIds = deptIds; + } + + public Set getPermissions() + { + return permissions; + } + + public void setPermissions(Set permissions) + { + this.permissions = permissions; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("roleId", getRoleId()) + .append("roleName", getRoleName()) + .append("roleKey", getRoleKey()) + .append("roleSort", getRoleSort()) + .append("dataScope", getDataScope()) + .append("menuCheckStrictly", isMenuCheckStrictly()) + .append("deptCheckStrictly", isDeptCheckStrictly()) + .append("status", getStatus()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/core/domain/entity/SysUser.java b/boyue-common/src/main/java/com/boyue/common/core/domain/entity/SysUser.java new file mode 100644 index 0000000..9fe1a12 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/core/domain/entity/SysUser.java @@ -0,0 +1,349 @@ +package com.boyue.common.core.domain.entity; + +import java.util.Date; +import java.util.List; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.boyue.common.annotation.Excel; +import com.boyue.common.annotation.Excel.ColumnType; +import com.boyue.common.annotation.Excel.Type; +import com.boyue.common.annotation.Excels; +import com.boyue.common.core.domain.BaseEntity; +import com.boyue.common.xss.Xss; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +/** + * 用户对象 sys_user + * + * @author boyue + */ +@Schema(title = "用户") +public class SysUser extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 用户ID */ + @Schema(title = "用户序号") + @Excel(name = "用户序号", cellType = ColumnType.NUMERIC, prompt = "用户编号") + private Long userId; + + /** 部门ID */ + @Schema(title = "部门编号") + @Excel(name = "部门编号", type = Type.IMPORT) + private Long deptId; + + /** 用户账号 */ + @Schema(title = "登录名称") + @Excel(name = "登录名称") + private String userName; + + /** 用户昵称 */ + @Schema(title = "用户名称") + @Excel(name = "用户名称") + private String nickName; + + /** 用户邮箱 */ + @Schema(title = "用户邮箱") + @Excel(name = "用户邮箱") + private String email; + + /** 手机号码 */ + @Schema(title = "手机号码") + @Excel(name = "手机号码") + private String phonenumber; + + /** 用户性别 */ + @Schema(title = "用户性别", description = "0=男,1=女,2=未知") + @Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知") + private String sex; + + /** 用户头像 */ + @Schema(title = "用户头像") + private String avatar; + + /** 密码 */ + @Schema(title = "密码") + private String password; + + /** 帐号状态(0正常 1停用) */ + @Schema(title = "帐号状态", description = "0正常 1停用") + @Excel(name = "帐号状态", readConverterExp = "0=正常,1=停用") + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + @Schema(title = "删除标志", description = "0代表存在 2代表删除") + private String delFlag; + + /** 最后登录IP */ + @Schema(title = "最后登录IP") + @Excel(name = "最后登录IP", type = Type.EXPORT) + private String loginIp; + + /** 最后登录时间 */ + @Schema(title = "最后登录时间") + @Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Type.EXPORT) + private Date loginDate; + + /** 部门对象 */ + @Schema(title = "部门对象") + @Excels({ + @Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT), + @Excel(name = "部门负责人", targetAttr = "leader", type = Type.EXPORT) + }) + private SysDept dept; + + /** 角色对象 */ + @Schema(title = "角色对象") + private List roles; + + /** 角色组 */ + @Schema(title = "角色组") + private Long[] roleIds; + + /** 岗位组 */ + @Schema(title = "岗位组") + private Long[] postIds; + + /** 角色ID */ + @Schema(title = "角色ID") + private Long roleId; + + public SysUser() + { + + } + + public SysUser(Long userId) + { + this.userId = userId; + } + + public Long getUserId() + { + return userId; + } + + public void setUserId(Long userId) + { + this.userId = userId; + } + + public boolean isAdmin() + { + return isAdmin(this.userId); + } + + public static boolean isAdmin(Long userId) + { + return userId != null && 1L == userId; + } + + public Long getDeptId() + { + return deptId; + } + + public void setDeptId(Long deptId) + { + this.deptId = deptId; + } + + @Xss(message = "用户昵称不能包含脚本字符") + @Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符") + public String getNickName() + { + return nickName; + } + + public void setNickName(String nickName) + { + this.nickName = nickName; + } + + @Xss(message = "用户账号不能包含脚本字符") + @NotBlank(message = "用户账号不能为空") + @Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符") + public String getUserName() + { + return userName; + } + + public void setUserName(String userName) + { + this.userName = userName; + } + + @Email(message = "邮箱格式不正确") + @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符") + public String getEmail() + { + return email; + } + + public void setEmail(String email) + { + this.email = email; + } + + @Size(min = 0, max = 11, message = "手机号码长度不能超过11个字符") + public String getPhonenumber() + { + return phonenumber; + } + + public void setPhonenumber(String phonenumber) + { + this.phonenumber = phonenumber; + } + + public String getSex() + { + return sex; + } + + public void setSex(String sex) + { + this.sex = sex; + } + + public String getAvatar() + { + return avatar; + } + + public void setAvatar(String avatar) + { + this.avatar = avatar; + } + + public String getPassword() + { + return password; + } + + public void setPassword(String password) + { + this.password = password; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getDelFlag() + { + return delFlag; + } + + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public String getLoginIp() + { + return loginIp; + } + + public void setLoginIp(String loginIp) + { + this.loginIp = loginIp; + } + + public Date getLoginDate() + { + return loginDate; + } + + public void setLoginDate(Date loginDate) + { + this.loginDate = loginDate; + } + + public SysDept getDept() + { + return dept; + } + + public void setDept(SysDept dept) + { + this.dept = dept; + } + + public List getRoles() + { + return roles; + } + + public void setRoles(List roles) + { + this.roles = roles; + } + + public Long[] getRoleIds() + { + return roleIds; + } + + public void setRoleIds(Long[] roleIds) + { + this.roleIds = roleIds; + } + + public Long[] getPostIds() + { + return postIds; + } + + public void setPostIds(Long[] postIds) + { + this.postIds = postIds; + } + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("userId", getUserId()) + .append("deptId", getDeptId()) + .append("userName", getUserName()) + .append("nickName", getNickName()) + .append("email", getEmail()) + .append("phonenumber", getPhonenumber()) + .append("sex", getSex()) + .append("avatar", getAvatar()) + .append("password", getPassword()) + .append("status", getStatus()) + .append("delFlag", getDelFlag()) + .append("loginIp", getLoginIp()) + .append("loginDate", getLoginDate()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .append("dept", getDept()) + .toString(); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/core/domain/model/LoginBody.java b/boyue-common/src/main/java/com/boyue/common/core/domain/model/LoginBody.java new file mode 100644 index 0000000..4909be9 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/core/domain/model/LoginBody.java @@ -0,0 +1,97 @@ +package com.boyue.common.core.domain.model; + +import com.boyue.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 用户登录对象 + * + * @author boyue + */ +@Schema(title = "用户登录对象") +public class LoginBody extends BaseEntity { + /** + * 用户名 + */ + @Schema(title = "用户名") + private String username; + + /** + * 用户密码 + */ + @Schema(title = "用户密码") + private String password; + + /** + * 手机号码 + */ + @Schema(title = "手机号码") + private String phonenumber; + + /** + * 邮箱 + */ + @Schema(title = "邮箱") + private String email; + + /** + * 验证码 + */ + @Schema(title = "验证码") + private String code; + + /** + * 唯一标识 + */ + @Schema(title = "唯一标识") + private String uuid; + + 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 String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getPhonenumber() { + return phonenumber; + } + + public void setPhonenumber(String phonenumber) { + this.phonenumber = phonenumber; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/core/domain/model/LoginUser.java b/boyue-common/src/main/java/com/boyue/common/core/domain/model/LoginUser.java new file mode 100644 index 0000000..c92b53b --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/core/domain/model/LoginUser.java @@ -0,0 +1,266 @@ +package com.boyue.common.core.domain.model; + +import java.util.Collection; +import java.util.Set; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import com.alibaba.fastjson2.annotation.JSONField; +import com.boyue.common.core.domain.entity.SysUser; + +/** + * 登录用户身份权限 + * + * @author boyue + */ +public class LoginUser implements UserDetails +{ + private static final long serialVersionUID = 1L; + + /** + * 用户ID + */ + private Long userId; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 用户唯一标识 + */ + private String token; + + /** + * 登录时间 + */ + private Long loginTime; + + /** + * 过期时间 + */ + private Long expireTime; + + /** + * 登录IP地址 + */ + private String ipaddr; + + /** + * 登录地点 + */ + private String loginLocation; + + /** + * 浏览器类型 + */ + private String browser; + + /** + * 操作系统 + */ + private String os; + + /** + * 权限列表 + */ + private Set permissions; + + /** + * 用户信息 + */ + private SysUser user; + + public Long getUserId() + { + return userId; + } + + public void setUserId(Long userId) + { + this.userId = userId; + } + + public Long getDeptId() + { + return deptId; + } + + public void setDeptId(Long deptId) + { + this.deptId = deptId; + } + + public String getToken() + { + return token; + } + + public void setToken(String token) + { + this.token = token; + } + + public LoginUser() + { + } + + public LoginUser(SysUser user, Set permissions) + { + this.user = user; + this.permissions = permissions; + } + + public LoginUser(Long userId, Long deptId, SysUser user, Set permissions) + { + this.userId = userId; + this.deptId = deptId; + this.user = user; + this.permissions = permissions; + } + + @JSONField(serialize = false) + @Override + public String getPassword() + { + return user.getPassword(); + } + + @Override + public String getUsername() + { + return user.getUserName(); + } + + /** + * 账户是否未过期,过期无法验证 + */ + @JSONField(serialize = false) + @Override + public boolean isAccountNonExpired() + { + return true; + } + + /** + * 指定用户是否解锁,锁定的用户无法进行身份验证 + * + * @return + */ + @JSONField(serialize = false) + @Override + public boolean isAccountNonLocked() + { + return true; + } + + /** + * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证 + * + * @return + */ + @JSONField(serialize = false) + @Override + public boolean isCredentialsNonExpired() + { + return true; + } + + /** + * 是否可用 ,禁用的用户不能身份验证 + * + * @return + */ + @JSONField(serialize = false) + @Override + public boolean isEnabled() + { + return true; + } + + public Long getLoginTime() + { + return loginTime; + } + + public void setLoginTime(Long loginTime) + { + this.loginTime = loginTime; + } + + public String getIpaddr() + { + return ipaddr; + } + + public void setIpaddr(String ipaddr) + { + this.ipaddr = ipaddr; + } + + public String getLoginLocation() + { + return loginLocation; + } + + public void setLoginLocation(String loginLocation) + { + this.loginLocation = loginLocation; + } + + public String getBrowser() + { + return browser; + } + + public void setBrowser(String browser) + { + this.browser = browser; + } + + public String getOs() + { + return os; + } + + public void setOs(String os) + { + this.os = os; + } + + public Long getExpireTime() + { + return expireTime; + } + + public void setExpireTime(Long expireTime) + { + this.expireTime = expireTime; + } + + public Set getPermissions() + { + return permissions; + } + + public void setPermissions(Set permissions) + { + this.permissions = permissions; + } + + public SysUser getUser() + { + return user; + } + + public void setUser(SysUser user) + { + this.user = user; + } + + @Override + public Collection getAuthorities() + { + return null; + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/core/domain/model/RegisterBody.java b/boyue-common/src/main/java/com/boyue/common/core/domain/model/RegisterBody.java new file mode 100644 index 0000000..a04e437 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/core/domain/model/RegisterBody.java @@ -0,0 +1,14 @@ +package com.boyue.common.core.domain.model; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 用户注册对象 + * + * @author boyue + */ +@Schema(title = "用户注册对象") +public class RegisterBody extends LoginBody +{ + +} diff --git a/boyue-common/src/main/java/com/boyue/common/core/page/PageDomain.java b/boyue-common/src/main/java/com/boyue/common/core/page/PageDomain.java new file mode 100644 index 0000000..c38b8b5 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/core/page/PageDomain.java @@ -0,0 +1,101 @@ +package com.boyue.common.core.page; + +import com.boyue.common.utils.StringUtils; + +/** + * 分页数据 + * + * @author boyue + */ +public class PageDomain +{ + /** 当前记录起始索引 */ + private Integer pageNum; + + /** 每页显示记录数 */ + private Integer pageSize; + + /** 排序列 */ + private String orderByColumn; + + /** 排序的方向desc或者asc */ + private String isAsc = "asc"; + + /** 分页参数合理化 */ + private Boolean reasonable = true; + + public String getOrderBy() + { + if (StringUtils.isEmpty(orderByColumn)) + { + return ""; + } + return StringUtils.toUnderScoreCase(orderByColumn) + " " + isAsc; + } + + public Integer getPageNum() + { + return pageNum; + } + + public void setPageNum(Integer pageNum) + { + this.pageNum = pageNum; + } + + public Integer getPageSize() + { + return pageSize; + } + + public void setPageSize(Integer pageSize) + { + this.pageSize = pageSize; + } + + public String getOrderByColumn() + { + return orderByColumn; + } + + public void setOrderByColumn(String orderByColumn) + { + this.orderByColumn = orderByColumn; + } + + public String getIsAsc() + { + return isAsc; + } + + public void setIsAsc(String isAsc) + { + if (StringUtils.isNotEmpty(isAsc)) + { + // 兼容前端排序类型 + if ("ascending".equals(isAsc)) + { + isAsc = "asc"; + } + else if ("descending".equals(isAsc)) + { + isAsc = "desc"; + } + this.isAsc = isAsc; + } + } + + public Boolean getReasonable() + { + if (StringUtils.isNull(reasonable)) + { + return Boolean.TRUE; + } + return reasonable; + } + + public void setReasonable(Boolean reasonable) + { + this.reasonable = reasonable; + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/core/page/TableDataInfo.java b/boyue-common/src/main/java/com/boyue/common/core/page/TableDataInfo.java new file mode 100644 index 0000000..203a992 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/core/page/TableDataInfo.java @@ -0,0 +1,92 @@ +package com.boyue.common.core.page; + +import java.io.Serializable; +import java.util.List; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 表格分页数据对象 + * + * @author boyue + */ +@Schema(title = "表格分页数据对象") +public class TableDataInfo implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 总记录数 */ + @Schema(title = "总记录数") + private long total; + + /** 列表数据 */ + @Schema(title = "列表数据") + private List rows; + + /** 消息状态码 */ + @Schema(title = "消息状态码") + private int code; + + /** 消息内容 */ + @Schema(title = "消息内容") + private String msg; + + /** + * 表格数据对象 + */ + public TableDataInfo() + { + } + + /** + * 分页 + * + * @param list 列表数据 + * @param total 总记录数 + */ + public TableDataInfo(List list, int total) + { + this.rows = list; + this.total = total; + } + + public long getTotal() + { + return total; + } + + public void setTotal(long total) + { + this.total = total; + } + + public List getRows() + { + return rows; + } + + public void setRows(List rows) + { + this.rows = rows; + } + + public int getCode() + { + return code; + } + + public void setCode(int code) + { + this.code = code; + } + + public String getMsg() + { + return msg; + } + + public void setMsg(String msg) + { + this.msg = msg; + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/core/page/TableSupport.java b/boyue-common/src/main/java/com/boyue/common/core/page/TableSupport.java new file mode 100644 index 0000000..8ae24b8 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/core/page/TableSupport.java @@ -0,0 +1,56 @@ +package com.boyue.common.core.page; + +import com.boyue.common.core.text.Convert; +import com.boyue.common.utils.ServletUtils; + +/** + * 表格数据处理 + * + * @author boyue + */ +public class TableSupport +{ + /** + * 当前记录起始索引 + */ + public static final String PAGE_NUM = "pageNum"; + + /** + * 每页显示记录数 + */ + public static final String PAGE_SIZE = "pageSize"; + + /** + * 排序列 + */ + public static final String ORDER_BY_COLUMN = "orderByColumn"; + + /** + * 排序的方向 "desc" 或者 "asc". + */ + public static final String IS_ASC = "isAsc"; + + /** + * 分页参数合理化 + */ + public static final String REASONABLE = "reasonable"; + + /** + * 封装分页对象 + */ + public static PageDomain getPageDomain() + { + PageDomain pageDomain = new PageDomain(); + pageDomain.setPageNum(Convert.toInt(ServletUtils.getParameter(PAGE_NUM), 1)); + pageDomain.setPageSize(Convert.toInt(ServletUtils.getParameter(PAGE_SIZE), 10)); + pageDomain.setOrderByColumn(ServletUtils.getParameter(ORDER_BY_COLUMN)); + pageDomain.setIsAsc(ServletUtils.getParameter(IS_ASC)); + pageDomain.setReasonable(ServletUtils.getParameterToBool(REASONABLE)); + return pageDomain; + } + + public static PageDomain buildPageRequest() + { + return getPageDomain(); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/core/security/service/IPermissionService.java b/boyue-common/src/main/java/com/boyue/common/core/security/service/IPermissionService.java new file mode 100644 index 0000000..dfe6151 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/core/security/service/IPermissionService.java @@ -0,0 +1,56 @@ +package com.boyue.common.core.security.service; + +public interface IPermissionService { + + /** + * 验证用户是否具备某权限 + * + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + boolean hasPermi(String permission); + + /** + * 验证用户是否不具备某权限,与 hasPermi 逻辑相反 + * + * @param permission 权限字符串 + * @return 用户是否不具备某权限 + */ + default boolean lacksPermi(String permission) { + return !hasPermi(permission); + } + + /** + * 验证用户是否具有以下任意一个权限 + * + * @param permissions 以 PERMISSION_DELIMETER 为分隔符的权限列表 + * @return 用户是否具有以下任意一个权限 + */ + boolean hasAnyPermi(String permissions); + + /** + * 判断用户是否拥有某个角色 + * + * @param role 角色字符串 + * @return 用户是否具备某角色 + */ + boolean hasRole(String role); + + /** + * 验证用户是否不具备某角色,与 hasRole 逻辑相反。 + * + * @param role 角色名称 + * @return 用户是否不具备某角色 + */ + default boolean lacksRole(String role) { + return !hasRole(role); + } + + /** + * 验证用户是否具有以下任意一个角色 + * + * @param roles 以 ROLE_DELIMETER 为分隔符的角色列表 + * @return 用户是否具有以下任意一个角色 + */ + boolean hasAnyRoles(String roles); +} \ No newline at end of file diff --git a/boyue-common/src/main/java/com/boyue/common/core/text/CharsetKit.java b/boyue-common/src/main/java/com/boyue/common/core/text/CharsetKit.java new file mode 100644 index 0000000..dbf7a0a --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/core/text/CharsetKit.java @@ -0,0 +1,86 @@ +package com.boyue.common.core.text; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import com.boyue.common.utils.StringUtils; + +/** + * 字符集工具类 + * + * @author boyue + */ +public class CharsetKit +{ + /** ISO-8859-1 */ + public static final String ISO_8859_1 = "ISO-8859-1"; + /** UTF-8 */ + public static final String UTF_8 = "UTF-8"; + /** GBK */ + public static final String GBK = "GBK"; + + /** ISO-8859-1 */ + public static final Charset CHARSET_ISO_8859_1 = Charset.forName(ISO_8859_1); + /** UTF-8 */ + public static final Charset CHARSET_UTF_8 = Charset.forName(UTF_8); + /** GBK */ + public static final Charset CHARSET_GBK = Charset.forName(GBK); + + /** + * 转换为Charset对象 + * + * @param charset 字符集,为空则返回默认字符集 + * @return Charset + */ + public static Charset charset(String charset) + { + return StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset); + } + + /** + * 转换字符串的字符集编码 + * + * @param source 字符串 + * @param srcCharset 源字符集,默认ISO-8859-1 + * @param destCharset 目标字符集,默认UTF-8 + * @return 转换后的字符集 + */ + public static String convert(String source, String srcCharset, String destCharset) + { + return convert(source, Charset.forName(srcCharset), Charset.forName(destCharset)); + } + + /** + * 转换字符串的字符集编码 + * + * @param source 字符串 + * @param srcCharset 源字符集,默认ISO-8859-1 + * @param destCharset 目标字符集,默认UTF-8 + * @return 转换后的字符集 + */ + public static String convert(String source, Charset srcCharset, Charset destCharset) + { + if (null == srcCharset) + { + srcCharset = StandardCharsets.ISO_8859_1; + } + + if (null == destCharset) + { + destCharset = StandardCharsets.UTF_8; + } + + if (StringUtils.isEmpty(source) || srcCharset.equals(destCharset)) + { + return source; + } + return new String(source.getBytes(srcCharset), destCharset); + } + + /** + * @return 系统字符集编码 + */ + public static String systemCharset() + { + return Charset.defaultCharset().name(); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/core/text/Convert.java b/boyue-common/src/main/java/com/boyue/common/core/text/Convert.java new file mode 100644 index 0000000..96b468f --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/core/text/Convert.java @@ -0,0 +1,1006 @@ +package com.boyue.common.core.text; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.text.NumberFormat; +import java.util.Set; + +import org.apache.commons.lang3.ArrayUtils; + +import com.boyue.common.utils.StringUtils; + +/** + * 类型转换器 + * + * @author boyue + */ +public class Convert +{ + /** + * 转换为字符串
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static String toStr(Object value, String defaultValue) + { + if (null == value) + { + return defaultValue; + } + if (value instanceof String) + { + return (String) value; + } + return value.toString(); + } + + /** + * 转换为字符串
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static String toStr(Object value) + { + return toStr(value, null); + } + + /** + * 转换为字符
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Character toChar(Object value, Character defaultValue) + { + if (null == value) + { + return defaultValue; + } + if (value instanceof Character) + { + return (Character) value; + } + + final String valueStr = toStr(value, null); + return StringUtils.isEmpty(valueStr) ? defaultValue : valueStr.charAt(0); + } + + /** + * 转换为字符
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Character toChar(Object value) + { + return toChar(value, null); + } + + /** + * 转换为byte
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Byte toByte(Object value, Byte defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Byte) + { + return (Byte) value; + } + if (value instanceof Number) + { + return ((Number) value).byteValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Byte.parseByte(valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为byte
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Byte toByte(Object value) + { + return toByte(value, null); + } + + /** + * 转换为Short
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Short toShort(Object value, Short defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Short) + { + return (Short) value; + } + if (value instanceof Number) + { + return ((Number) value).shortValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Short.parseShort(valueStr.trim()); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为Short
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Short toShort(Object value) + { + return toShort(value, null); + } + + /** + * 转换为Number
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Number toNumber(Object value, Number defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Number) + { + return (Number) value; + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return NumberFormat.getInstance().parse(valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为Number
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Number toNumber(Object value) + { + return toNumber(value, null); + } + + /** + * 转换为int
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Integer toInt(Object value, Integer defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Integer) + { + return (Integer) value; + } + if (value instanceof Number) + { + return ((Number) value).intValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Integer.parseInt(valueStr.trim()); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为int
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Integer toInt(Object value) + { + return toInt(value, null); + } + + /** + * 转换为Integer数组
+ * + * @param str 被转换的值 + * @return 结果 + */ + public static Integer[] toIntArray(String str) + { + return toIntArray(",", str); + } + + /** + * 转换为Long数组
+ * + * @param str 被转换的值 + * @return 结果 + */ + public static Long[] toLongArray(String str) + { + return toLongArray(",", str); + } + + /** + * 转换为Integer数组
+ * + * @param split 分隔符 + * @param split 被转换的值 + * @return 结果 + */ + public static Integer[] toIntArray(String split, String str) + { + if (StringUtils.isEmpty(str)) + { + return new Integer[] {}; + } + String[] arr = str.split(split); + final Integer[] ints = new Integer[arr.length]; + for (int i = 0; i < arr.length; i++) + { + final Integer v = toInt(arr[i], 0); + ints[i] = v; + } + return ints; + } + + /** + * 转换为Long数组
+ * + * @param split 分隔符 + * @param str 被转换的值 + * @return 结果 + */ + public static Long[] toLongArray(String split, String str) + { + if (StringUtils.isEmpty(str)) + { + return new Long[] {}; + } + String[] arr = str.split(split); + final Long[] longs = new Long[arr.length]; + for (int i = 0; i < arr.length; i++) + { + final Long v = toLong(arr[i], null); + longs[i] = v; + } + return longs; + } + + /** + * 转换为String数组
+ * + * @param str 被转换的值 + * @return 结果 + */ + public static String[] toStrArray(String str) + { + if (StringUtils.isEmpty(str)) + { + return new String[] {}; + } + return toStrArray(",", str); + } + + /** + * 转换为String数组
+ * + * @param split 分隔符 + * @param split 被转换的值 + * @return 结果 + */ + public static String[] toStrArray(String split, String str) + { + return str.split(split); + } + + /** + * 转换为long
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Long toLong(Object value, Long defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Long) + { + return (Long) value; + } + if (value instanceof Number) + { + return ((Number) value).longValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + // 支持科学计数法 + return new BigDecimal(valueStr.trim()).longValue(); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为long
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Long toLong(Object value) + { + return toLong(value, null); + } + + /** + * 转换为double
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Double toDouble(Object value, Double defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Double) + { + return (Double) value; + } + if (value instanceof Number) + { + return ((Number) value).doubleValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + // 支持科学计数法 + return new BigDecimal(valueStr.trim()).doubleValue(); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为double
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Double toDouble(Object value) + { + return toDouble(value, null); + } + + /** + * 转换为Float
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Float toFloat(Object value, Float defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Float) + { + return (Float) value; + } + if (value instanceof Number) + { + return ((Number) value).floatValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Float.parseFloat(valueStr.trim()); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为Float
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Float toFloat(Object value) + { + return toFloat(value, null); + } + + /** + * 转换为boolean
+ * String支持的值为:true、false、yes、ok、no,1,0 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Boolean toBool(Object value, Boolean defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Boolean) + { + return (Boolean) value; + } + String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + valueStr = valueStr.trim().toLowerCase(); + switch (valueStr) + { + case "true": + case "yes": + case "ok": + case "1": + return true; + case "false": + case "no": + case "0": + return false; + default: + return defaultValue; + } + } + + /** + * 转换为boolean
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Boolean toBool(Object value) + { + return toBool(value, null); + } + + /** + * 转换为Enum对象
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * + * @param clazz Enum的Class + * @param value 值 + * @param defaultValue 默认值 + * @return Enum + */ + public static > E toEnum(Class clazz, Object value, E defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (clazz.isAssignableFrom(value.getClass())) + { + @SuppressWarnings("unchecked") + E myE = (E) value; + return myE; + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Enum.valueOf(clazz, valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为Enum对象
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * + * @param clazz Enum的Class + * @param value 值 + * @return Enum + */ + public static > E toEnum(Class clazz, Object value) + { + return toEnum(clazz, value, null); + } + + /** + * 转换为BigInteger
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static BigInteger toBigInteger(Object value, BigInteger defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof BigInteger) + { + return (BigInteger) value; + } + if (value instanceof Long) + { + return BigInteger.valueOf((Long) value); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return new BigInteger(valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为BigInteger
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static BigInteger toBigInteger(Object value) + { + return toBigInteger(value, null); + } + + /** + * 转换为BigDecimal
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static BigDecimal toBigDecimal(Object value, BigDecimal defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof BigDecimal) + { + return (BigDecimal) value; + } + if (value instanceof Long) + { + return new BigDecimal((Long) value); + } + if (value instanceof Double) + { + return BigDecimal.valueOf((Double) value); + } + if (value instanceof Integer) + { + return new BigDecimal((Integer) value); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return new BigDecimal(valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为BigDecimal
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static BigDecimal toBigDecimal(Object value) + { + return toBigDecimal(value, null); + } + + /** + * 将对象转为字符串
+ * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * + * @param obj 对象 + * @return 字符串 + */ + public static String utf8Str(Object obj) + { + return str(obj, CharsetKit.CHARSET_UTF_8); + } + + /** + * 将对象转为字符串
+ * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * + * @param obj 对象 + * @param charsetName 字符集 + * @return 字符串 + */ + public static String str(Object obj, String charsetName) + { + return str(obj, Charset.forName(charsetName)); + } + + /** + * 将对象转为字符串
+ * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * + * @param obj 对象 + * @param charset 字符集 + * @return 字符串 + */ + public static String str(Object obj, Charset charset) + { + if (null == obj) + { + return null; + } + + if (obj instanceof String) + { + return (String) obj; + } + else if (obj instanceof byte[]) + { + return str((byte[]) obj, charset); + } + else if (obj instanceof Byte[]) + { + byte[] bytes = ArrayUtils.toPrimitive((Byte[]) obj); + return str(bytes, charset); + } + else if (obj instanceof ByteBuffer) + { + return str((ByteBuffer) obj, charset); + } + return obj.toString(); + } + + /** + * 将byte数组转为字符串 + * + * @param bytes byte数组 + * @param charset 字符集 + * @return 字符串 + */ + public static String str(byte[] bytes, String charset) + { + return str(bytes, StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset)); + } + + /** + * 解码字节码 + * + * @param data 字符串 + * @param charset 字符集,如果此字段为空,则解码的结果取决于平台 + * @return 解码后的字符串 + */ + public static String str(byte[] data, Charset charset) + { + if (data == null) + { + return null; + } + + if (null == charset) + { + return new String(data); + } + return new String(data, charset); + } + + /** + * 将编码的byteBuffer数据转换为字符串 + * + * @param data 数据 + * @param charset 字符集,如果为空使用当前系统字符集 + * @return 字符串 + */ + public static String str(ByteBuffer data, String charset) + { + if (data == null) + { + return null; + } + + return str(data, Charset.forName(charset)); + } + + /** + * 将编码的byteBuffer数据转换为字符串 + * + * @param data 数据 + * @param charset 字符集,如果为空使用当前系统字符集 + * @return 字符串 + */ + public static String str(ByteBuffer data, Charset charset) + { + if (null == charset) + { + charset = Charset.defaultCharset(); + } + return charset.decode(data).toString(); + } + + // ----------------------------------------------------------------------- 全角半角转换 + /** + * 半角转全角 + * + * @param input String. + * @return 全角字符串. + */ + public static String toSBC(String input) + { + return toSBC(input, null); + } + + /** + * 半角转全角 + * + * @param input String + * @param notConvertSet 不替换的字符集合 + * @return 全角字符串. + */ + public static String toSBC(String input, Set notConvertSet) + { + char[] c = input.toCharArray(); + for (int i = 0; i < c.length; i++) + { + if (null != notConvertSet && notConvertSet.contains(c[i])) + { + // 跳过不替换的字符 + continue; + } + + if (c[i] == ' ') + { + c[i] = '\u3000'; + } + else if (c[i] < '\177') + { + c[i] = (char) (c[i] + 65248); + + } + } + return new String(c); + } + + /** + * 全角转半角 + * + * @param input String. + * @return 半角字符串 + */ + public static String toDBC(String input) + { + return toDBC(input, null); + } + + /** + * 替换全角为半角 + * + * @param text 文本 + * @param notConvertSet 不替换的字符集合 + * @return 替换后的字符 + */ + public static String toDBC(String text, Set notConvertSet) + { + char[] c = text.toCharArray(); + for (int i = 0; i < c.length; i++) + { + if (null != notConvertSet && notConvertSet.contains(c[i])) + { + // 跳过不替换的字符 + continue; + } + + if (c[i] == '\u3000') + { + c[i] = ' '; + } + else if (c[i] > '\uFF00' && c[i] < '\uFF5F') + { + c[i] = (char) (c[i] - 65248); + } + } + String returnString = new String(c); + + return returnString; + } + + /** + * 数字金额大写转换 先写个完整的然后将如零拾替换成零 + * + * @param n 数字 + * @return 中文大写数字 + */ + public static String digitUppercase(double n) + { + String[] fraction = { "角", "分" }; + String[] digit = { "零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖" }; + String[][] unit = { { "元", "万", "亿" }, { "", "拾", "佰", "仟" } }; + + String head = n < 0 ? "负" : ""; + n = Math.abs(n); + + String s = ""; + for (int i = 0; i < fraction.length; i++) + { + s += (digit[(int) (Math.floor(n * 10 * Math.pow(10, i)) % 10)] + fraction[i]).replaceAll("(零.)+", ""); + } + if (s.length() < 1) + { + s = "整"; + } + int integerPart = (int) Math.floor(n); + + for (int i = 0; i < unit[0].length && integerPart > 0; i++) + { + String p = ""; + for (int j = 0; j < unit[1].length && n > 0; j++) + { + p = digit[integerPart % 10] + unit[1][j] + p; + integerPart = integerPart / 10; + } + s = p.replaceAll("(零.)*零$", "").replaceAll("^$", "零") + unit[0][i] + s; + } + return head + s.replaceAll("(零.)*零元", "元").replaceFirst("(零.)+", "").replaceAll("(零.)+", "零").replaceAll("^整$", "零元整"); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/core/text/StrFormatter.java b/boyue-common/src/main/java/com/boyue/common/core/text/StrFormatter.java new file mode 100644 index 0000000..97af39d --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/core/text/StrFormatter.java @@ -0,0 +1,92 @@ +package com.boyue.common.core.text; + +import com.boyue.common.utils.StringUtils; + +/** + * 字符串格式化 + * + * @author boyue + */ +public class StrFormatter +{ + public static final String EMPTY_JSON = "{}"; + public static final char C_BACKSLASH = '\\'; + public static final char C_DELIM_START = '{'; + public static final char C_DELIM_END = '}'; + + /** + * 格式化字符串
+ * 此方法只是简单将占位符 {} 按照顺序替换为参数
+ * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
+ * 例:
+ * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
+ * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a
+ * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
+ * + * @param strPattern 字符串模板 + * @param argArray 参数列表 + * @return 结果 + */ + public static String format(final String strPattern, final Object... argArray) + { + if (StringUtils.isEmpty(strPattern) || StringUtils.isEmpty(argArray)) + { + return strPattern; + } + final int strPatternLength = strPattern.length(); + + // 初始化定义好的长度以获得更好的性能 + StringBuilder sbuf = new StringBuilder(strPatternLength + 50); + + int handledPosition = 0; + int delimIndex;// 占位符所在位置 + for (int argIndex = 0; argIndex < argArray.length; argIndex++) + { + delimIndex = strPattern.indexOf(EMPTY_JSON, handledPosition); + if (delimIndex == -1) + { + if (handledPosition == 0) + { + return strPattern; + } + else + { // 字符串模板剩余部分不再包含占位符,加入剩余部分后返回结果 + sbuf.append(strPattern, handledPosition, strPatternLength); + return sbuf.toString(); + } + } + else + { + if (delimIndex > 0 && strPattern.charAt(delimIndex - 1) == C_BACKSLASH) + { + if (delimIndex > 1 && strPattern.charAt(delimIndex - 2) == C_BACKSLASH) + { + // 转义符之前还有一个转义符,占位符依旧有效 + sbuf.append(strPattern, handledPosition, delimIndex - 1); + sbuf.append(Convert.utf8Str(argArray[argIndex])); + handledPosition = delimIndex + 2; + } + else + { + // 占位符被转义 + argIndex--; + sbuf.append(strPattern, handledPosition, delimIndex - 1); + sbuf.append(C_DELIM_START); + handledPosition = delimIndex + 1; + } + } + else + { + // 正常占位符 + sbuf.append(strPattern, handledPosition, delimIndex); + sbuf.append(Convert.utf8Str(argArray[argIndex])); + handledPosition = delimIndex + 2; + } + } + } + // 加入最后一个占位符后所有的字符 + sbuf.append(strPattern, handledPosition, strPattern.length()); + + return sbuf.toString(); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/enums/BusinessStatus.java b/boyue-common/src/main/java/com/boyue/common/enums/BusinessStatus.java new file mode 100644 index 0000000..761d2ff --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/enums/BusinessStatus.java @@ -0,0 +1,20 @@ +package com.boyue.common.enums; + +/** + * 操作状态 + * + * @author boyue + * + */ +public enum BusinessStatus +{ + /** + * 成功 + */ + SUCCESS, + + /** + * 失败 + */ + FAIL, +} diff --git a/boyue-common/src/main/java/com/boyue/common/enums/BusinessType.java b/boyue-common/src/main/java/com/boyue/common/enums/BusinessType.java new file mode 100644 index 0000000..34a4dcb --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/enums/BusinessType.java @@ -0,0 +1,59 @@ +package com.boyue.common.enums; + +/** + * 业务操作类型 + * + * @author boyue + */ +public enum BusinessType +{ + /** + * 其它 + */ + OTHER, + + /** + * 新增 + */ + INSERT, + + /** + * 修改 + */ + UPDATE, + + /** + * 删除 + */ + DELETE, + + /** + * 授权 + */ + GRANT, + + /** + * 导出 + */ + EXPORT, + + /** + * 导入 + */ + IMPORT, + + /** + * 强退 + */ + FORCE, + + /** + * 生成代码 + */ + GENCODE, + + /** + * 清空数据 + */ + CLEAN, +} diff --git a/boyue-common/src/main/java/com/boyue/common/enums/DataSourceType.java b/boyue-common/src/main/java/com/boyue/common/enums/DataSourceType.java new file mode 100644 index 0000000..4622cb9 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/enums/DataSourceType.java @@ -0,0 +1,18 @@ +package com.boyue.common.enums; + +/** + * 数据源 + * + * @author boyue + */ +public enum DataSourceType { + /** + * 主库 + */ + MASTER, + + /** + * 从库 + */ + SLAVE, +} diff --git a/boyue-common/src/main/java/com/boyue/common/enums/DesensitizedType.java b/boyue-common/src/main/java/com/boyue/common/enums/DesensitizedType.java new file mode 100644 index 0000000..2b62ad9 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/enums/DesensitizedType.java @@ -0,0 +1,60 @@ +package com.boyue.common.enums; + +import java.util.function.Function; + +import com.boyue.common.utils.DesensitizedUtil; + +/** + * 脱敏类型 + * + * @author boyue + */ +public enum DesensitizedType +{ + /** + * 姓名,第2位星号替换 + */ + USERNAME(s -> s.replaceAll("(\\S)\\S(\\S*)", "$1*$2")), + + /** + * 密码,全部字符都用*代替 + */ + PASSWORD(DesensitizedUtil::password), + + /** + * 身份证,中间10位星号替换 + */ + ID_CARD(s -> s.replaceAll("(\\d{4})\\d{10}(\\d{3}[Xx]|\\d{4})", "$1** **** ****$2")), + + /** + * 手机号,中间4位星号替换 + */ + PHONE(s -> s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")), + + /** + * 电子邮箱,仅显示第一个字母和@后面的地址显示,其他星号替换 + */ + EMAIL(s -> s.replaceAll("(^.)[^@]*(@.*$)", "$1****$2")), + + /** + * 银行卡号,保留最后4位,其他星号替换 + */ + BANK_CARD(s -> s.replaceAll("\\d{15}(\\d{3})", "**** **** **** **** $1")), + + /** + * 车牌号码,包含普通车辆、新能源车辆 + */ + CAR_LICENSE(DesensitizedUtil::carLicense); + + private final Function desensitizer; + + DesensitizedType(Function desensitizer) + { + this.desensitizer = desensitizer; + } + + public Function desensitizer() + { + return desensitizer; + } +} \ No newline at end of file diff --git a/boyue-common/src/main/java/com/boyue/common/enums/HttpMethod.java b/boyue-common/src/main/java/com/boyue/common/enums/HttpMethod.java new file mode 100644 index 0000000..6e2f46d --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/enums/HttpMethod.java @@ -0,0 +1,52 @@ +package com.boyue.common.enums; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.lang.Nullable; + +/** + * 请求方式 + * + * @author boyue + */ +public enum HttpMethod +{ + /** GET 请求 */ + GET, + /** POST 请求 */ + HEAD, + /** HEAD 请求 */ + POST, + /** PUT 请求 */ + PUT, + /** PATCH 请求 */ + PATCH, + /** DELETE 请求 */ + DELETE, + /** OPTIONS 请求 */ + OPTIONS, + /** TRACE 请求 */ + TRACE; + + private static final Map mappings = new HashMap<>(16); + + static + { + for (HttpMethod httpMethod : values()) + { + mappings.put(httpMethod.name(), httpMethod); + } + } + + @Nullable + public static HttpMethod resolve(@Nullable String method) + { + return (method != null ? mappings.get(method) : null); + } + + public boolean matches(String method) + { + return (this == resolve(method)); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/enums/LimitType.java b/boyue-common/src/main/java/com/boyue/common/enums/LimitType.java new file mode 100644 index 0000000..ea1fe50 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/enums/LimitType.java @@ -0,0 +1,29 @@ +package com.boyue.common.enums; + +/** + * 限流类型 + * + * @author boyue + */ + +public enum LimitType { + /** + * 默认策略全局限流 + */ + DEFAULT, + + /** + * 根据请求者IP进行限流 + */ + IP, + + /** + * 根据请求者的用户ID进行限流 + */ + USER, + + /** + * 根据请求者的部门进行限流 + */ + DEPT, +} diff --git a/boyue-common/src/main/java/com/boyue/common/enums/OperatorType.java b/boyue-common/src/main/java/com/boyue/common/enums/OperatorType.java new file mode 100644 index 0000000..5306015 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/enums/OperatorType.java @@ -0,0 +1,24 @@ +package com.boyue.common.enums; + +/** + * 操作人类别 + * + * @author boyue + */ +public enum OperatorType +{ + /** + * 其它 + */ + OTHER, + + /** + * 后台用户 + */ + MANAGE, + + /** + * 手机端用户 + */ + MOBILE +} diff --git a/boyue-common/src/main/java/com/boyue/common/enums/UserStatus.java b/boyue-common/src/main/java/com/boyue/common/enums/UserStatus.java new file mode 100644 index 0000000..8240e4b --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/enums/UserStatus.java @@ -0,0 +1,35 @@ +package com.boyue.common.enums; + +/** + * 用户状态 + * + * @author boyue + */ +public enum UserStatus +{ + /** 正常 */ + OK("0", "正常"), + /** 停用 */ + DISABLE("1", "停用"), + /** 删除 */ + DELETED("2", "删除"); + + private final String code; + private final String info; + + UserStatus(String code, String info) + { + this.code = code; + this.info = info; + } + + public String getCode() + { + return code; + } + + public String getInfo() + { + return info; + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/exception/DemoModeException.java b/boyue-common/src/main/java/com/boyue/common/exception/DemoModeException.java new file mode 100644 index 0000000..ba7c514 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/exception/DemoModeException.java @@ -0,0 +1,15 @@ +package com.boyue.common.exception; + +/** + * 演示模式异常 + * + * @author boyue + */ +public class DemoModeException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + public DemoModeException() + { + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/exception/GlobalException.java b/boyue-common/src/main/java/com/boyue/common/exception/GlobalException.java new file mode 100644 index 0000000..73e74fd --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/exception/GlobalException.java @@ -0,0 +1,58 @@ +package com.boyue.common.exception; + +/** + * 全局异常 + * + * @author boyue + */ +public class GlobalException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + /** + * 错误提示 + */ + private String message; + + /** + * 错误明细,内部调试错误 + * + * 和 {@link CommonResult#getDetailMessage()} 一致的设计 + */ + private String detailMessage; + + /** + * 空构造方法,避免反序列化问题 + */ + public GlobalException() + { + } + + public GlobalException(String message) + { + this.message = message; + } + + public String getDetailMessage() + { + return detailMessage; + } + + public GlobalException setDetailMessage(String detailMessage) + { + this.detailMessage = detailMessage; + return this; + } + + @Override + public String getMessage() + { + return message; + } + + public GlobalException setMessage(String message) + { + this.message = message; + return this; + } +} \ No newline at end of file diff --git a/boyue-common/src/main/java/com/boyue/common/exception/ServiceException.java b/boyue-common/src/main/java/com/boyue/common/exception/ServiceException.java new file mode 100644 index 0000000..249ff81 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/exception/ServiceException.java @@ -0,0 +1,74 @@ +package com.boyue.common.exception; + +/** + * 业务异常 + * + * @author boyue + */ +public final class ServiceException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + /** + * 错误码 + */ + private Integer code; + + /** + * 错误提示 + */ + private String message; + + /** + * 错误明细,内部调试错误 + * + * 和 {@link CommonResult#getDetailMessage()} 一致的设计 + */ + private String detailMessage; + + /** + * 空构造方法,避免反序列化问题 + */ + public ServiceException() + { + } + + public ServiceException(String message) + { + this.message = message; + } + + public ServiceException(String message, Integer code) + { + this.message = message; + this.code = code; + } + + public String getDetailMessage() + { + return detailMessage; + } + + @Override + public String getMessage() + { + return message; + } + + public Integer getCode() + { + return code; + } + + public ServiceException setMessage(String message) + { + this.message = message; + return this; + } + + public ServiceException setDetailMessage(String detailMessage) + { + this.detailMessage = detailMessage; + return this; + } +} \ No newline at end of file diff --git a/boyue-common/src/main/java/com/boyue/common/exception/UtilException.java b/boyue-common/src/main/java/com/boyue/common/exception/UtilException.java new file mode 100644 index 0000000..49f868f --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/exception/UtilException.java @@ -0,0 +1,26 @@ +package com.boyue.common.exception; + +/** + * 工具类异常 + * + * @author boyue + */ +public class UtilException extends RuntimeException +{ + private static final long serialVersionUID = 8247610319171014183L; + + public UtilException(Throwable e) + { + super(e.getMessage(), e); + } + + public UtilException(String message) + { + super(message); + } + + public UtilException(String message, Throwable throwable) + { + super(message, throwable); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/exception/base/BaseException.java b/boyue-common/src/main/java/com/boyue/common/exception/base/BaseException.java new file mode 100644 index 0000000..4cd33f2 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/exception/base/BaseException.java @@ -0,0 +1,97 @@ +package com.boyue.common.exception.base; + +import com.boyue.common.utils.MessageUtils; +import com.boyue.common.utils.StringUtils; + +/** + * 基础异常 + * + * @author boyue + */ +public class BaseException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + /** + * 所属模块 + */ + private String module; + + /** + * 错误码 + */ + private String code; + + /** + * 错误码对应的参数 + */ + private Object[] args; + + /** + * 错误消息 + */ + private String defaultMessage; + + public BaseException(String module, String code, Object[] args, String defaultMessage) + { + this.module = module; + this.code = code; + this.args = args; + this.defaultMessage = defaultMessage; + } + + public BaseException(String module, String code, Object[] args) + { + this(module, code, args, null); + } + + public BaseException(String module, String defaultMessage) + { + this(module, null, null, defaultMessage); + } + + public BaseException(String code, Object[] args) + { + this(null, code, args, null); + } + + public BaseException(String defaultMessage) + { + this(null, null, null, defaultMessage); + } + + @Override + public String getMessage() + { + String message = null; + if (!StringUtils.isEmpty(code)) + { + message = MessageUtils.message(code, args); + } + if (message == null) + { + message = defaultMessage; + } + return message; + } + + public String getModule() + { + return module; + } + + public String getCode() + { + return code; + } + + public Object[] getArgs() + { + return args; + } + + public String getDefaultMessage() + { + return defaultMessage; + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/exception/file/FileException.java b/boyue-common/src/main/java/com/boyue/common/exception/file/FileException.java new file mode 100644 index 0000000..039bc6e --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/exception/file/FileException.java @@ -0,0 +1,19 @@ +package com.boyue.common.exception.file; + +import com.boyue.common.exception.base.BaseException; + +/** + * 文件信息异常类 + * + * @author boyue + */ +public class FileException extends BaseException +{ + private static final long serialVersionUID = 1L; + + public FileException(String code, Object[] args) + { + super("file", code, args, null); + } + +} diff --git a/boyue-common/src/main/java/com/boyue/common/exception/file/FileNameLengthLimitExceededException.java b/boyue-common/src/main/java/com/boyue/common/exception/file/FileNameLengthLimitExceededException.java new file mode 100644 index 0000000..1609109 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/exception/file/FileNameLengthLimitExceededException.java @@ -0,0 +1,16 @@ +package com.boyue.common.exception.file; + +/** + * 文件名称超长限制异常类 + * + * @author boyue + */ +public class FileNameLengthLimitExceededException extends FileException +{ + private static final long serialVersionUID = 1L; + + public FileNameLengthLimitExceededException(int defaultFileNameLength) + { + super("upload.filename.exceed.length", new Object[] { defaultFileNameLength }); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/exception/file/FileSizeLimitExceededException.java b/boyue-common/src/main/java/com/boyue/common/exception/file/FileSizeLimitExceededException.java new file mode 100644 index 0000000..9272a2f --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/exception/file/FileSizeLimitExceededException.java @@ -0,0 +1,16 @@ +package com.boyue.common.exception.file; + +/** + * 文件名大小限制异常类 + * + * @author boyue + */ +public class FileSizeLimitExceededException extends FileException +{ + private static final long serialVersionUID = 1L; + + public FileSizeLimitExceededException(long defaultMaxSize) + { + super("upload.exceed.maxSize", new Object[] { defaultMaxSize }); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/exception/file/FileUploadException.java b/boyue-common/src/main/java/com/boyue/common/exception/file/FileUploadException.java new file mode 100644 index 0000000..5b8eb21 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/exception/file/FileUploadException.java @@ -0,0 +1,61 @@ +package com.boyue.common.exception.file; + +import java.io.PrintStream; +import java.io.PrintWriter; + +/** + * 文件上传异常类 + * + * @author boyue + */ +public class FileUploadException extends Exception +{ + + private static final long serialVersionUID = 1L; + + private final Throwable cause; + + public FileUploadException() + { + this(null, null); + } + + public FileUploadException(final String msg) + { + this(msg, null); + } + + public FileUploadException(String msg, Throwable cause) + { + super(msg); + this.cause = cause; + } + + @Override + public void printStackTrace(PrintStream stream) + { + super.printStackTrace(stream); + if (cause != null) + { + stream.println("Caused by:"); + cause.printStackTrace(stream); + } + } + + @Override + public void printStackTrace(PrintWriter writer) + { + super.printStackTrace(writer); + if (cause != null) + { + writer.println("Caused by:"); + cause.printStackTrace(writer); + } + } + + @Override + public Throwable getCause() + { + return cause; + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/exception/file/InvalidExtensionException.java b/boyue-common/src/main/java/com/boyue/common/exception/file/InvalidExtensionException.java new file mode 100644 index 0000000..c1fc58c --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/exception/file/InvalidExtensionException.java @@ -0,0 +1,80 @@ +package com.boyue.common.exception.file; + +import java.util.Arrays; + +/** + * 文件上传 误异常类 + * + * @author boyue + */ +public class InvalidExtensionException extends FileUploadException +{ + private static final long serialVersionUID = 1L; + + private String[] allowedExtension; + private String extension; + private String filename; + + public InvalidExtensionException(String[] allowedExtension, String extension, String filename) + { + super("文件[" + filename + "]后缀[" + extension + "]不正确,请上传" + Arrays.toString(allowedExtension) + "格式"); + this.allowedExtension = allowedExtension; + this.extension = extension; + this.filename = filename; + } + + public String[] getAllowedExtension() + { + return allowedExtension; + } + + public String getExtension() + { + return extension; + } + + public String getFilename() + { + return filename; + } + + public static class InvalidImageExtensionException extends InvalidExtensionException + { + private static final long serialVersionUID = 1L; + + public InvalidImageExtensionException(String[] allowedExtension, String extension, String filename) + { + super(allowedExtension, extension, filename); + } + } + + public static class InvalidFlashExtensionException extends InvalidExtensionException + { + private static final long serialVersionUID = 1L; + + public InvalidFlashExtensionException(String[] allowedExtension, String extension, String filename) + { + super(allowedExtension, extension, filename); + } + } + + public static class InvalidMediaExtensionException extends InvalidExtensionException + { + private static final long serialVersionUID = 1L; + + public InvalidMediaExtensionException(String[] allowedExtension, String extension, String filename) + { + super(allowedExtension, extension, filename); + } + } + + public static class InvalidVideoExtensionException extends InvalidExtensionException + { + private static final long serialVersionUID = 1L; + + public InvalidVideoExtensionException(String[] allowedExtension, String extension, String filename) + { + super(allowedExtension, extension, filename); + } + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/exception/job/TaskException.java b/boyue-common/src/main/java/com/boyue/common/exception/job/TaskException.java new file mode 100644 index 0000000..7e09689 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/exception/job/TaskException.java @@ -0,0 +1,40 @@ +package com.boyue.common.exception.job; + +/** + * 计划策略异常 + * + * @author boyue + */ +public class TaskException extends Exception { + private static final long serialVersionUID = 1L; + + private Code code; + + public TaskException(String msg, Code code) { + this(msg, code, null); + } + + public TaskException(String msg, Code code, Exception nestedEx) { + super(msg, nestedEx); + this.code = code; + } + + public Code getCode() { + return code; + } + + public enum Code { + /** 任务存在 */ + TASK_EXISTS, + /** 任务不存在 */ + NO_TASK_EXISTS, + /** 任务已经开始 */ + TASK_ALREADY_STARTED, + /** 未知 */ + UNKNOWN, + /** 配置错误 */ + CONFIG_ERROR, + /** 任务节点不可用 */ + TASK_NODE_NOT_AVAILABLE + } +} \ No newline at end of file diff --git a/boyue-common/src/main/java/com/boyue/common/exception/user/BlackListException.java b/boyue-common/src/main/java/com/boyue/common/exception/user/BlackListException.java new file mode 100644 index 0000000..d5b5844 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/exception/user/BlackListException.java @@ -0,0 +1,16 @@ +package com.boyue.common.exception.user; + +/** + * 黑名单IP异常类 + * + * @author boyue + */ +public class BlackListException extends UserException +{ + private static final long serialVersionUID = 1L; + + public BlackListException() + { + super("login.blocked", null); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/exception/user/CaptchaException.java b/boyue-common/src/main/java/com/boyue/common/exception/user/CaptchaException.java new file mode 100644 index 0000000..e862ae2 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/exception/user/CaptchaException.java @@ -0,0 +1,16 @@ +package com.boyue.common.exception.user; + +/** + * 验证码错误异常类 + * + * @author boyue + */ +public class CaptchaException extends UserException +{ + private static final long serialVersionUID = 1L; + + public CaptchaException() + { + super("user.jcaptcha.error", null); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/exception/user/CaptchaExpireException.java b/boyue-common/src/main/java/com/boyue/common/exception/user/CaptchaExpireException.java new file mode 100644 index 0000000..6afbd22 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/exception/user/CaptchaExpireException.java @@ -0,0 +1,16 @@ +package com.boyue.common.exception.user; + +/** + * 验证码失效异常类 + * + * @author boyue + */ +public class CaptchaExpireException extends UserException +{ + private static final long serialVersionUID = 1L; + + public CaptchaExpireException() + { + super("user.jcaptcha.expire", null); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/exception/user/IpRetryLimitExceedException.java b/boyue-common/src/main/java/com/boyue/common/exception/user/IpRetryLimitExceedException.java new file mode 100644 index 0000000..d58b3af --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/exception/user/IpRetryLimitExceedException.java @@ -0,0 +1,15 @@ +package com.boyue.common.exception.user; + +/** + * IP 登录重试次数超限异常类 + * + */ +public class IpRetryLimitExceedException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + public IpRetryLimitExceedException(int retryLimitCount, int lockTime) + { + super("失败次数过多,你的ip暂时被限制登录."); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/exception/user/UserException.java b/boyue-common/src/main/java/com/boyue/common/exception/user/UserException.java new file mode 100644 index 0000000..877d0b8 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/exception/user/UserException.java @@ -0,0 +1,18 @@ +package com.boyue.common.exception.user; + +import com.boyue.common.exception.base.BaseException; + +/** + * 用户信息异常类 + * + * @author boyue + */ +public class UserException extends BaseException +{ + private static final long serialVersionUID = 1L; + + public UserException(String code, Object[] args) + { + super("user", code, args, null); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/exception/user/UserNotExistsException.java b/boyue-common/src/main/java/com/boyue/common/exception/user/UserNotExistsException.java new file mode 100644 index 0000000..0dfc9b0 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/exception/user/UserNotExistsException.java @@ -0,0 +1,16 @@ +package com.boyue.common.exception.user; + +/** + * 用户不存在异常类 + * + * @author boyue + */ +public class UserNotExistsException extends UserException +{ + private static final long serialVersionUID = 1L; + + public UserNotExistsException() + { + super("user.not.exists", null); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/exception/user/UserPasswordNotMatchException.java b/boyue-common/src/main/java/com/boyue/common/exception/user/UserPasswordNotMatchException.java new file mode 100644 index 0000000..2435666 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/exception/user/UserPasswordNotMatchException.java @@ -0,0 +1,16 @@ +package com.boyue.common.exception.user; + +/** + * 用户密码不正确或不符合规范异常类 + * + * @author boyue + */ +public class UserPasswordNotMatchException extends UserException +{ + private static final long serialVersionUID = 1L; + + public UserPasswordNotMatchException() + { + super("user.password.not.match", null); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/exception/user/UserPasswordRetryLimitExceedException.java b/boyue-common/src/main/java/com/boyue/common/exception/user/UserPasswordRetryLimitExceedException.java new file mode 100644 index 0000000..c906d0d --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/exception/user/UserPasswordRetryLimitExceedException.java @@ -0,0 +1,16 @@ +package com.boyue.common.exception.user; + +/** + * 用户错误最大次数异常类 + * + * @author boyue + */ +public class UserPasswordRetryLimitExceedException extends UserException +{ + private static final long serialVersionUID = 1L; + + public UserPasswordRetryLimitExceedException(int retryLimitCount, int lockTime) + { + super("user.password.retry.limit.exceed", new Object[] { retryLimitCount, lockTime }); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/filter/PropertyPreExcludeFilter.java b/boyue-common/src/main/java/com/boyue/common/filter/PropertyPreExcludeFilter.java new file mode 100644 index 0000000..d2e624a --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/filter/PropertyPreExcludeFilter.java @@ -0,0 +1,24 @@ +package com.boyue.common.filter; + +import com.alibaba.fastjson2.filter.SimplePropertyPreFilter; + +/** + * 排除JSON敏感属性 + * + * @author boyue + */ +public class PropertyPreExcludeFilter extends SimplePropertyPreFilter +{ + public PropertyPreExcludeFilter() + { + } + + public PropertyPreExcludeFilter addExcludes(String... filters) + { + for (int i = 0; i < filters.length; i++) + { + this.getExcludes().add(filters[i]); + } + return this; + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/filter/RepeatableFilter.java b/boyue-common/src/main/java/com/boyue/common/filter/RepeatableFilter.java new file mode 100644 index 0000000..70f4d63 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/filter/RepeatableFilter.java @@ -0,0 +1,52 @@ +package com.boyue.common.filter; + +import java.io.IOException; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.http.MediaType; +import com.boyue.common.utils.StringUtils; + +/** + * Repeatable 过滤器 + * + * @author boyue + */ +public class RepeatableFilter implements Filter +{ + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + ServletRequest requestWrapper = null; + if (request instanceof HttpServletRequest + && StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) + { + requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response); + } + if (null == requestWrapper) + { + chain.doFilter(request, response); + } + else + { + chain.doFilter(requestWrapper, response); + } + } + + @Override + public void destroy() + { + + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/filter/RepeatedlyRequestWrapper.java b/boyue-common/src/main/java/com/boyue/common/filter/RepeatedlyRequestWrapper.java new file mode 100644 index 0000000..08f5e30 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/filter/RepeatedlyRequestWrapper.java @@ -0,0 +1,76 @@ +package com.boyue.common.filter; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; +import com.boyue.common.utils.http.HttpHelper; +import com.boyue.common.constant.Constants; + +/** + * 构建可重复读取inputStream的request + * + * @author boyue + */ +public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper +{ + private final byte[] body; + + public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException + { + super(request); + request.setCharacterEncoding(Constants.UTF8); + response.setCharacterEncoding(Constants.UTF8); + + body = HttpHelper.getBodyString(request).getBytes(Constants.UTF8); + } + + @Override + public BufferedReader getReader() throws IOException + { + return new BufferedReader(new InputStreamReader(getInputStream())); + } + + @Override + public ServletInputStream getInputStream() throws IOException + { + final ByteArrayInputStream bais = new ByteArrayInputStream(body); + return new ServletInputStream() + { + @Override + public int read() throws IOException + { + return bais.read(); + } + + @Override + public int available() throws IOException + { + return body.length; + } + + @Override + public boolean isFinished() + { + return false; + } + + @Override + public boolean isReady() + { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) + { + + } + }; + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/filter/XssFilter.java b/boyue-common/src/main/java/com/boyue/common/filter/XssFilter.java new file mode 100644 index 0000000..34cf439 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/filter/XssFilter.java @@ -0,0 +1,75 @@ +package com.boyue.common.filter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.enums.HttpMethod; + +/** + * 防止XSS攻击的过滤器 + * + * @author boyue + */ +public class XssFilter implements Filter +{ + /** + * 排除链接 + */ + public List excludes = new ArrayList<>(); + + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + String tempExcludes = filterConfig.getInitParameter("excludes"); + if (StringUtils.isNotEmpty(tempExcludes)) + { + String[] url = tempExcludes.split(","); + for (int i = 0; url != null && i < url.length; i++) + { + excludes.add(url[i]); + } + } + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse resp = (HttpServletResponse) response; + if (handleExcludeURL(req, resp)) + { + chain.doFilter(request, response); + return; + } + XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request); + chain.doFilter(xssRequest, response); + } + + private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) + { + String url = request.getServletPath(); + String method = request.getMethod(); + // GET DELETE 不过滤 + if (method == null || HttpMethod.GET.matches(method) || HttpMethod.DELETE.matches(method)) + { + return true; + } + return StringUtils.matches(url, excludes); + } + + @Override + public void destroy() + { + + } +} \ No newline at end of file diff --git a/boyue-common/src/main/java/com/boyue/common/filter/XssHttpServletRequestWrapper.java b/boyue-common/src/main/java/com/boyue/common/filter/XssHttpServletRequestWrapper.java new file mode 100644 index 0000000..b67fff9 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/filter/XssHttpServletRequestWrapper.java @@ -0,0 +1,111 @@ +package com.boyue.common.filter; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; +import org.apache.commons.io.IOUtils; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.html.EscapeUtil; + +/** + * XSS过滤处理 + * + * @author boyue + */ +public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper +{ + /** + * @param request + */ + public XssHttpServletRequestWrapper(HttpServletRequest request) + { + super(request); + } + + @Override + public String[] getParameterValues(String name) + { + String[] values = super.getParameterValues(name); + if (values != null) + { + int length = values.length; + String[] escapesValues = new String[length]; + for (int i = 0; i < length; i++) + { + // 防xss攻击和过滤前后空格 + escapesValues[i] = EscapeUtil.clean(values[i]).trim(); + } + return escapesValues; + } + return super.getParameterValues(name); + } + + @Override + public ServletInputStream getInputStream() throws IOException + { + // 非json类型,直接返回 + if (!isJsonRequest()) + { + return super.getInputStream(); + } + + // 为空,直接返回 + String json = IOUtils.toString(super.getInputStream(), "utf-8"); + if (StringUtils.isEmpty(json)) + { + return super.getInputStream(); + } + + // xss过滤 + json = EscapeUtil.clean(json).trim(); + byte[] jsonBytes = json.getBytes("utf-8"); + final ByteArrayInputStream bis = new ByteArrayInputStream(jsonBytes); + return new ServletInputStream() + { + @Override + public boolean isFinished() + { + return true; + } + + @Override + public boolean isReady() + { + return true; + } + + @Override + public int available() throws IOException + { + return jsonBytes.length; + } + + @Override + public void setReadListener(ReadListener readListener) + { + } + + @Override + public int read() throws IOException + { + return bis.read(); + } + }; + } + + /** + * 是否是Json请求 + * + * @param request + */ + public boolean isJsonRequest() + { + String header = super.getHeader(HttpHeaders.CONTENT_TYPE); + return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE); + } +} \ No newline at end of file diff --git a/boyue-common/src/main/java/com/boyue/common/handler/GenericListTypeHandler.java b/boyue-common/src/main/java/com/boyue/common/handler/GenericListTypeHandler.java new file mode 100644 index 0000000..ffb967c --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/handler/GenericListTypeHandler.java @@ -0,0 +1,131 @@ +package com.boyue.common.handler; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import org.apache.ibatis.type.BaseTypeHandler; +import org.apache.ibatis.type.JdbcType; + +import com.boyue.common.utils.StringUtils; + +/** + * 通用的List类型处理器,可处理任何元素类型 + * + * 用法示例: + * 1. 在XML中配置: + * {@code } + * + * 2. 在字段上使用注解: + * {@code @Result(column="tags", property="tags", + * typeHandler=GenericListTypeHandler.StringList.class)} + * + * 3. 在xml插值语法中使用 + * {@code #{tags,typeHandler=handler.com.boyue.common.GenericListTypeHandler$StringList} + * } + * + * @param 列表元素类型 + */ +public class GenericListTypeHandler extends BaseTypeHandler> { + + private final Function converter; + + /** + * 构造函数 + * + * @param converter 字符串到元素类型T的转换器 + */ + protected GenericListTypeHandler(Function converter) { + this.converter = converter; + } + + @Override + public void setNonNullParameter(PreparedStatement ps, int i, List parameter, JdbcType jdbcType) + throws SQLException { + String value = StringUtils.join(parameter, ","); + ps.setString(i, value); + } + + @Override + public List getNullableResult(ResultSet rs, String columnName) throws SQLException { + return convertToList(rs.getString(columnName)); + } + + @Override + public List getNullableResult(ResultSet rs, int columnIndex) throws SQLException { + return convertToList(rs.getString(columnIndex)); + } + + @Override + public List getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { + return convertToList(cs.getString(columnIndex)); + } + + private List convertToList(String value) { + if (StringUtils.isBlank(value)) { + return null; + } + + List list = new ArrayList<>(); + String[] items = value.split(","); + for (String item : items) { + if (StringUtils.isNotBlank(item)) { + list.add(converter.apply(item.trim())); + } + } + return list; + } + + // 内部类 - 常用类型的TypeHandler实现 + + /** + * String类型列表的TypeHandler + */ + public static class StringList extends GenericListTypeHandler { + public StringList() { + super(String::valueOf); + } + } + + /** + * Integer类型列表的TypeHandler + */ + public static class IntegerList extends GenericListTypeHandler { + public IntegerList() { + super(Integer::valueOf); + } + } + + /** + * Long类型列表的TypeHandler + */ + public static class LongList extends GenericListTypeHandler { + public LongList() { + super(Long::valueOf); + } + } + + /** + * Double类型列表的TypeHandler + */ + public static class DoubleList extends GenericListTypeHandler { + public DoubleList() { + super(Double::valueOf); + } + } + + /** + * Boolean类型列表的TypeHandler + */ + public static class BooleanList extends GenericListTypeHandler { + public BooleanList() { + super(Boolean::valueOf); + } + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/service/cache/CacheKeys.java b/boyue-common/src/main/java/com/boyue/common/service/cache/CacheKeys.java new file mode 100644 index 0000000..ce0db91 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/service/cache/CacheKeys.java @@ -0,0 +1,10 @@ +package com.boyue.common.service.cache; + +import java.util.Set; + +import org.springframework.cache.Cache; + +public interface CacheKeys { + + public Set getCachekeys(final Cache cache); +} diff --git a/boyue-common/src/main/java/com/boyue/common/service/cache/CacheNoTimeOut.java b/boyue-common/src/main/java/com/boyue/common/service/cache/CacheNoTimeOut.java new file mode 100644 index 0000000..f71b6a6 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/service/cache/CacheNoTimeOut.java @@ -0,0 +1,7 @@ +package com.boyue.common.service.cache; + +public interface CacheNoTimeOut { + + public void setCacheObject(final String cacheName,final String key, final T value); + +} diff --git a/boyue-common/src/main/java/com/boyue/common/service/cache/CacheTimeOut.java b/boyue-common/src/main/java/com/boyue/common/service/cache/CacheTimeOut.java new file mode 100644 index 0000000..648d63b --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/service/cache/CacheTimeOut.java @@ -0,0 +1,10 @@ +package com.boyue.common.service.cache; + +import java.util.concurrent.TimeUnit; + +public interface CacheTimeOut extends CacheNoTimeOut { + + public void setCacheObject(final String cacheName, final String key, final T value, final long timeout, + final TimeUnit timeUnit); + +} diff --git a/boyue-common/src/main/java/com/boyue/common/service/datasource/AfterCreateDataSource.java b/boyue-common/src/main/java/com/boyue/common/service/datasource/AfterCreateDataSource.java new file mode 100644 index 0000000..dbca225 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/service/datasource/AfterCreateDataSource.java @@ -0,0 +1,10 @@ +package com.boyue.common.service.datasource; + +import java.util.Properties; + +import javax.sql.CommonDataSource; +import javax.sql.DataSource; + +public interface AfterCreateDataSource { + DataSource afterCreateDataSource(String name,Properties prop, CommonDataSource dataSource); +} diff --git a/boyue-common/src/main/java/com/boyue/common/service/datasource/CreateDataSource.java b/boyue-common/src/main/java/com/boyue/common/service/datasource/CreateDataSource.java new file mode 100644 index 0000000..5d4472f --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/service/datasource/CreateDataSource.java @@ -0,0 +1,9 @@ +package com.boyue.common.service.datasource; + +import java.util.Properties; + +import javax.sql.CommonDataSource; + +public interface CreateDataSource { + CommonDataSource createDataSource(String name, Properties prop); +} diff --git a/boyue-common/src/main/java/com/boyue/common/service/mybatis/CreateSqlSessionFactory.java b/boyue-common/src/main/java/com/boyue/common/service/mybatis/CreateSqlSessionFactory.java new file mode 100644 index 0000000..478d4c6 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/service/mybatis/CreateSqlSessionFactory.java @@ -0,0 +1,10 @@ +package com.boyue.common.service.mybatis; + +import javax.sql.DataSource; + +import org.apache.ibatis.session.SqlSessionFactory; +import org.springframework.core.env.Environment; + +public interface CreateSqlSessionFactory { + public SqlSessionFactory createSqlSessionFactory(Environment env, DataSource dataSource) throws Exception; +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/Arith.java b/boyue-common/src/main/java/com/boyue/common/utils/Arith.java new file mode 100644 index 0000000..043cb73 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/Arith.java @@ -0,0 +1,114 @@ +package com.boyue.common.utils; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * 精确的浮点数运算 + * + * @author boyue + */ +public class Arith +{ + + /** 默认除法运算精度 */ + private static final int DEF_DIV_SCALE = 10; + + /** 这个类不能实例化 */ + private Arith() + { + } + + /** + * 提供精确的加法运算。 + * @param v1 被加数 + * @param v2 加数 + * @return 两个参数的和 + */ + public static double add(double v1, double v2) + { + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + return b1.add(b2).doubleValue(); + } + + /** + * 提供精确的减法运算。 + * @param v1 被减数 + * @param v2 减数 + * @return 两个参数的差 + */ + public static double sub(double v1, double v2) + { + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + return b1.subtract(b2).doubleValue(); + } + + /** + * 提供精确的乘法运算。 + * @param v1 被乘数 + * @param v2 乘数 + * @return 两个参数的积 + */ + public static double mul(double v1, double v2) + { + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + return b1.multiply(b2).doubleValue(); + } + + /** + * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 + * 小数点以后10位,以后的数字四舍五入。 + * @param v1 被除数 + * @param v2 除数 + * @return 两个参数的商 + */ + public static double div(double v1, double v2) + { + return div(v1, v2, DEF_DIV_SCALE); + } + + /** + * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 + * 定精度,以后的数字四舍五入。 + * @param v1 被除数 + * @param v2 除数 + * @param scale 表示表示需要精确到小数点以后几位。 + * @return 两个参数的商 + */ + public static double div(double v1, double v2, int scale) + { + if (scale < 0) + { + throw new IllegalArgumentException( + "The scale must be a positive integer or zero"); + } + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + if (b1.compareTo(BigDecimal.ZERO) == 0) + { + return BigDecimal.ZERO.doubleValue(); + } + return b1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue(); + } + + /** + * 提供精确的小数位四舍五入处理。 + * @param v 需要四舍五入的数字 + * @param scale 小数点后保留几位 + * @return 四舍五入后的结果 + */ + public static double round(double v, int scale) + { + if (scale < 0) + { + throw new IllegalArgumentException( + "The scale must be a positive integer or zero"); + } + BigDecimal b = new BigDecimal(Double.toString(v)); + BigDecimal one = BigDecimal.ONE; + return b.divide(one, scale, RoundingMode.HALF_UP).doubleValue(); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/CacheUtils.java b/boyue-common/src/main/java/com/boyue/common/utils/CacheUtils.java new file mode 100644 index 0000000..aca52b3 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/CacheUtils.java @@ -0,0 +1,161 @@ +package com.boyue.common.utils; + +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.cache.jcache.JCacheCache; +import org.springframework.cache.transaction.TransactionAwareCacheDecorator; +import org.springframework.lang.Nullable; +import org.springframework.util.ObjectUtils; + +import com.boyue.common.service.cache.CacheKeys; +import com.boyue.common.service.cache.CacheTimeOut; +import com.boyue.common.utils.spring.SpringUtils; + +public class CacheUtils { + + /** + * 获取CacheManager + * + * @return + */ + public static CacheManager getCacheManager() { + return SpringUtils.getBean(CacheManager.class); + } + + /** + * 根据cacheName从CacheManager中获取cache + * + * @param cacheName + * @return + */ + public static Cache getCache(String cacheName) { + return getCacheManager().getCache(cacheName); + } + + /** + * 获取缓存的所有key值(由于springcache不支持获取所有key,只能根据cache类型来单独获取) + * + * @param cacheName + * @return + */ + public static Set getkeys(String cacheName) { + Cache cache = getCacheManager().getCache(cacheName); + CacheKeys cacheGetKets = SpringUtils.getBean(CacheKeys.class); + return cacheGetKets.getCachekeys(cache); + } + + /** + * 根据cacheName,key缓存数据 + * + * @param cacheName + * @param key + * @param value + * @param + */ + public static void put(String cacheName, String key, T value) { + put(cacheName, key, value, 0, null); + } + + /** + * 如果没有则进行缓存,根据cacheName,key缓存数据 + * + * @param cacheName + * @param key + * @param value + * @param + */ + public static void putIfAbsent(String cacheName, String key, T value) { + if (ObjectUtils.isEmpty(get(cacheName, key))) { + put(cacheName, key, value, 0, null); + } + } + + public static boolean hasKey(String cacheName, String key) { + return !ObjectUtils.isEmpty(get(cacheName, key)); + } + + /** + * 根据cacheName,key和缓存过期时间进行缓存数据,使用各种不同缓存可以单独进行操作 + * + * @param cacheName + * @param key + * @param value + * @param timeout + * @param unit + * @param + */ + public static void put(String cacheName, String key, T value, long timeout, TimeUnit unit) { + Cache cache = getCacheManager().getCache(cacheName); + if (cache instanceof JCacheCache) { + JCacheCache ehcache = (JCacheCache) cache; + ehcache.put(key, value); + } else if (cache instanceof TransactionAwareCacheDecorator) { + CacheTimeOut cacheTimeOut = SpringUtils.getBean(CacheTimeOut.class); + if (timeout != 0 && unit != null) { + cacheTimeOut.setCacheObject(cacheName, key, value, timeout, unit); + } else { + cacheTimeOut.setCacheObject(cacheName, key, value); + } + } else { + cache.put(key, value); + } + } + + /** + * 获取数据 + * + * @param cacheName + * @param key + * @return + */ + public static Cache.ValueWrapper get(String cacheName, String key) { + return getCacheManager().getCache(cacheName).get(key); + } + + /** + * 根据类型获取数据 + * + * @param cacheName + * @param key + * @param type + * @param + * @return + */ + public static T get(String cacheName, String key, @Nullable Class type) { + return getCacheManager().getCache(cacheName).get(key, type); + } + + /** + * 移除缓存数据 + * + * @param cacheName + * @param key + */ + public static void remove(String cacheName, String key) { + getCacheManager().getCache(cacheName).evict(key); + } + + /** + * 如果存在则移除缓存数据 + * + * @param cacheName + * @param key + * @return + */ + public static boolean removeIfPresent(String cacheName, String key) { + remove(cacheName, key); + return false; + } + + /** + * 清除缓存名称为cacheName的所有缓存数据 + * + * @param cacheName + */ + public static void clear(String cacheName) { + getCacheManager().getCache(cacheName).clear(); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/DateUtils.java b/boyue-common/src/main/java/com/boyue/common/utils/DateUtils.java new file mode 100644 index 0000000..233b62e --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/DateUtils.java @@ -0,0 +1,174 @@ +package com.boyue.common.utils; + +import java.lang.management.ManagementFactory; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Date; + +import org.apache.commons.lang3.time.DateFormatUtils; + +/** + * 时间工具类 + * + * @author boyue + */ +public class DateUtils extends org.apache.commons.lang3.time.DateUtils { + + public static String YYYY = "yyyy"; + + public static String YYYY_MM = "yyyy-MM"; + + public static String YYYY_MM_DD = "yyyy-MM-dd"; + + public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss"; + + public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; + + private static String[] parsePatterns = { + "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM", + "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM", + "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM" }; + + /** + * 获取当前Date型日期 + * + * @return Date() 当前日期 + */ + public static Date getNowDate() { + return new Date(); + } + + /** + * 获取当前日期, 默认格式为yyyy-MM-dd + * + * @return String + */ + public static String getDate() { + return dateTimeNow(YYYY_MM_DD); + } + + public static final String getTime() { + return dateTimeNow(YYYY_MM_DD_HH_MM_SS); + } + + public static final String dateTimeNow() { + return dateTimeNow(YYYYMMDDHHMMSS); + } + + public static final String dateTimeNow(final String format) { + return parseDateToStr(format, new Date()); + } + + public static final String dateTime(final Date date) { + return parseDateToStr(YYYY_MM_DD, date); + } + + public static final String parseDateToStr(final String format, final Date date) { + return new SimpleDateFormat(format).format(date); + } + + public static final Date dateTime(final String format, final String ts) { + try { + return new SimpleDateFormat(format).parse(ts); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + + /** + * 日期路径 即年/月/日 如2018/08/08 + */ + public static final String datePath() { + Date now = new Date(); + return DateFormatUtils.format(now, "yyyy/MM/dd"); + } + + /** + * 日期路径 即年/月/日 如20180808 + */ + public static final String dateTime() { + Date now = new Date(); + return DateFormatUtils.format(now, "yyyyMMdd"); + } + + /** + * 日期型字符串转化为日期 格式 + */ + public static Date parseDate(Object str) { + if (str == null) { + return null; + } + try { + return parseDate(str.toString(), parsePatterns); + } catch (ParseException e) { + return null; + } + } + + /** + * 获取服务器启动时间 + */ + public static Date getServerStartDate() { + long time = ManagementFactory.getRuntimeMXBean().getStartTime(); + return new Date(time); + } + + /** + * 计算相差天数 + */ + public static int differentDaysByMillisecond(Date date1, Date date2) { + return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24))); + } + + /** + * 计算时间差 + * + * @param endTime 最后时间 + * @param startTime 开始时间 + * @return 时间差(天/小时/分钟) + */ + public static String timeDistance(Date endDate, Date startTime) { + long nd = 1000 * 24 * 60 * 60; + long nh = 1000 * 60 * 60; + long nm = 1000 * 60; + // long ns = 1000; + // 获得两个时间的毫秒时间差异 + long diff = endDate.getTime() - startTime.getTime(); + // 计算差多少天 + long day = diff / nd; + // 计算差多少小时 + long hour = diff % nd / nh; + // 计算差多少分钟 + long min = diff % nd % nh / nm; + // 计算差多少秒//输出结果 + // long sec = diff % nd % nh % nm / ns; + return day + "天" + hour + "小时" + min + "分钟"; + } + + /** + * 增加 LocalDateTime ==> Date + */ + public static Date toDate(LocalDateTime temporalAccessor) { + ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault()); + return Date.from(zdt.toInstant()); + } + + /** + * 增加 LocalDate ==> Date + */ + public static Date toDate(LocalDate temporalAccessor) { + LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0)); + ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault()); + return Date.from(zdt.toInstant()); + } + + /** @deprecated */ + @Deprecated + public DateUtils() { + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/DesensitizedUtil.java b/boyue-common/src/main/java/com/boyue/common/utils/DesensitizedUtil.java new file mode 100644 index 0000000..7880b68 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/DesensitizedUtil.java @@ -0,0 +1,49 @@ +package com.boyue.common.utils; + +/** + * 脱敏工具类 + * + * @author boyue + */ +public class DesensitizedUtil +{ + /** + * 密码的全部字符都用*代替,比如:****** + * + * @param password 密码 + * @return 脱敏后的密码 + */ + public static String password(String password) + { + if (StringUtils.isBlank(password)) + { + return StringUtils.EMPTY; + } + return StringUtils.repeat('*', password.length()); + } + + /** + * 车牌中间用*代替,如果是错误的车牌,不处理 + * + * @param carLicense 完整的车牌号 + * @return 脱敏后的车牌 + */ + public static String carLicense(String carLicense) + { + if (StringUtils.isBlank(carLicense)) + { + return StringUtils.EMPTY; + } + // 普通车牌 + if (carLicense.length() == 7) + { + carLicense = StringUtils.hide(carLicense, 3, 6); + } + else if (carLicense.length() == 8) + { + // 新能源车牌 + carLicense = StringUtils.hide(carLicense, 3, 7); + } + return carLicense; + } +} \ No newline at end of file diff --git a/boyue-common/src/main/java/com/boyue/common/utils/DictUtils.java b/boyue-common/src/main/java/com/boyue/common/utils/DictUtils.java new file mode 100644 index 0000000..c24c94b --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/DictUtils.java @@ -0,0 +1,204 @@ +package com.boyue.common.utils; + +import java.util.List; + +import org.springframework.cache.Cache; + +import com.boyue.common.constant.CacheConstants; +import com.boyue.common.core.domain.entity.SysDictData; + +/** + * 字典工具类 + * + * @author boyue + */ +public class DictUtils +{ + /** + * 分隔符 + */ + public static final String SEPARATOR = ","; + + /** + * 设置字典缓存 + * + * @param key 参数键 + * @param dictDatas 字典数据列表 + */ + public static void setDictCache(String key, List dictDatas) + { + getDictCacheKey().put(key, dictDatas); + } + + /** + * 获取字典缓存 + * + * @param key 参数键 + * @return dictDatas 字典数据列表 + */ + @SuppressWarnings("unchecked") + public static List getDictCache(String key) + { + List arrayCache = (List) getDictCacheKey().get(key, List.class); + if (StringUtils.isNotNull(arrayCache)) + { + return arrayCache; + } + return null; + } + + /** + * 根据字典类型和字典值获取字典标签 + * + * @param dictType 字典类型 + * @param dictValue 字典值 + * @return 字典标签 + */ + public static String getDictLabel(String dictType, String dictValue) + { + return getDictLabel(dictType, dictValue, SEPARATOR); + } + + /** + * 根据字典类型和字典标签获取字典值 + * + * @param dictType 字典类型 + * @param dictLabel 字典标签 + * @return 字典值 + */ + public static String getDictValue(String dictType, String dictLabel) + { + return getDictValue(dictType, dictLabel, SEPARATOR); + } + + /** + * 根据字典类型和字典值获取字典标签 + * + * @param dictType 字典类型 + * @param dictValue 字典值 + * @param separator 分隔符 + * @return 字典标签 + */ + public static String getDictLabel(String dictType, String dictValue, String separator) + { + StringBuilder propertyString = new StringBuilder(); + List datas = getDictCache(dictType); + if (StringUtils.isNotNull(datas)) + { + if (StringUtils.containsAny(separator, dictValue)) + { + for (SysDictData dict : datas) + { + for (String value : dictValue.split(separator)) + { + if (value.equals(dict.getDictValue())) + { + propertyString.append(dict.getDictLabel()).append(separator); + break; + } + } + } + } + else + { + for (SysDictData dict : datas) + { + if (dictValue.equals(dict.getDictValue())) + { + return dict.getDictLabel(); + } + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + /** + * 根据字典类型和字典标签获取字典值 + * + * @param dictType 字典类型 + * @param dictLabel 字典标签 + * @param separator 分隔符 + * @return 字典值 + */ + public static String getDictValue(String dictType, String dictLabel, String separator) + { + StringBuilder propertyString = new StringBuilder(); + List datas = getDictCache(dictType); + if (StringUtils.containsAny(separator, dictLabel) && StringUtils.isNotEmpty(datas)) + { + for (SysDictData dict : datas) + { + for (String label : dictLabel.split(separator)) + { + if (label.equals(dict.getDictLabel())) + { + propertyString.append(dict.getDictValue()).append(separator); + break; + } + } + } + } + else + { + for (SysDictData dict : datas) + { + if (dictLabel.equals(dict.getDictLabel())) + { + return dict.getDictValue(); + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + + /** + * 根据字典类型获取字典所有标签 + * + * @param dictType 字典类型 + * @return 字典值 + */ + public static String getDictLabels(String dictType) + { + StringBuilder propertyString = new StringBuilder(); + List datas = getDictCache(dictType); + if (StringUtils.isNull(datas)) + { + return StringUtils.EMPTY; + } + for (SysDictData dict : datas) + { + propertyString.append(dict.getDictLabel()).append(SEPARATOR); + } + return StringUtils.stripEnd(propertyString.toString(), SEPARATOR); + } + + /** + * 删除指定字典缓存 + * + * @param key 字典键 + */ + public static void removeDictCache(String key) + { + getDictCacheKey().evict(key); + } + + /** + * 清空字典缓存 + */ + public static void clearDictCache() + { + getDictCacheKey().clear(); + } + + /** + * 获取dict缓存 + * + * @return 缓存Cache + */ + public static Cache getDictCacheKey() + { + return CacheUtils.getCache(CacheConstants.SYS_DICT_KEY); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/ExceptionUtil.java b/boyue-common/src/main/java/com/boyue/common/utils/ExceptionUtil.java new file mode 100644 index 0000000..be10248 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/ExceptionUtil.java @@ -0,0 +1,39 @@ +package com.boyue.common.utils; + +import java.io.PrintWriter; +import java.io.StringWriter; +import org.apache.commons.lang3.exception.ExceptionUtils; + +/** + * 错误信息处理类。 + * + * @author boyue + */ +public class ExceptionUtil +{ + /** + * 获取exception的详细错误信息。 + */ + public static String getExceptionMessage(Throwable e) + { + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw, true)); + return sw.toString(); + } + + public static String getRootErrorMessage(Exception e) + { + Throwable root = ExceptionUtils.getRootCause(e); + root = (root == null ? e : root); + if (root == null) + { + return ""; + } + String msg = root.getMessage(); + if (msg == null) + { + return "null"; + } + return StringUtils.defaultString(msg); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/LogUtils.java b/boyue-common/src/main/java/com/boyue/common/utils/LogUtils.java new file mode 100644 index 0000000..15eb06a --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/LogUtils.java @@ -0,0 +1,18 @@ +package com.boyue.common.utils; + +/** + * 处理并记录日志文件 + * + * @author boyue + */ +public class LogUtils +{ + public static String getBlock(Object msg) + { + if (msg == null) + { + msg = ""; + } + return "[" + msg.toString() + "]"; + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/MessageUtils.java b/boyue-common/src/main/java/com/boyue/common/utils/MessageUtils.java new file mode 100644 index 0000000..a3b55da --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/MessageUtils.java @@ -0,0 +1,26 @@ +package com.boyue.common.utils; + +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; +import com.boyue.common.utils.spring.SpringUtils; + +/** + * 获取i18n资源文件 + * + * @author boyue + */ +public class MessageUtils +{ + /** + * 根据消息键和参数 获取消息 委托给spring messageSource + * + * @param code 消息键 + * @param args 参数 + * @return 获取国际化翻译值 + */ + public static String message(String code, Object... args) + { + MessageSource messageSource = SpringUtils.getBean(MessageSource.class); + return messageSource.getMessage(code, args, LocaleContextHolder.getLocale()); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/MybatisUtils.java b/boyue-common/src/main/java/com/boyue/common/utils/MybatisUtils.java new file mode 100644 index 0000000..9cb5bf2 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/MybatisUtils.java @@ -0,0 +1,77 @@ +package com.boyue.common.utils; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.core.type.classreading.CachingMetadataReaderFactory; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.util.ClassUtils; + +public class MybatisUtils { + static final String DEFAULT_RESOURCE_PATTERN = "**/*.class"; + + public static String setTypeAliasesPackage(String typeAliasesPackage) { + ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver(); + MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver); + List allResult = new ArrayList(); + try { + for (String aliasesPackage : typeAliasesPackage.split(",")) { + List result = new ArrayList(); + aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + + ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + + DEFAULT_RESOURCE_PATTERN; + Resource[] resources = resolver.getResources(aliasesPackage); + if (resources != null && resources.length > 0) { + MetadataReader metadataReader = null; + for (Resource resource : resources) { + if (resource.isReadable()) { + metadataReader = metadataReaderFactory.getMetadataReader(resource); + try { + result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage() + .getName()); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + } + } + if (result.size() > 0) { + HashSet hashResult = new HashSet(result); + allResult.addAll(hashResult); + } + } + if (allResult.size() > 0) { + typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0])); + } else { + throw new RuntimeException( + "mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包"); + } + } catch (IOException e) { + e.printStackTrace(); + } + return typeAliasesPackage; + } + + public static Resource[] resolveMapperLocations(String[] mapperLocations) { + ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(); + List resources = new ArrayList(); + if (mapperLocations != null) { + for (String mapperLocation : mapperLocations) { + try { + Resource[] mappers = resourceResolver.getResources(mapperLocation); + resources.addAll(Arrays.asList(mappers)); + } catch (IOException e) { + // ignore + } + } + } + return resources.toArray(new Resource[resources.size()]); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/PageUtils.java b/boyue-common/src/main/java/com/boyue/common/utils/PageUtils.java new file mode 100644 index 0000000..e048a6f --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/PageUtils.java @@ -0,0 +1,35 @@ +package com.boyue.common.utils; + +import com.github.pagehelper.PageHelper; +import com.boyue.common.core.page.PageDomain; +import com.boyue.common.core.page.TableSupport; +import com.boyue.common.utils.sql.SqlUtil; + +/** + * 分页工具类 + * + * @author boyue + */ +public class PageUtils extends PageHelper +{ + /** + * 设置请求分页数据 + */ + public static void startPage() + { + PageDomain pageDomain = TableSupport.buildPageRequest(); + Integer pageNum = pageDomain.getPageNum(); + Integer pageSize = pageDomain.getPageSize(); + String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy()); + Boolean reasonable = pageDomain.getReasonable(); + PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable); + } + + /** + * 清理分页的线程变量 + */ + public static void clearPage() + { + PageHelper.clearPage(); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/SecurityUtils.java b/boyue-common/src/main/java/com/boyue/common/utils/SecurityUtils.java new file mode 100644 index 0000000..095dd6c --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/SecurityUtils.java @@ -0,0 +1,178 @@ +package com.boyue.common.utils; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.util.PatternMatchUtils; +import com.boyue.common.constant.Constants; +import com.boyue.common.constant.HttpStatus; +import com.boyue.common.core.domain.entity.SysRole; +import com.boyue.common.core.domain.model.LoginUser; +import com.boyue.common.exception.ServiceException; + +/** + * 安全服务工具类 + * + * @author boyue + */ +public class SecurityUtils +{ + + /** + * 用户ID + **/ + public static Long getUserId() + { + try + { + return getLoginUser().getUserId(); + } + catch (Exception e) + { + throw new ServiceException("获取用户ID异常", HttpStatus.UNAUTHORIZED); + } + } + + /** + * 获取部门ID + **/ + public static Long getDeptId() + { + try + { + return getLoginUser().getDeptId(); + } + catch (Exception e) + { + throw new ServiceException("获取部门ID异常", HttpStatus.UNAUTHORIZED); + } + } + + /** + * 获取用户账户 + **/ + public static String getUsername() + { + try + { + return getLoginUser().getUsername(); + } + catch (Exception e) + { + throw new ServiceException("获取用户账户异常", HttpStatus.UNAUTHORIZED); + } + } + + /** + * 获取用户 + **/ + public static LoginUser getLoginUser() + { + try + { + return (LoginUser) getAuthentication().getPrincipal(); + } + catch (Exception e) + { + throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED); + } + } + + /** + * 获取Authentication + */ + public static Authentication getAuthentication() + { + return SecurityContextHolder.getContext().getAuthentication(); + } + + /** + * 生成BCryptPasswordEncoder密码 + * + * @param password 密码 + * @return 加密字符串 + */ + public static String encryptPassword(String password) + { + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + return passwordEncoder.encode(password); + } + + /** + * 判断密码是否相同 + * + * @param rawPassword 真实密码 + * @param encodedPassword 加密后字符 + * @return 结果 + */ + public static boolean matchesPassword(String rawPassword, String encodedPassword) + { + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + return passwordEncoder.matches(rawPassword, encodedPassword); + } + + /** + * 是否为管理员 + * + * @param userId 用户ID + * @return 结果 + */ + public static boolean isAdmin(Long userId) + { + return userId != null && 1L == userId; + } + + /** + * 验证用户是否具备某权限 + * + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public static boolean hasPermi(String permission) + { + return hasPermi(getLoginUser().getPermissions(), permission); + } + + /** + * 判断是否包含权限 + * + * @param authorities 权限列表 + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public static boolean hasPermi(Collection authorities, String permission) + { + return authorities.stream().filter(StringUtils::hasText) + .anyMatch(x -> Constants.ALL_PERMISSION.equals(x) || PatternMatchUtils.simpleMatch(x, permission)); + } + + /** + * 验证用户是否拥有某个角色 + * + * @param role 角色标识 + * @return 用户是否具备某角色 + */ + public static boolean hasRole(String role) + { + List roleList = getLoginUser().getUser().getRoles(); + Collection roles = roleList.stream().map(SysRole::getRoleKey).collect(Collectors.toSet()); + return hasRole(roles, role); + } + + /** + * 判断是否包含角色 + * + * @param roles 角色列表 + * @param role 角色 + * @return 用户是否具备某角色权限 + */ + public static boolean hasRole(Collection roles, String role) + { + return roles.stream().filter(StringUtils::hasText) + .anyMatch(x -> Constants.SUPER_ADMIN.equals(x) || PatternMatchUtils.simpleMatch(x, role)); + } + +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/ServletUtils.java b/boyue-common/src/main/java/com/boyue/common/utils/ServletUtils.java new file mode 100644 index 0000000..f249ee2 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/ServletUtils.java @@ -0,0 +1,218 @@ +package com.boyue.common.utils; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import com.boyue.common.constant.Constants; +import com.boyue.common.core.text.Convert; + +/** + * 客户端工具类 + * + * @author boyue + */ +public class ServletUtils +{ + /** + * 获取String参数 + */ + public static String getParameter(String name) + { + return getRequest().getParameter(name); + } + + /** + * 获取String参数 + */ + public static String getParameter(String name, String defaultValue) + { + return Convert.toStr(getRequest().getParameter(name), defaultValue); + } + + /** + * 获取Integer参数 + */ + public static Integer getParameterToInt(String name) + { + return Convert.toInt(getRequest().getParameter(name)); + } + + /** + * 获取Integer参数 + */ + public static Integer getParameterToInt(String name, Integer defaultValue) + { + return Convert.toInt(getRequest().getParameter(name), defaultValue); + } + + /** + * 获取Boolean参数 + */ + public static Boolean getParameterToBool(String name) + { + return Convert.toBool(getRequest().getParameter(name)); + } + + /** + * 获取Boolean参数 + */ + public static Boolean getParameterToBool(String name, Boolean defaultValue) + { + return Convert.toBool(getRequest().getParameter(name), defaultValue); + } + + /** + * 获得所有请求参数 + * + * @param request 请求对象{@link ServletRequest} + * @return Map + */ + public static Map getParams(ServletRequest request) + { + final Map map = request.getParameterMap(); + return Collections.unmodifiableMap(map); + } + + /** + * 获得所有请求参数 + * + * @param request 请求对象{@link ServletRequest} + * @return Map + */ + public static Map getParamMap(ServletRequest request) + { + Map params = new HashMap<>(); + for (Map.Entry entry : getParams(request).entrySet()) + { + params.put(entry.getKey(), StringUtils.join(entry.getValue(), ",")); + } + return params; + } + + /** + * 获取request + */ + public static HttpServletRequest getRequest() + { + return getRequestAttributes().getRequest(); + } + + /** + * 获取response + */ + public static HttpServletResponse getResponse() + { + return getRequestAttributes().getResponse(); + } + + /** + * 获取session + */ + public static HttpSession getSession() + { + return getRequest().getSession(); + } + + public static ServletRequestAttributes getRequestAttributes() + { + RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); + return (ServletRequestAttributes) attributes; + } + + /** + * 将字符串渲染到客户端 + * + * @param response 渲染对象 + * @param string 待渲染的字符串 + */ + public static void renderString(HttpServletResponse response, String string) + { + try + { + response.setStatus(200); + response.setContentType("application/json"); + response.setCharacterEncoding("utf-8"); + response.getWriter().print(string); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + /** + * 是否是Ajax异步请求 + * + * @param request + */ + public static boolean isAjaxRequest(HttpServletRequest request) + { + String accept = request.getHeader("accept"); + if (accept != null && accept.contains("application/json")) + { + return true; + } + + String xRequestedWith = request.getHeader("X-Requested-With"); + if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) + { + return true; + } + + String uri = request.getRequestURI(); + if (StringUtils.inStringIgnoreCase(uri, ".json", ".xml")) + { + return true; + } + + String ajax = request.getParameter("__ajax"); + return StringUtils.inStringIgnoreCase(ajax, "json", "xml"); + } + + /** + * 内容编码 + * + * @param str 内容 + * @return 编码后的内容 + */ + public static String urlEncode(String str) + { + try + { + return URLEncoder.encode(str, Constants.UTF8); + } + catch (UnsupportedEncodingException e) + { + return StringUtils.EMPTY; + } + } + + /** + * 内容解码 + * + * @param str 内容 + * @return 解码后的内容 + */ + public static String urlDecode(String str) + { + try + { + return URLDecoder.decode(str, Constants.UTF8); + } + catch (UnsupportedEncodingException e) + { + return StringUtils.EMPTY; + } + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/StringUtils.java b/boyue-common/src/main/java/com/boyue/common/utils/StringUtils.java new file mode 100644 index 0000000..8d38818 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/StringUtils.java @@ -0,0 +1,588 @@ +package com.boyue.common.utils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.util.AntPathMatcher; + +import com.boyue.common.constant.Constants; +import com.boyue.common.core.text.StrFormatter; + +/** + * 字符串工具类 + * + * @author boyue + */ +public class StringUtils extends org.apache.commons.lang3.StringUtils { + + /** 空字符串 */ + private static final String NULLSTR = ""; + + /** 下划线 */ + private static final char SEPARATOR = '_'; + + /** 星号 */ + private static final char ASTERISK = '*'; + + /** + * 获取参数不为空值 + * + * @param value defaultValue 要判断的value + * @return value 返回值 + */ + public static T nvl(T value, T defaultValue) { + return value != null ? value : defaultValue; + } + + /** + * * 判断一个Collection是否为空, 包含List,Set,Queue + * + * @param coll 要判断的Collection + * @return true:为空 false:非空 + */ + public static boolean isEmpty(Collection coll) { + return isNull(coll) || coll.isEmpty(); + } + + /** + * * 判断一个Collection是否非空,包含List,Set,Queue + * + * @param coll 要判断的Collection + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Collection coll) { + return !isEmpty(coll); + } + + /** + * * 判断一个对象数组是否为空 + * + * @param objects 要判断的对象数组 + ** @return true:为空 false:非空 + */ + public static boolean isEmpty(Object[] objects) { + return isNull(objects) || (objects.length == 0); + } + + /** + * * 判断一个对象数组是否非空 + * + * @param objects 要判断的对象数组 + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Object[] objects) { + return !isEmpty(objects); + } + + /** + * * 判断一个Map是否为空 + * + * @param map 要判断的Map + * @return true:为空 false:非空 + */ + public static boolean isEmpty(Map map) { + return isNull(map) || map.isEmpty(); + } + + /** + * * 判断一个Map是否为空 + * + * @param map 要判断的Map + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Map map) { + return !isEmpty(map); + } + + /** + * * 判断一个字符串是否为空串 + * + * @param str String + * @return true:为空 false:非空 + */ + public static boolean isEmpty(String str) { + return isNull(str) || NULLSTR.equals(str.trim()); + } + + /** + * * 判断一个字符串是否为非空串 + * + * @param str String + * @return true:非空串 false:空串 + */ + public static boolean isNotEmpty(String str) { + return !isEmpty(str); + } + + /** + * * 判断一个对象是否为空 + * + * @param object Object + * @return true:为空 false:非空 + */ + public static boolean isNull(Object object) { + return object == null; + } + + /** + * * 判断一个对象是否非空 + * + * @param object Object + * @return true:非空 false:空 + */ + public static boolean isNotNull(Object object) { + return !isNull(object); + } + + /** + * * 判断一个对象是否是数组类型(Java基本型别的数组) + * + * @param object 对象 + * @return true:是数组 false:不是数组 + */ + public static boolean isArray(Object object) { + return isNotNull(object) && object.getClass().isArray(); + } + + /** + * 去空格 + */ + public static String trim(String str) { + return (str == null ? "" : str.trim()); + } + + /** + * 替换指定字符串的指定区间内字符为"*" + * + * @param str 字符串 + * @param startInclude 开始位置(包含) + * @param endExclude 结束位置(不包含) + * @return 替换后的字符串 + */ + public static String hide(CharSequence str, int startInclude, int endExclude) { + if (isEmpty(str)) { + return NULLSTR; + } + final int strLength = str.length(); + if (startInclude > strLength) { + return NULLSTR; + } + if (endExclude > strLength) { + endExclude = strLength; + } + if (startInclude > endExclude) { + // 如果起始位置大于结束位置,不替换 + return NULLSTR; + } + final char[] chars = new char[strLength]; + for (int i = 0; i < strLength; i++) { + if (i >= startInclude && i < endExclude) { + chars[i] = ASTERISK; + } else { + chars[i] = str.charAt(i); + } + } + return new String(chars); + } + + /** + * 截取字符串 + * + * @param str 字符串 + * @param start 开始 + * @return 结果 + */ + public static String substring(final String str, int start) { + if (str == null) { + return NULLSTR; + } + + if (start < 0) { + start = str.length() + start; + } + + if (start < 0) { + start = 0; + } + if (start > str.length()) { + return NULLSTR; + } + + return str.substring(start); + } + + /** + * 截取字符串 + * + * @param str 字符串 + * @param start 开始 + * @param end 结束 + * @return 结果 + */ + public static String substring(final String str, int start, int end) { + if (str == null) { + return NULLSTR; + } + + if (end < 0) { + end = str.length() + end; + } + if (start < 0) { + start = str.length() + start; + } + + if (end > str.length()) { + end = str.length(); + } + + if (start > end) { + return NULLSTR; + } + + if (start < 0) { + start = 0; + } + if (end < 0) { + end = 0; + } + + return str.substring(start, end); + } + + /** + * 判断是否为空,并且不是空白字符 + * + * @param str 要判断的value + * @return 结果 + */ + public static boolean hasText(String str) { + return (str != null && !str.isEmpty() && containsText(str)); + } + + private static boolean containsText(CharSequence str) { + int strLen = str.length(); + for (int i = 0; i < strLen; i++) { + if (!Character.isWhitespace(str.charAt(i))) { + return true; + } + } + return false; + } + + /** + * 格式化文本, {} 表示占位符
+ * 此方法只是简单将占位符 {} 按照顺序替换为参数
+ * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
+ * 例:
+ * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
+ * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a
+ * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
+ * + * @param template 文本模板,被替换的部分用 {} 表示 + * @param params 参数值 + * @return 格式化后的文本 + */ + public static String format(String template, Object... params) { + if (isEmpty(params) || isEmpty(template)) { + return template; + } + return StrFormatter.format(template, params); + } + + /** + * 是否为http(s)://开头 + * + * @param link 链接 + * @return 结果 + */ + public static boolean ishttp(String link) { + return StringUtils.startsWithAny(link, Constants.HTTP, Constants.HTTPS); + } + + /** + * 字符串转set + * + * @param str 字符串 + * @param sep 分隔符 + * @return set集合 + */ + public static final Set str2Set(String str, String sep) { + return new HashSet(str2List(str, sep, true, false)); + } + + /** + * 字符串转list + * + * @param str 字符串 + * @param sep 分隔符 + * @param filterBlank 过滤纯空白 + * @param trim 去掉首尾空白 + * @return list集合 + */ + public static final List str2List(String str, String sep, boolean filterBlank, boolean trim) { + List list = new ArrayList(); + if (StringUtils.isEmpty(str)) { + return list; + } + + // 过滤空白字符串 + if (filterBlank && StringUtils.isBlank(str)) { + return list; + } + String[] split = str.split(sep); + for (String string : split) { + if (filterBlank && StringUtils.isBlank(string)) { + continue; + } + if (trim) { + string = string.trim(); + } + list.add(string); + } + + return list; + } + + /** + * 判断给定的collection列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value + * + * @param collection 给定的集合 + * @param array 给定的数组 + * @return boolean 结果 + */ + public static boolean containsAny(Collection collection, String... array) { + if (isEmpty(collection) || isEmpty(array)) { + return false; + } else { + for (String str : array) { + if (collection.contains(str)) { + return true; + } + } + return false; + } + } + + /** + * 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写 + * + * @param cs 指定字符串 + * @param searchCharSequences 需要检查的字符串数组 + * @return 是否包含任意一个字符串 + */ + public static boolean containsAnyIgnoreCase(CharSequence cs, CharSequence... searchCharSequences) { + if (isEmpty(cs) || isEmpty(searchCharSequences)) { + return false; + } + for (CharSequence testStr : searchCharSequences) { + if (containsIgnoreCase(cs, testStr)) { + return true; + } + } + return false; + } + + /** + * 驼峰转下划线命名 + */ + public static String toUnderScoreCase(String str) { + if (str == null) { + return null; + } + StringBuilder sb = new StringBuilder(); + // 前置字符是否大写 + boolean preCharIsUpperCase = true; + // 当前字符是否大写 + boolean curreCharIsUpperCase = true; + // 下一字符是否大写 + boolean nexteCharIsUpperCase = true; + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if (i > 0) { + preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1)); + } else { + preCharIsUpperCase = false; + } + + curreCharIsUpperCase = Character.isUpperCase(c); + + if (i < (str.length() - 1)) { + nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1)); + } + + if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase) { + sb.append(SEPARATOR); + } else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase) { + sb.append(SEPARATOR); + } + sb.append(Character.toLowerCase(c)); + } + + return sb.toString(); + } + + /** + * 是否包含字符串 + * + * @param str 验证字符串 + * @param strs 字符串组 + * @return 包含返回true + */ + public static boolean inStringIgnoreCase(String str, String... strs) { + if (str != null && strs != null) { + for (String s : strs) { + if (str.equalsIgnoreCase(trim(s))) { + return true; + } + } + } + return false; + } + + /** + * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 + * 例如:HELLO_WORLD->HelloWorld + * + * @param name 转换前的下划线大写方式命名的字符串 + * @return 转换后的驼峰式命名的字符串 + */ + public static String convertToCamelCase(String name) { + StringBuilder result = new StringBuilder(); + // 快速检查 + if (name == null || name.isEmpty()) { + // 没必要转换 + return ""; + } else if (!name.contains("_")) { + // 不含下划线,仅将首字母大写 + return name.substring(0, 1).toUpperCase() + name.substring(1); + } + // 用下划线将原始字符串分割 + String[] camels = name.split("_"); + for (String camel : camels) { + // 跳过原始字符串中开头、结尾的下换线或双重下划线 + if (camel.isEmpty()) { + continue; + } + // 首字母大写 + result.append(camel.substring(0, 1).toUpperCase()); + result.append(camel.substring(1).toLowerCase()); + } + return result.toString(); + } + + /** + * 驼峰式命名法 + * 例如:user_name->userName + */ + public static String toCamelCase(String s) { + if (s == null) { + return null; + } + if (s.indexOf(SEPARATOR) == -1) { + return s; + } + s = s.toLowerCase(); + StringBuilder sb = new StringBuilder(s.length()); + boolean upperCase = false; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + + if (c == SEPARATOR) { + upperCase = true; + } else if (upperCase) { + sb.append(Character.toUpperCase(c)); + upperCase = false; + } else { + sb.append(c); + } + } + return sb.toString(); + } + + /** + * 查找指定字符串是否匹配指定字符串列表中的任意一个字符串 + * + * @param str 指定字符串 + * @param strs 需要检查的字符串数组 + * @return 是否匹配 + */ + public static boolean matches(String str, List strs) { + if (isEmpty(str) || isEmpty(strs)) { + return false; + } + for (String pattern : strs) { + if (isMatch(pattern, str)) { + return true; + } + } + return false; + } + + /** + * 判断url是否与规则配置: + * ? 表示单个字符; + * * 表示一层路径内的任意字符串,不可跨层级; + * ** 表示任意层路径; + * + * @param pattern 匹配规则 + * @param url 需要匹配的url + * @return + */ + public static boolean isMatch(String pattern, String url) { + AntPathMatcher matcher = new AntPathMatcher(); + return matcher.match(pattern, url); + } + + @SuppressWarnings("unchecked") + public static T cast(Object obj) { + return (T) obj; + } + + /** + * 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。 + * + * @param num 数字对象 + * @param size 字符串指定长度 + * @return 返回数字的字符串格式,该字符串为指定长度。 + */ + public static final String padl(final Number num, final int size) { + return padl(num.toString(), size, '0'); + } + + /** + * 字符串左补齐。如果原始字符串s长度大于size,则只保留最后size个字符。 + * + * @param s 原始字符串 + * @param size 字符串指定长度 + * @param c 用于补齐的字符 + * @return 返回指定长度的字符串,由原字符串左补齐或截取得到。 + */ + public static final String padl(final String s, final int size, final char c) { + final StringBuilder sb = new StringBuilder(size); + if (s != null) { + final int len = s.length(); + if (s.length() <= size) { + for (int i = size - len; i > 0; i--) { + sb.append(c); + } + sb.append(s); + } else { + return s.substring(len - size, len); + } + } else { + for (int i = size; i > 0; i--) { + sb.append(c); + } + } + return sb.toString(); + } + + /** @deprecated */ + @Deprecated + public StringUtils() { + } +} \ No newline at end of file diff --git a/boyue-common/src/main/java/com/boyue/common/utils/Threads.java b/boyue-common/src/main/java/com/boyue/common/utils/Threads.java new file mode 100644 index 0000000..50b0b48 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/Threads.java @@ -0,0 +1,99 @@ +package com.boyue.common.utils; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 线程相关工具类. + * + * @author boyue + */ +public class Threads +{ + private static final Logger logger = LoggerFactory.getLogger(Threads.class); + + /** + * sleep等待,单位为毫秒 + */ + public static void sleep(long milliseconds) + { + try + { + Thread.sleep(milliseconds); + } + catch (InterruptedException e) + { + return; + } + } + + /** + * 停止线程池 + * 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务. + * 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数. + * 如果仍然超時,則強制退出. + * 另对在shutdown时线程本身被调用中断做了处理. + */ + public static void shutdownAndAwaitTermination(ExecutorService pool) + { + if (pool != null && !pool.isShutdown()) + { + pool.shutdown(); + try + { + if (!pool.awaitTermination(120, TimeUnit.SECONDS)) + { + pool.shutdownNow(); + if (!pool.awaitTermination(120, TimeUnit.SECONDS)) + { + logger.info("Pool did not terminate"); + } + } + } + catch (InterruptedException ie) + { + pool.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + } + + /** + * 打印线程异常信息 + */ + public static void printException(Runnable r, Throwable t) + { + if (t == null && r instanceof Future) + { + try + { + Future future = (Future) r; + if (future.isDone()) + { + future.get(); + } + } + catch (CancellationException ce) + { + t = ce; + } + catch (ExecutionException ee) + { + t = ee.getCause(); + } + catch (InterruptedException ie) + { + Thread.currentThread().interrupt(); + } + } + if (t != null) + { + logger.error(t.getMessage(), t); + } + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/bean/BeanUtils.java b/boyue-common/src/main/java/com/boyue/common/utils/bean/BeanUtils.java new file mode 100644 index 0000000..98da101 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/bean/BeanUtils.java @@ -0,0 +1,110 @@ +package com.boyue.common.utils.bean; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Bean 工具类 + * + * @author boyue + */ +public class BeanUtils extends org.springframework.beans.BeanUtils +{ + /** Bean方法名中属性名开始的下标 */ + private static final int BEAN_METHOD_PROP_INDEX = 3; + + /** * 匹配getter方法的正则表达式 */ + private static final Pattern GET_PATTERN = Pattern.compile("get(\\p{javaUpperCase}\\w*)"); + + /** * 匹配setter方法的正则表达式 */ + private static final Pattern SET_PATTERN = Pattern.compile("set(\\p{javaUpperCase}\\w*)"); + + /** + * Bean属性复制工具方法。 + * + * @param dest 目标对象 + * @param src 源对象 + */ + public static void copyBeanProp(Object dest, Object src) + { + try + { + copyProperties(src, dest); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * 获取对象的setter方法。 + * + * @param obj 对象 + * @return 对象的setter方法列表 + */ + public static List getSetterMethods(Object obj) + { + // setter方法列表 + List setterMethods = new ArrayList(); + + // 获取所有方法 + Method[] methods = obj.getClass().getMethods(); + + // 查找setter方法 + + for (Method method : methods) + { + Matcher m = SET_PATTERN.matcher(method.getName()); + if (m.matches() && (method.getParameterTypes().length == 1)) + { + setterMethods.add(method); + } + } + // 返回setter方法列表 + return setterMethods; + } + + /** + * 获取对象的getter方法。 + * + * @param obj 对象 + * @return 对象的getter方法列表 + */ + + public static List getGetterMethods(Object obj) + { + // getter方法列表 + List getterMethods = new ArrayList(); + // 获取所有方法 + Method[] methods = obj.getClass().getMethods(); + // 查找getter方法 + for (Method method : methods) + { + Matcher m = GET_PATTERN.matcher(method.getName()); + if (m.matches() && (method.getParameterTypes().length == 0)) + { + getterMethods.add(method); + } + } + // 返回getter方法列表 + return getterMethods; + } + + /** + * 检查Bean方法名中的属性名是否相等。
+ * 如getName()和setName()属性名一样,getName()和setAge()属性名不一样。 + * + * @param m1 方法名1 + * @param m2 方法名2 + * @return 属性名一样返回true,否则返回false + */ + + public static boolean isMethodPropEquals(String m1, String m2) + { + return m1.substring(BEAN_METHOD_PROP_INDEX).equals(m2.substring(BEAN_METHOD_PROP_INDEX)); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/bean/BeanValidators.java b/boyue-common/src/main/java/com/boyue/common/utils/bean/BeanValidators.java new file mode 100644 index 0000000..ae3315b --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/bean/BeanValidators.java @@ -0,0 +1,24 @@ +package com.boyue.common.utils.bean; + +import java.util.Set; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.Validator; + +/** + * bean对象属性验证 + * + * @author boyue + */ +public class BeanValidators +{ + public static void validateWithException(Validator validator, Object object, Class... groups) + throws ConstraintViolationException + { + Set> constraintViolations = validator.validate(object, groups); + if (!constraintViolations.isEmpty()) + { + throw new ConstraintViolationException(constraintViolations); + } + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/file/FileTypeUtils.java b/boyue-common/src/main/java/com/boyue/common/utils/file/FileTypeUtils.java new file mode 100644 index 0000000..8658f68 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/file/FileTypeUtils.java @@ -0,0 +1,77 @@ +package com.boyue.common.utils.file; + +import org.apache.commons.lang3.StringUtils; + +import java.io.File; + +/** + * 文件类型工具类 + * + * @author boyue + */ +public class FileTypeUtils +{ + /** + * 获取文件类型 + *

+ * 例如: boyue.txt, 返回: txt + * + * @param file 文件名 + * @return 后缀(不含".") + */ + public static String getFileType(File file) + { + if (null == file) + { + return StringUtils.EMPTY; + } + return getFileType(file.getName()); + } + + /** + * 获取文件类型 + *

+ * 例如: boyue.txt, 返回: txt + * + * @param fileName 文件名 + * @return 后缀(不含".") + */ + public static String getFileType(String fileName) + { + int separatorIndex = fileName.lastIndexOf("."); + if (separatorIndex < 0) + { + return ""; + } + return fileName.substring(separatorIndex + 1).toLowerCase(); + } + + /** + * 获取文件类型 + * + * @param photoByte 文件字节码 + * @return 后缀(不含".") + */ + public static String getFileExtendName(byte[] photoByte) + { + String strFileExtendName = "JPG"; + if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56) + && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97)) + { + strFileExtendName = "GIF"; + } + else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70)) + { + strFileExtendName = "JPG"; + } + else if ((photoByte[0] == 66) && (photoByte[1] == 77)) + { + strFileExtendName = "BMP"; + } + else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71)) + { + strFileExtendName = "PNG"; + } + return strFileExtendName; + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/file/FileUtils.java b/boyue-common/src/main/java/com/boyue/common/utils/file/FileUtils.java new file mode 100644 index 0000000..30238a8 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/file/FileUtils.java @@ -0,0 +1,405 @@ +package com.boyue.common.utils.file; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Objects; + +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.ArrayUtils; +import org.springframework.web.multipart.MultipartFile; + +import com.boyue.common.config.BoYueConfig; +import com.boyue.common.constant.Constants; +import com.boyue.common.exception.file.FileSizeLimitExceededException; +import com.boyue.common.exception.file.InvalidExtensionException; +import com.boyue.common.utils.DateUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.spring.SpringUtils; +import com.boyue.common.utils.uuid.IdUtils; +import com.boyue.common.utils.uuid.Seq; +import com.boyue.common.utils.uuid.UUID; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 文件处理工具类 + * + * @author boyue + */ +public class FileUtils { + public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+"; + + /** + * 默认的文件名最大长度 100 + */ + public static final int DEFAULT_FILE_NAME_LENGTH = 100; + public static final long DEFAULT_MAX_SIZE = Long.valueOf(SpringUtils.getRequiredProperty("boyue.fileMaxSize")) + * 1024 * 1024; + + /** + * 输出指定文件的byte数组 + * + * @param os 输出流 + * @return + */ + public static void writeBytes(InputStream inputStream, OutputStream os) throws IOException { + + try { + + byte[] b = new byte[1024]; + int length; + while ((length = inputStream.read(b)) > 0) { + os.write(b, 0, length); + } + } catch (IOException e) { + throw e; + } finally { + IOUtils.close(os); + IOUtils.close(inputStream); + } + } + + /** + * 写数据到文件中 + * + * @param data 数据 + * @return 目标文件 + * @throws IOException IO异常 + */ + public static String writeImportBytes(byte[] data) throws IOException { + return writeBytes(data, BoYueConfig.getImportPath()); + } + + /** + * 写数据到文件中 + * + * @param data 数据 + * @param uploadDir 目标文件 + * @return 目标文件 + * @throws IOException IO异常 + */ + public static String writeBytes(byte[] data, String uploadDir) throws IOException { + FileOutputStream fos = null; + String pathName = ""; + try { + String extension = getFileExtendName(data); + pathName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension; + File file = FileUtils.getAbsoluteFile(uploadDir, pathName); + fos = new FileOutputStream(file); + fos.write(data); + } finally { + IOUtils.close(fos); + } + return FileUtils.getPathFileName(uploadDir, pathName); + } + + /** + * 删除文件 + * + * @param filePath 文件 + * @return + */ + public static boolean deleteFile(String filePath) { + boolean flag = false; + File file = new File(filePath); + // 路径为文件且不为空则进行删除 + if (file.isFile() && file.exists()) { + flag = file.delete(); + } + return flag; + } + + /** + * 文件名称验证 + * + * @param filename 文件名称 + * @return true 正常 false 非法 + */ + public static boolean isValidFilename(String filename) { + return filename.matches(FILENAME_PATTERN); + } + + /** + * 检查文件是否可下载 + * + * @param resource 需要下载的文件 + * @return true 正常 false 非法 + */ + public static boolean checkAllowDownload(String resource) { + // 禁止目录上跳级别 + if (StringUtils.contains(resource, "..")) { + return false; + } + + // 检查允许下载的文件规则 + if (ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource))) { + return true; + } + + // 不在允许下载的文件规则 + return false; + } + + /** + * 下载文件名重新编码 + * + * @param request 请求对象 + * @param fileName 文件名 + * @return 编码后的文件名 + */ + public static String setFileDownloadHeader(HttpServletRequest request, String fileName) + throws UnsupportedEncodingException { + final String agent = request.getHeader("USER-AGENT"); + String filename = fileName; + if (agent.contains("MSIE")) { + // IE浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + filename = filename.replace("+", " "); + } else if (agent.contains("Firefox")) { + // 火狐浏览器 + filename = new String(fileName.getBytes(), "ISO8859-1"); + } else if (agent.contains("Chrome")) { + // google浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + } else { + // 其它浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + } + return filename; + } + + /** + * 下载文件名重新编码 + * + * @param response 响应对象 + * @param realFileName 真实文件名 + */ + public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) + throws UnsupportedEncodingException { + String percentEncodedFileName = percentEncode(realFileName); + + StringBuilder contentDispositionValue = new StringBuilder(); + contentDispositionValue.append("attachment; filename=") + .append(percentEncodedFileName) + .append(";") + .append("filename*=") + .append("utf-8''") + .append(percentEncodedFileName); + + response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename"); + response.setHeader("Content-disposition", contentDispositionValue.toString()); + response.setHeader("download-filename", percentEncodedFileName); + } + + /** + * 百分号编码工具方法 + * + * @param s 需要百分号编码的字符串 + * @return 百分号编码后的字符串 + */ + public static String percentEncode(String s) throws UnsupportedEncodingException { + String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString()); + return encode.replaceAll("\\+", "%20"); + } + + /** + * 获取图像后缀 + * + * @param photoByte 图像数据 + * @return 后缀名 + */ + public static String getFileExtendName(byte[] photoByte) { + String strFileExtendName = "jpg"; + if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56) + && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97)) { + strFileExtendName = "gif"; + } else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70)) { + strFileExtendName = "jpg"; + } else if ((photoByte[0] == 66) && (photoByte[1] == 77)) { + strFileExtendName = "bmp"; + } else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71)) { + strFileExtendName = "png"; + } + return strFileExtendName; + } + + /** + * 获取文件名称 /profile/upload/2022/04/16/boyue.png -- boyue.png + * + * @param fileName 路径名称 + * @return 没有文件路径的名称 + */ + public static String getName(String fileName) { + if (fileName == null) { + return null; + } + int lastUnixPos = fileName.lastIndexOf('/'); + int lastWindowsPos = fileName.lastIndexOf('\\'); + int index = Math.max(lastUnixPos, lastWindowsPos); + return fileName.substring(index + 1); + } + + /** + * 获取不带后缀文件名称 /profile/upload/2022/04/16/boyue.png -- boyue + * + * @param fileName 路径名称 + * @return 没有文件路径和后缀的名称 + */ + public static String getNameNotSuffix(String fileName) { + if (fileName == null) { + return null; + } + String baseName = FilenameUtils.getBaseName(fileName); + return baseName; + } + + /** + * 编码文件名 + */ + public static final String extractFilename(MultipartFile file) { + return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(), + FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), + getExtension(file)); + } + + public static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException { + File desc = new File(uploadDir + File.separator + fileName); + + if (!desc.exists()) { + if (!desc.getParentFile().exists()) { + desc.getParentFile().mkdirs(); + } + } + return desc; + } + + public static final File getAbsoluteFile(String filePath) throws IOException { + File desc = new File(filePath); + if (!desc.exists()) { + if (!desc.getParentFile().exists()) { + desc.getParentFile().mkdirs(); + } + } + return desc; + } + + public static final String getPathFileName(String uploadDir, String fileName) throws IOException { + int dirLastIndex = BoYueConfig.getProfile().length() + 1; + String currentDir = StringUtils.substring(uploadDir, dirLastIndex); + StringBuilder sb = new StringBuilder(); + sb.append(Constants.RESOURCE_PREFIX) + .append("/").append(currentDir) + .append("/").append(fileName); + return sb.toString().replace("\\", "/"); + } + + /** + * 文件大小校验 + * + * @param file 上传的文件 + * @return + * @throws FileSizeLimitExceededException 如果超出最大大小 + * @throws InvalidExtensionException + */ + public static final void assertAllowed(MultipartFile file, String[] allowedExtension) + throws FileSizeLimitExceededException, InvalidExtensionException { + long size = file.getSize(); + if (size > DEFAULT_MAX_SIZE) { + throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024); + } + + String fileName = file.getOriginalFilename(); + String extension = getExtension(file); + if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) { + if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) { + throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension, + fileName); + } else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) { + throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension, + fileName); + } else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) { + throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension, + fileName); + } else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION) { + throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension, + fileName); + } else { + throw new InvalidExtensionException(allowedExtension, extension, fileName); + } + } + } + + /** + * 判断MIME类型是否是允许的MIME类型 + * + * @param extension + * @param allowedExtension + * @return + */ + public static final boolean isAllowedExtension(String extension, String[] allowedExtension) { + for (String str : allowedExtension) { + if (str.equalsIgnoreCase(extension)) { + return true; + } + } + return false; + } + + /** + * 获取文件名的后缀 + * + * @param file 表单文件 + * @return 后缀名 + */ + public static final String getExtension(MultipartFile file) { + String extension = FilenameUtils.getExtension(file.getOriginalFilename()); + if (StringUtils.isEmpty(extension)) { + extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType())); + } + return extension; + } + + /** + * 获取相对路径 + * + * @param filePath 文件路径,可以是绝对路径或相对路径 + * @return 相对路径,将反斜杠替换为斜杠,如果输入是绝对路径则去除根路径部分。 + */ + public static final String getRelativePath(String filePath) { + Path absolute = Paths.get(filePath); + if (!absolute.isAbsolute()) { + if (filePath.startsWith("/") || filePath.startsWith("\\")) { + return filePath.replace("\\", "/").substring(1); + } else { + return filePath.replace("\\", "/"); + } + } + Path root = absolute.getRoot(); + Path normalize = absolute.normalize(); + String relativePath = normalize.subpath(root.getNameCount(), normalize.getNameCount()).toString(); + return relativePath.replace("\\", "/"); + } + + public static final boolean isAbsolutePath(String path) { + Path filePath = Paths.get(path); + return filePath.isAbsolute(); + } + + public static final String fastFilePath(MultipartFile file) { + return new StringBuilder(DateUtils.datePath()) + .append(File.separatorChar).append(DateUtils.dateTimeNow()) + .append("_").append(UUID.fastUUID().toString().substring(0, 4)) + .append(".").append(FileUtils.getExtension(file)).toString(); + } + +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/file/ImageUtils.java b/boyue-common/src/main/java/com/boyue/common/utils/file/ImageUtils.java new file mode 100644 index 0000000..46478be --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/file/ImageUtils.java @@ -0,0 +1,101 @@ +package com.boyue.common.utils.file; + +import com.boyue.common.config.BoYueConfig; +import com.boyue.common.constant.Constants; +import com.boyue.common.utils.StringUtils; +import org.apache.poi.util.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.InputStream; +import java.net.URI; +import java.net.URL; +import java.net.URLConnection; +import java.util.Arrays; + +/** + * 图片处理工具类 + * + * @author boyue + */ +public class ImageUtils +{ + private static final Logger log = LoggerFactory.getLogger(ImageUtils.class); + + public static byte[] getImage(String imagePath) + { + InputStream is = getFile(imagePath); + try + { + return IOUtils.toByteArray(is); + } + catch (Exception e) + { + log.error("图片加载异常 {}", e); + return null; + } + finally + { + IOUtils.closeQuietly(is); + } + } + + public static InputStream getFile(String imagePath) + { + try + { + byte[] result = readFile(imagePath); + result = Arrays.copyOf(result, result.length); + return new ByteArrayInputStream(result); + } + catch (Exception e) + { + log.error("获取图片异常 {}", e); + } + return null; + } + + /** + * 读取文件为字节数据 + * + * @param url 地址 + * @return 字节数据 + */ + public static byte[] readFile(String url) + { + InputStream in = null; + try + { + if (url.startsWith("http")) + { + // 网络地址 + URI uriObj = new URI(url); + URL urlObj = uriObj.toURL(); + URLConnection urlConnection = urlObj.openConnection(); + urlConnection.setConnectTimeout(30 * 1000); + urlConnection.setReadTimeout(60 * 1000); + urlConnection.setDoInput(true); + in = urlConnection.getInputStream(); + } + else + { + // 本机地址 + String localPath = BoYueConfig.getProfile(); + String downloadPath = localPath + StringUtils.substringAfter(url, Constants.RESOURCE_PREFIX); + in = new FileInputStream(downloadPath); + } + return IOUtils.toByteArray(in); + } + catch (Exception e) + { + log.error("获取文件路径异常 {}", e); + return null; + } + finally + { + IOUtils.closeQuietly(in); + } + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/file/MimeTypeUtils.java b/boyue-common/src/main/java/com/boyue/common/utils/file/MimeTypeUtils.java new file mode 100644 index 0000000..c486440 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/file/MimeTypeUtils.java @@ -0,0 +1,61 @@ +package com.boyue.common.utils.file; + +/** + * 媒体类型工具类 + * + * @author boyue + */ +public class MimeTypeUtils +{ + public static final String IMAGE_PNG = "image/png"; + + public static final String IMAGE_JPG = "image/jpg"; + + public static final String IMAGE_JPEG = "image/jpeg"; + + public static final String IMAGE_BMP = "image/bmp"; + + public static final String IMAGE_GIF = "image/gif"; + + public static final String[] IMAGE_EXTENSION = { "bmp", "gif", "jpg", "jpeg", "png" }; + + public static final String[] FLASH_EXTENSION = { "swf", "flv" }; + + public static final String[] MEDIA_EXTENSION = { "swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg", + "asf", "rm", "rmvb" }; + + public static final String[] VIDEO_EXTENSION = { "mp4", "avi", "rmvb" }; + + public static final String[] DEFAULT_ALLOWED_EXTENSION = { + // 图片 + "bmp", "gif", "jpg", "jpeg", "png", + // word excel powerpoint + "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt", + // 压缩文件 + "rar", "zip", "gz", "bz2", + // 音频格式 + "mp3", "wav", + // 视频格式 + "mp4", "avi", "rmvb", + // pdf + "pdf" }; + + public static String getExtension(String prefix) + { + switch (prefix) + { + case IMAGE_PNG: + return "png"; + case IMAGE_JPG: + return "jpg"; + case IMAGE_JPEG: + return "jpeg"; + case IMAGE_BMP: + return "bmp"; + case IMAGE_GIF: + return "gif"; + default: + return ""; + } + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/html/EscapeUtil.java b/boyue-common/src/main/java/com/boyue/common/utils/html/EscapeUtil.java new file mode 100644 index 0000000..d0dc42a --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/html/EscapeUtil.java @@ -0,0 +1,167 @@ +package com.boyue.common.utils.html; + +import com.boyue.common.utils.StringUtils; + +/** + * 转义和反转义工具类 + * + * @author boyue + */ +public class EscapeUtil +{ + public static final String RE_HTML_MARK = "(<[^<]*?>)|(<[\\s]*?/[^<]*?>)|(<[^<]*?/[\\s]*?>)"; + + private static final char[][] TEXT = new char[64][]; + + static + { + for (int i = 0; i < 64; i++) + { + TEXT[i] = new char[] { (char) i }; + } + + // special HTML characters + TEXT['\''] = "'".toCharArray(); // 单引号 + TEXT['"'] = """.toCharArray(); // 双引号 + TEXT['&'] = "&".toCharArray(); // &符 + TEXT['<'] = "<".toCharArray(); // 小于号 + TEXT['>'] = ">".toCharArray(); // 大于号 + } + + /** + * 转义文本中的HTML字符为安全的字符 + * + * @param text 被转义的文本 + * @return 转义后的文本 + */ + public static String escape(String text) + { + return encode(text); + } + + /** + * 还原被转义的HTML特殊字符 + * + * @param content 包含转义符的HTML内容 + * @return 转换后的字符串 + */ + public static String unescape(String content) + { + return decode(content); + } + + /** + * 清除所有HTML标签,但是不删除标签内的内容 + * + * @param content 文本 + * @return 清除标签后的文本 + */ + public static String clean(String content) + { + return new HTMLFilter().filter(content); + } + + /** + * Escape编码 + * + * @param text 被编码的文本 + * @return 编码后的字符 + */ + private static String encode(String text) + { + if (StringUtils.isEmpty(text)) + { + return StringUtils.EMPTY; + } + + final StringBuilder tmp = new StringBuilder(text.length() * 6); + char c; + for (int i = 0; i < text.length(); i++) + { + c = text.charAt(i); + if (c < 256) + { + tmp.append("%"); + if (c < 16) + { + tmp.append("0"); + } + tmp.append(Integer.toString(c, 16)); + } + else + { + tmp.append("%u"); + if (c <= 0xfff) + { + // issue#I49JU8@Gitee + tmp.append("0"); + } + tmp.append(Integer.toString(c, 16)); + } + } + return tmp.toString(); + } + + /** + * Escape解码 + * + * @param content 被转义的内容 + * @return 解码后的字符串 + */ + public static String decode(String content) + { + if (StringUtils.isEmpty(content)) + { + return content; + } + + StringBuilder tmp = new StringBuilder(content.length()); + int lastPos = 0, pos = 0; + char ch; + while (lastPos < content.length()) + { + pos = content.indexOf("%", lastPos); + if (pos == lastPos) + { + if (content.charAt(pos + 1) == 'u') + { + ch = (char) Integer.parseInt(content.substring(pos + 2, pos + 6), 16); + tmp.append(ch); + lastPos = pos + 6; + } + else + { + ch = (char) Integer.parseInt(content.substring(pos + 1, pos + 3), 16); + tmp.append(ch); + lastPos = pos + 3; + } + } + else + { + if (pos == -1) + { + tmp.append(content.substring(lastPos)); + lastPos = content.length(); + } + else + { + tmp.append(content.substring(lastPos, pos)); + lastPos = pos; + } + } + } + return tmp.toString(); + } + + public static void main(String[] args) + { + String html = ""; + String escape = EscapeUtil.escape(html); + // String html = "ipt>alert(\"XSS\")ipt>"; + // String html = "<123"; + // String html = "123>"; + System.out.println("clean: " + EscapeUtil.clean(html)); + System.out.println("escape: " + escape); + System.out.println("unescape: " + EscapeUtil.unescape(escape)); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/html/HTMLFilter.java b/boyue-common/src/main/java/com/boyue/common/utils/html/HTMLFilter.java new file mode 100644 index 0000000..44a0ca0 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/html/HTMLFilter.java @@ -0,0 +1,570 @@ +package com.boyue.common.utils.html; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * HTML过滤器,用于去除XSS漏洞隐患。 + * + * @author boyue + */ +public final class HTMLFilter +{ + /** + * regex flag union representing /si modifiers in php + **/ + private static final int REGEX_FLAGS_SI = Pattern.CASE_INSENSITIVE | Pattern.DOTALL; + private static final Pattern P_COMMENTS = Pattern.compile("", Pattern.DOTALL); + private static final Pattern P_COMMENT = Pattern.compile("^!--(.*)--$", REGEX_FLAGS_SI); + private static final Pattern P_TAGS = Pattern.compile("<(.*?)>", Pattern.DOTALL); + private static final Pattern P_END_TAG = Pattern.compile("^/([a-z0-9]+)", REGEX_FLAGS_SI); + private static final Pattern P_START_TAG = Pattern.compile("^([a-z0-9]+)(.*?)(/?)$", REGEX_FLAGS_SI); + private static final Pattern P_QUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)=([\"'])(.*?)\\2", REGEX_FLAGS_SI); + private static final Pattern P_UNQUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)(=)([^\"\\s']+)", REGEX_FLAGS_SI); + private static final Pattern P_PROTOCOL = Pattern.compile("^([^:]+):", REGEX_FLAGS_SI); + private static final Pattern P_ENTITY = Pattern.compile("&#(\\d+);?"); + private static final Pattern P_ENTITY_UNICODE = Pattern.compile("&#x([0-9a-f]+);?"); + private static final Pattern P_ENCODE = Pattern.compile("%([0-9a-f]{2});?"); + private static final Pattern P_VALID_ENTITIES = Pattern.compile("&([^&;]*)(?=(;|&|$))"); + private static final Pattern P_VALID_QUOTES = Pattern.compile("(>|^)([^<]+?)(<|$)", Pattern.DOTALL); + private static final Pattern P_END_ARROW = Pattern.compile("^>"); + private static final Pattern P_BODY_TO_END = Pattern.compile("<([^>]*?)(?=<|$)"); + private static final Pattern P_XML_CONTENT = Pattern.compile("(^|>)([^<]*?)(?=>)"); + private static final Pattern P_STRAY_LEFT_ARROW = Pattern.compile("<([^>]*?)(?=<|$)"); + private static final Pattern P_STRAY_RIGHT_ARROW = Pattern.compile("(^|>)([^<]*?)(?=>)"); + private static final Pattern P_AMP = Pattern.compile("&"); + private static final Pattern P_QUOTE = Pattern.compile("\""); + private static final Pattern P_LEFT_ARROW = Pattern.compile("<"); + private static final Pattern P_RIGHT_ARROW = Pattern.compile(">"); + private static final Pattern P_BOTH_ARROWS = Pattern.compile("<>"); + + // @xxx could grow large... maybe use sesat's ReferenceMap + private static final ConcurrentMap P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap<>(); + private static final ConcurrentMap P_REMOVE_SELF_BLANKS = new ConcurrentHashMap<>(); + + /** + * set of allowed html elements, along with allowed attributes for each element + **/ + private final Map> vAllowed; + /** + * counts of open tags for each (allowable) html element + **/ + private final Map vTagCounts = new HashMap<>(); + + /** + * html elements which must always be self-closing (e.g. "") + **/ + private final String[] vSelfClosingTags; + /** + * html elements which must always have separate opening and closing tags (e.g. "") + **/ + private final String[] vNeedClosingTags; + /** + * set of disallowed html elements + **/ + private final String[] vDisallowed; + /** + * attributes which should be checked for valid protocols + **/ + private final String[] vProtocolAtts; + /** + * allowed protocols + **/ + private final String[] vAllowedProtocols; + /** + * tags which should be removed if they contain no content (e.g. "" or "") + **/ + private final String[] vRemoveBlanks; + /** + * entities allowed within html markup + **/ + private final String[] vAllowedEntities; + /** + * flag determining whether comments are allowed in input String. + */ + private final boolean stripComment; + private final boolean encodeQuotes; + /** + * flag determining whether to try to make tags when presented with "unbalanced" angle brackets (e.g. "" + * becomes " text "). If set to false, unbalanced angle brackets will be html escaped. + */ + private final boolean alwaysMakeTags; + + /** + * Default constructor. + */ + public HTMLFilter() + { + vAllowed = new HashMap<>(); + + final ArrayList a_atts = new ArrayList<>(); + a_atts.add("href"); + a_atts.add("target"); + vAllowed.put("a", a_atts); + + final ArrayList img_atts = new ArrayList<>(); + img_atts.add("src"); + img_atts.add("width"); + img_atts.add("height"); + img_atts.add("alt"); + vAllowed.put("img", img_atts); + + final ArrayList no_atts = new ArrayList<>(); + vAllowed.put("b", no_atts); + vAllowed.put("strong", no_atts); + vAllowed.put("i", no_atts); + vAllowed.put("em", no_atts); + + vSelfClosingTags = new String[] { "img" }; + vNeedClosingTags = new String[] { "a", "b", "strong", "i", "em" }; + vDisallowed = new String[] {}; + vAllowedProtocols = new String[] { "http", "mailto", "https" }; // no ftp. + vProtocolAtts = new String[] { "src", "href" }; + vRemoveBlanks = new String[] { "a", "b", "strong", "i", "em" }; + vAllowedEntities = new String[] { "amp", "gt", "lt", "quot" }; + stripComment = true; + encodeQuotes = true; + alwaysMakeTags = false; + } + + /** + * Map-parameter configurable constructor. + * + * @param conf map containing configuration. keys match field names. + */ + @SuppressWarnings("unchecked") + public HTMLFilter(final Map conf) + { + + assert conf.containsKey("vAllowed") : "configuration requires vAllowed"; + assert conf.containsKey("vSelfClosingTags") : "configuration requires vSelfClosingTags"; + assert conf.containsKey("vNeedClosingTags") : "configuration requires vNeedClosingTags"; + assert conf.containsKey("vDisallowed") : "configuration requires vDisallowed"; + assert conf.containsKey("vAllowedProtocols") : "configuration requires vAllowedProtocols"; + assert conf.containsKey("vProtocolAtts") : "configuration requires vProtocolAtts"; + assert conf.containsKey("vRemoveBlanks") : "configuration requires vRemoveBlanks"; + assert conf.containsKey("vAllowedEntities") : "configuration requires vAllowedEntities"; + + vAllowed = Collections.unmodifiableMap((HashMap>) conf.get("vAllowed")); + vSelfClosingTags = (String[]) conf.get("vSelfClosingTags"); + vNeedClosingTags = (String[]) conf.get("vNeedClosingTags"); + vDisallowed = (String[]) conf.get("vDisallowed"); + vAllowedProtocols = (String[]) conf.get("vAllowedProtocols"); + vProtocolAtts = (String[]) conf.get("vProtocolAtts"); + vRemoveBlanks = (String[]) conf.get("vRemoveBlanks"); + vAllowedEntities = (String[]) conf.get("vAllowedEntities"); + stripComment = conf.containsKey("stripComment") ? (Boolean) conf.get("stripComment") : true; + encodeQuotes = conf.containsKey("encodeQuotes") ? (Boolean) conf.get("encodeQuotes") : true; + alwaysMakeTags = conf.containsKey("alwaysMakeTags") ? (Boolean) conf.get("alwaysMakeTags") : true; + } + + private void reset() + { + vTagCounts.clear(); + } + + // --------------------------------------------------------------- + // my versions of some PHP library functions + public static String chr(final int decimal) + { + return String.valueOf((char) decimal); + } + + public static String htmlSpecialChars(final String s) + { + String result = s; + result = regexReplace(P_AMP, "&", result); + result = regexReplace(P_QUOTE, """, result); + result = regexReplace(P_LEFT_ARROW, "<", result); + result = regexReplace(P_RIGHT_ARROW, ">", result); + return result; + } + + // --------------------------------------------------------------- + + /** + * given a user submitted input String, filter out any invalid or restricted html. + * + * @param input text (i.e. submitted by a user) than may contain html + * @return "clean" version of input, with only valid, whitelisted html elements allowed + */ + public String filter(final String input) + { + reset(); + String s = input; + + s = escapeComments(s); + + s = balanceHTML(s); + + s = checkTags(s); + + s = processRemoveBlanks(s); + + // s = validateEntities(s); + + return s; + } + + public boolean isAlwaysMakeTags() + { + return alwaysMakeTags; + } + + public boolean isStripComments() + { + return stripComment; + } + + private String escapeComments(final String s) + { + final Matcher m = P_COMMENTS.matcher(s); + final StringBuffer buf = new StringBuffer(); + if (m.find()) + { + final String match = m.group(1); // (.*?) + m.appendReplacement(buf, Matcher.quoteReplacement("")); + } + m.appendTail(buf); + + return buf.toString(); + } + + private String balanceHTML(String s) + { + if (alwaysMakeTags) + { + // + // try and form html + // + s = regexReplace(P_END_ARROW, "", s); + // 不追加结束标签 + s = regexReplace(P_BODY_TO_END, "<$1>", s); + s = regexReplace(P_XML_CONTENT, "$1<$2", s); + + } + else + { + // + // escape stray brackets + // + s = regexReplace(P_STRAY_LEFT_ARROW, "<$1", s); + s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2><", s); + + // + // the last regexp causes '<>' entities to appear + // (we need to do a lookahead assertion so that the last bracket can + // be used in the next pass of the regexp) + // + s = regexReplace(P_BOTH_ARROWS, "", s); + } + + return s; + } + + private String checkTags(String s) + { + Matcher m = P_TAGS.matcher(s); + + final StringBuffer buf = new StringBuffer(); + while (m.find()) + { + String replaceStr = m.group(1); + replaceStr = processTag(replaceStr); + m.appendReplacement(buf, Matcher.quoteReplacement(replaceStr)); + } + m.appendTail(buf); + + // these get tallied in processTag + // (remember to reset before subsequent calls to filter method) + final StringBuilder sBuilder = new StringBuilder(buf.toString()); + for (String key : vTagCounts.keySet()) + { + for (int ii = 0; ii < vTagCounts.get(key); ii++) + { + sBuilder.append(""); + } + } + s = sBuilder.toString(); + + return s; + } + + private String processRemoveBlanks(final String s) + { + String result = s; + for (String tag : vRemoveBlanks) + { + if (!P_REMOVE_PAIR_BLANKS.containsKey(tag)) + { + P_REMOVE_PAIR_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?>")); + } + result = regexReplace(P_REMOVE_PAIR_BLANKS.get(tag), "", result); + if (!P_REMOVE_SELF_BLANKS.containsKey(tag)) + { + P_REMOVE_SELF_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?/>")); + } + result = regexReplace(P_REMOVE_SELF_BLANKS.get(tag), "", result); + } + + return result; + } + + private static String regexReplace(final Pattern regex_pattern, final String replacement, final String s) + { + Matcher m = regex_pattern.matcher(s); + return m.replaceAll(replacement); + } + + private String processTag(final String s) + { + // ending tags + Matcher m = P_END_TAG.matcher(s); + if (m.find()) + { + final String name = m.group(1).toLowerCase(); + if (allowed(name)) + { + if (!inArray(name, vSelfClosingTags)) + { + if (vTagCounts.containsKey(name)) + { + vTagCounts.put(name, vTagCounts.get(name) - 1); + return ""; + } + } + } + } + + // starting tags + m = P_START_TAG.matcher(s); + if (m.find()) + { + final String name = m.group(1).toLowerCase(); + final String body = m.group(2); + String ending = m.group(3); + + // debug( "in a starting tag, name='" + name + "'; body='" + body + "'; ending='" + ending + "'" ); + if (allowed(name)) + { + final StringBuilder params = new StringBuilder(); + + final Matcher m2 = P_QUOTED_ATTRIBUTES.matcher(body); + final Matcher m3 = P_UNQUOTED_ATTRIBUTES.matcher(body); + final List paramNames = new ArrayList<>(); + final List paramValues = new ArrayList<>(); + while (m2.find()) + { + paramNames.add(m2.group(1)); // ([a-z0-9]+) + paramValues.add(m2.group(3)); // (.*?) + } + while (m3.find()) + { + paramNames.add(m3.group(1)); // ([a-z0-9]+) + paramValues.add(m3.group(3)); // ([^\"\\s']+) + } + + String paramName, paramValue; + for (int ii = 0; ii < paramNames.size(); ii++) + { + paramName = paramNames.get(ii).toLowerCase(); + paramValue = paramValues.get(ii); + + // debug( "paramName='" + paramName + "'" ); + // debug( "paramValue='" + paramValue + "'" ); + // debug( "allowed? " + vAllowed.get( name ).contains( paramName ) ); + + if (allowedAttribute(name, paramName)) + { + if (inArray(paramName, vProtocolAtts)) + { + paramValue = processParamProtocol(paramValue); + } + params.append(' ').append(paramName).append("=\\\"").append(paramValue).append("\\\""); + } + } + + if (inArray(name, vSelfClosingTags)) + { + ending = " /"; + } + + if (inArray(name, vNeedClosingTags)) + { + ending = ""; + } + + if (ending == null || ending.length() < 1) + { + if (vTagCounts.containsKey(name)) + { + vTagCounts.put(name, vTagCounts.get(name) + 1); + } + else + { + vTagCounts.put(name, 1); + } + } + else + { + ending = " /"; + } + return "<" + name + params + ending + ">"; + } + else + { + return ""; + } + } + + // comments + m = P_COMMENT.matcher(s); + if (!stripComment && m.find()) + { + return "<" + m.group() + ">"; + } + + return ""; + } + + private String processParamProtocol(String s) + { + s = decodeEntities(s); + final Matcher m = P_PROTOCOL.matcher(s); + if (m.find()) + { + final String protocol = m.group(1); + if (!inArray(protocol, vAllowedProtocols)) + { + // bad protocol, turn into local anchor link instead + s = "#" + s.substring(protocol.length() + 1); + if (s.startsWith("#//")) + { + s = "#" + s.substring(3); + } + } + } + + return s; + } + + private String decodeEntities(String s) + { + StringBuffer buf = new StringBuffer(); + + Matcher m = P_ENTITY.matcher(s); + while (m.find()) + { + final String match = m.group(1); + final int decimal = Integer.decode(match).intValue(); + m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); + } + m.appendTail(buf); + s = buf.toString(); + + buf = new StringBuffer(); + m = P_ENTITY_UNICODE.matcher(s); + while (m.find()) + { + final String match = m.group(1); + final int decimal = Integer.valueOf(match, 16).intValue(); + m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); + } + m.appendTail(buf); + s = buf.toString(); + + buf = new StringBuffer(); + m = P_ENCODE.matcher(s); + while (m.find()) + { + final String match = m.group(1); + final int decimal = Integer.valueOf(match, 16).intValue(); + m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); + } + m.appendTail(buf); + s = buf.toString(); + + s = validateEntities(s); + return s; + } + + private String validateEntities(final String s) + { + StringBuffer buf = new StringBuffer(); + + // validate entities throughout the string + Matcher m = P_VALID_ENTITIES.matcher(s); + while (m.find()) + { + final String one = m.group(1); // ([^&;]*) + final String two = m.group(2); // (?=(;|&|$)) + m.appendReplacement(buf, Matcher.quoteReplacement(checkEntity(one, two))); + } + m.appendTail(buf); + + return encodeQuotes(buf.toString()); + } + + private String encodeQuotes(final String s) + { + if (encodeQuotes) + { + StringBuffer buf = new StringBuffer(); + Matcher m = P_VALID_QUOTES.matcher(s); + while (m.find()) + { + final String one = m.group(1); // (>|^) + final String two = m.group(2); // ([^<]+?) + final String three = m.group(3); // (<|$) + // 不替换双引号为",防止json格式无效 regexReplace(P_QUOTE, """, two) + m.appendReplacement(buf, Matcher.quoteReplacement(one + two + three)); + } + m.appendTail(buf); + return buf.toString(); + } + else + { + return s; + } + } + + private String checkEntity(final String preamble, final String term) + { + + return ";".equals(term) && isValidEntity(preamble) ? '&' + preamble : "&" + preamble; + } + + private boolean isValidEntity(final String entity) + { + return inArray(entity, vAllowedEntities); + } + + private static boolean inArray(final String s, final String[] array) + { + for (String item : array) + { + if (item != null && item.equals(s)) + { + return true; + } + } + return false; + } + + private boolean allowed(final String name) + { + return (vAllowed.isEmpty() || vAllowed.containsKey(name)) && !inArray(name, vDisallowed); + } + + private boolean allowedAttribute(final String name, final String paramName) + { + return allowed(name) && (vAllowed.isEmpty() || vAllowed.get(name).contains(paramName)); + } +} \ No newline at end of file diff --git a/boyue-common/src/main/java/com/boyue/common/utils/http/HttpClientUtil.java b/boyue-common/src/main/java/com/boyue/common/utils/http/HttpClientUtil.java new file mode 100644 index 0000000..53fe7f0 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/http/HttpClientUtil.java @@ -0,0 +1,322 @@ +package com.boyue.common.utils.http; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.conn.ssl.DefaultHostnameVerifier; +import org.apache.http.conn.util.PublicSuffixMatcher; +import org.apache.http.conn.util.PublicSuffixMatcherLoader; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.fastjson2.JSON; + +public class HttpClientUtil { + + private static final Logger log = LoggerFactory.getLogger(HttpClientUtil.class); + /** + * 默认参数设置 + * setConnectTimeout:设置连接超时时间,单位毫秒。 + * setConnectionRequestTimeout:设置从connect Manager获取Connection 超时时间,单位毫秒。 + * setSocketTimeout:请求获取数据的超时时间,单位毫秒。访问一个接口,多少时间内无法返回数据,就直接放弃此次调用。 暂时定义15分钟 + */ + private static RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(600000) + .setConnectTimeout(600000) + .setConnectionRequestTimeout(600000).build(); + + private static CloseableHttpClient client = HttpUtils.initClient(); + + /** + * 发送 post请求 + * + * @param httpUrl 地址 + */ + public static String sendHttpPost(String httpUrl) { + HttpPost httpPost = new HttpPost(httpUrl);// 创建httpPost + return sendHttpPost(httpPost); + } + + /** + * 发送 post请求 + * + * @param httpUrl 地址 + * @param params 参数(格式:key1=value1&key2=value2) + */ + public static String sendHttpPost(String httpUrl, String params) { + HttpPost httpPost = new HttpPost(httpUrl);// 创建httpPost + try { + // 设置参数 + StringEntity stringEntity = new StringEntity(params, "UTF-8"); + stringEntity.setContentType("application/x-www-form-urlencoded"); + httpPost.setEntity(stringEntity); + } catch (Exception e) { + e.printStackTrace(); + } + return sendHttpPost(httpPost); + } + + public static String sendHttpPost(String httpUrl, Object object) { + HttpPost httpPost = new HttpPost(httpUrl);// 创建httpPost + String jsonString = JSON.toJSONString(object); + httpPost.setHeader("Accept", ContentType.APPLICATION_JSON.getMimeType()); + httpPost.setHeader("Content-Type", ContentType.APPLICATION_JSON.getMimeType()); + log.debug(jsonString); + try { + + // 设置参数 + StringEntity stringEntity = new StringEntity(jsonString, ContentType.APPLICATION_JSON); + + httpPost.setEntity(stringEntity); + } catch (Exception e) { + e.printStackTrace(); + } + return sendHttpPost(httpPost); + } + + public static String sendHttpPost(String httpUrl, Object data, Map headersMap) { + HttpPost httpPost = new HttpPost(httpUrl);// 创建httpPost + String jsonString = JSON.toJSONString(data); + httpPost.setHeader("Accept", ContentType.APPLICATION_JSON.getMimeType()); + httpPost.setHeader("Content-Type", ContentType.APPLICATION_JSON.getMimeType()); + for (String keySet : headersMap.keySet()) { + httpPost.setHeader(keySet, headersMap.get(keySet)); + } + log.debug(jsonString); + try { + // 设置参数 + StringEntity stringEntity = new StringEntity(jsonString, ContentType.APPLICATION_JSON); + + httpPost.setEntity(stringEntity); + } catch (Exception e) { + e.printStackTrace(); + } + return sendHttpPost(httpPost); + } + + /** + * 发送 post请求 + * + * @param httpUrl 地址 + * @param maps 参数 + */ + public static String sendHttpPost(String httpUrl, Map maps) { + HttpPost httpPost = new HttpPost(httpUrl);// 创建httpPost + // 创建参数队列 + List nameValuePairs = new ArrayList(); + for (String key : maps.keySet()) { + nameValuePairs.add(new BasicNameValuePair(key, maps.get(key))); + } + try { + httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, "UTF-8")); + } catch (Exception e) { + e.printStackTrace(); + } + return sendHttpPost(httpPost); + } + + /** + * 发送 get请求 + * + * @param httpUrl + */ + public static String sendHttpGet(String httpUrl) { + HttpGet httpGet = new HttpGet(httpUrl);// 创建get请求 + return sendHttpGet(httpGet); + } + + /** + * 发送 get请求Https + * + * @param httpUrl + */ + public static String sendHttpsGet(String httpUrl) { + HttpGet httpGet = new HttpGet(httpUrl);// 创建get请求 + return sendHttpsGet(httpGet); + } + + /** + * 发送Post请求 + * + * @param httpPost + * @return + */ + private static String sendHttpPost(HttpPost httpPost) { + CloseableHttpClient httpClient = null; + CloseableHttpResponse response = null; + HttpEntity entity = null; + String responseContent = null; + try { + // 创建默认的httpClient实例 + // httpClient = HttpClients.createDefault(); + httpPost.setConfig(requestConfig); + // 执行请求 + long execStart = System.currentTimeMillis(); + response = client.execute(httpPost); + long execEnd = System.currentTimeMillis(); + log.debug("=================执行post请求耗时:" + (execEnd - execStart) + "ms"); + long getStart = System.currentTimeMillis(); + entity = response.getEntity(); + responseContent = EntityUtils.toString(entity, "UTF-8"); + long getEnd = System.currentTimeMillis(); + log.debug("=================获取响应结果耗时:" + (getEnd - getStart) + "ms"); + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + // 关闭连接,释放资源 + if (response != null) { + response.close(); + } + if (httpClient != null) { + httpClient.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return responseContent; + } + + /** + * 发送Get请求 + * + * @param httpGet + * @return + */ + private static String sendHttpGet(HttpGet httpGet) { + CloseableHttpClient httpClient = null; + CloseableHttpResponse response = null; + HttpEntity entity = null; + String responseContent = null; + try { + // 创建默认的httpClient实例. + + httpGet.setConfig(requestConfig); + // 执行请求 + response = client.execute(httpGet); + entity = response.getEntity(); + responseContent = EntityUtils.toString(entity, "UTF-8"); + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + // 关闭连接,释放资源 + if (response != null) { + response.close(); + } + if (httpClient != null) { + httpClient.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return responseContent; + } + + /** + * 发送Get请求Https + * + * @param httpGet + * @return + */ + private static String sendHttpsGet(HttpGet httpGet) { + CloseableHttpClient httpClient = null; + CloseableHttpResponse response = null; + HttpEntity entity = null; + String responseContent = null; + try { + // 创建默认的httpClient实例. + PublicSuffixMatcher publicSuffixMatcher = PublicSuffixMatcherLoader + .load(httpGet.getURI().toURL()); + DefaultHostnameVerifier hostnameVerifier = new DefaultHostnameVerifier(publicSuffixMatcher); + httpClient = HttpClients.custom().setSSLHostnameVerifier(hostnameVerifier).build(); + httpGet.setConfig(requestConfig); + // 执行请求 + response = httpClient.execute(httpGet); + entity = response.getEntity(); + responseContent = EntityUtils.toString(entity, "UTF-8"); + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + // 关闭连接,释放资源 + if (response != null) { + response.close(); + } + if (httpClient != null) { + httpClient.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return responseContent; + } + + /** + * 发送xml数据 + * + * @param url + * @param xmlData + * @return + * @throws ClientProtocolException + * @throws IOException + */ + public static HttpResponse sendXMLDataByPost(String url, String xmlData) + throws ClientProtocolException, IOException { + + HttpPost httppost = new HttpPost(url); + StringEntity entity = new StringEntity(xmlData); + httppost.setEntity(entity); + httppost.setHeader("Content-Type", "text/xml;charset=UTF-8"); + HttpResponse response = client.execute(httppost); + return response; + } + + /** + * 获得响应HTTP实体内容 + * + * @param response + * @return + * @throws IOException + * @throws UnsupportedEncodingException + */ + public static String getHttpEntityContent(HttpResponse response) throws IOException, UnsupportedEncodingException { + HttpEntity entity = response.getEntity(); + if (entity != null) { + InputStream is = entity.getContent(); + BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8")); + String line = br.readLine(); + StringBuilder sb = new StringBuilder(); + while (line != null) { + sb.append(line + "\n"); + line = br.readLine(); + } + return sb.toString(); + } + return ""; + } + +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/http/HttpConf.java b/boyue-common/src/main/java/com/boyue/common/utils/http/HttpConf.java new file mode 100644 index 0000000..571f9d2 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/http/HttpConf.java @@ -0,0 +1,33 @@ +package com.boyue.common.utils.http; + +/** + * http 配置信息 + * + * @author boyue + */ +public class HttpConf +{ + // 获取连接的最大等待时间 + public static int WAIT_TIMEOUT = 10000; + + // 连接超时时间 + public static int CONNECT_TIMEOUT = 10000; + + // 读取超时时间 + public static int SO_TIMEOUT = 60000; + + // 最大连接数 + public static int MAX_TOTAL_CONN = 200; + + // 每个路由最大连接数 + public static int MAX_ROUTE_CONN = 150; + + // 重试次数 + public static int RETRY_COUNT = 3; + + // EPTWebServes地址 + public static String EPTWEBSERVES_URL; + + // tomcat默认keepAliveTimeout为20s + public static int KEEP_ALIVE_TIMEOUT; +} \ No newline at end of file diff --git a/boyue-common/src/main/java/com/boyue/common/utils/http/HttpHelper.java b/boyue-common/src/main/java/com/boyue/common/utils/http/HttpHelper.java new file mode 100644 index 0000000..e692194 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/http/HttpHelper.java @@ -0,0 +1,55 @@ +package com.boyue.common.utils.http; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import jakarta.servlet.ServletRequest; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 通用http工具封装 + * + * @author boyue + */ +public class HttpHelper +{ + private static final Logger LOGGER = LoggerFactory.getLogger(HttpHelper.class); + + public static String getBodyString(ServletRequest request) + { + StringBuilder sb = new StringBuilder(); + BufferedReader reader = null; + try (InputStream inputStream = request.getInputStream()) + { + reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + String line = ""; + while ((line = reader.readLine()) != null) + { + sb.append(line); + } + } + catch (IOException e) + { + LOGGER.warn("getBodyString出现问题!"); + } + finally + { + if (reader != null) + { + try + { + reader.close(); + } + catch (IOException e) + { + LOGGER.error(ExceptionUtils.getMessage(e)); + } + } + } + return sb.toString(); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/http/HttpUtils.java b/boyue-common/src/main/java/com/boyue/common/utils/http/HttpUtils.java new file mode 100644 index 0000000..927109f --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/http/HttpUtils.java @@ -0,0 +1,748 @@ +package com.boyue.common.utils.http; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.ConnectException; +import java.net.SocketTimeoutException; +import java.net.URI; +import java.net.URL; +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.NameValuePair; +import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.config.RequestConfig.Builder; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.conn.ConnectTimeoutException; +import org.apache.http.conn.ConnectionKeepAliveStrategy; +import org.apache.http.conn.HttpClientConnectionManager; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.TrustStrategy; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.DefaultHttpRequestRetryHandler; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.protocol.HttpContext; +import org.apache.http.ssl.SSLContextBuilder; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.boyue.common.constant.Constants; +import com.boyue.common.utils.StringUtils; + +/** + * 通用http发送方法 + * + * @author boyue + */ +public class HttpUtils +{ + private static final Logger log = LoggerFactory.getLogger(HttpUtils.class); + + public static RequestConfig requestConfig; + + private static CloseableHttpClient httpClient; + + private static PoolingHttpClientConnectionManager connMgr; + + private static IdleConnectionMonitorThread idleThread; + + static + { + HttpUtils.initClient(); + } + + /** + * 向指定 URL 发送GET方法的请求 + * + * @param url 发送请求的 URL + * @return 所代表远程资源的响应结果 + */ + public static String sendGet(String url) + { + return sendGet(url, StringUtils.EMPTY); + } + + /** + * 向指定 URL 发送GET方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @return 所代表远程资源的响应结果 + */ + public static String sendGet(String url, String param) + { + return sendGet(url, param, Constants.UTF8); + } + + /** + * 向指定 URL 发送GET方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @param contentType 编码类型 + * @return 所代表远程资源的响应结果 + */ + public static String sendGet(String url, String param, String contentType) + { + StringBuilder result = new StringBuilder(); + BufferedReader in = null; + try + { + String urlNameString = StringUtils.isNotBlank(param) ? url + "?" + param : url; + log.info("sendGet - {}", urlNameString); + URI uri = new URI(urlNameString); + URL realUrl = uri.toURL(); + URLConnection connection = realUrl.openConnection(); + connection.setRequestProperty("accept", "*/*"); + connection.setRequestProperty("connection", "Keep-Alive"); + connection.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); + connection.connect(); + in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType)); + String line; + while ((line = in.readLine()) != null) + { + result.append(line); + } + log.info("recv - {}", result); + } + catch (ConnectException e) + { + log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e); + } + catch (SocketTimeoutException e) + { + log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e); + } + catch (IOException e) + { + log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e); + } + catch (Exception e) + { + log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e); + } + finally + { + try + { + if (in != null) + { + in.close(); + } + } + catch (Exception ex) + { + log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); + } + } + return result.toString(); + } + + /** + * 向指定 URL 发送POST方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @return 所代表远程资源的响应结果 + */ + public static String sendPost(String url, String param) + { + PrintWriter out = null; + BufferedReader in = null; + StringBuilder result = new StringBuilder(); + try + { + log.info("sendPost - {}", url); + URI uri = new URI(url); + URL realUrl = uri.toURL(); + URLConnection conn = realUrl.openConnection(); + conn.setRequestProperty("accept", "*/*"); + conn.setRequestProperty("connection", "Keep-Alive"); + conn.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); + conn.setRequestProperty("Accept-Charset", "utf-8"); + conn.setRequestProperty("contentType", "utf-8"); + conn.setDoOutput(true); + conn.setDoInput(true); + out = new PrintWriter(conn.getOutputStream()); + out.print(param); + out.flush(); + in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8)); + String line; + while ((line = in.readLine()) != null) + { + result.append(line); + } + log.info("recv - {}", result); + } + catch (ConnectException e) + { + log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e); + } + catch (SocketTimeoutException e) + { + log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e); + } + catch (IOException e) + { + log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e); + } + catch (Exception e) + { + log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e); + } + finally + { + try + { + if (out != null) + { + out.close(); + } + if (in != null) + { + in.close(); + } + } + catch (IOException ex) + { + log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); + } + } + return result.toString(); + } + + public static String sendSSLPost(String url, String param) + { + StringBuilder result = new StringBuilder(); + String urlNameString = url + "?" + param; + try + { + log.info("sendSSLPost - {}", urlNameString); + SSLContext sc = SSLContext.getInstance("SSL"); + sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom()); + URI uri = new URI(urlNameString); + URL console = uri.toURL(); + HttpsURLConnection conn = (HttpsURLConnection) console.openConnection(); + conn.setRequestProperty("accept", "*/*"); + conn.setRequestProperty("connection", "Keep-Alive"); + conn.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); + conn.setRequestProperty("Accept-Charset", "utf-8"); + conn.setRequestProperty("contentType", "utf-8"); + conn.setDoOutput(true); + conn.setDoInput(true); + + conn.setSSLSocketFactory(sc.getSocketFactory()); + conn.setHostnameVerifier(new TrustAnyHostnameVerifier()); + conn.connect(); + InputStream is = conn.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(is)); + String ret = ""; + while ((ret = br.readLine()) != null) + { + if (ret != null && !ret.trim().equals("")) + { + result.append(new String(ret.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8)); + } + } + log.info("recv - {}", result); + conn.disconnect(); + br.close(); + } + catch (ConnectException e) + { + log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e); + } + catch (SocketTimeoutException e) + { + log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e); + } + catch (IOException e) + { + log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e); + } + catch (Exception e) + { + log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e); + } + return result.toString(); + } + + private static class TrustAnyTrustManager implements X509TrustManager + { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) + { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) + { + } + + @Override + public X509Certificate[] getAcceptedIssuers() + { + return new X509Certificate[] {}; + } + } + + private static class TrustAnyHostnameVerifier implements HostnameVerifier + { + @Override + public boolean verify(String hostname, SSLSession session) + { + return true; + } + } + + /** + * 获取httpClient + * + * @return + */ + public static CloseableHttpClient getHttpClient() + { + if (httpClient != null) + { + return httpClient; + } + else + { + return HttpClients.createDefault(); + } + } + + /** + * 创建连接池管理器 + * + * @return + */ + private static PoolingHttpClientConnectionManager createConnectionManager() + { + + PoolingHttpClientConnectionManager connMgr = new PoolingHttpClientConnectionManager(); + // 将最大连接数增加到 + connMgr.setMaxTotal(HttpConf.MAX_TOTAL_CONN); + // 将每个路由基础的连接增加到 + connMgr.setDefaultMaxPerRoute(HttpConf.MAX_ROUTE_CONN); + + return connMgr; + } + + /** + * 根据当前配置创建HTTP请求配置参数。 + * + * @return 返回HTTP请求配置。 + */ + private static RequestConfig createRequestConfig() + { + Builder builder = RequestConfig.custom(); + builder.setConnectionRequestTimeout(StringUtils.nvl(HttpConf.WAIT_TIMEOUT, 10000)); + builder.setConnectTimeout(StringUtils.nvl(HttpConf.CONNECT_TIMEOUT, 10000)); + builder.setSocketTimeout(StringUtils.nvl(HttpConf.SO_TIMEOUT, 60000)); + return builder.build(); + } + + /** + * 创建默认的HTTPS客户端,信任所有的证书。 + * + * @return 返回HTTPS客户端,如果创建失败,返回HTTP客户端。 + */ + private static CloseableHttpClient createHttpClient(HttpClientConnectionManager connMgr) + { + try + { + final SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() + { + @Override + public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException + { + // 信任所有 + return true; + } + }).build(); + final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext); + + // 重试机制 + HttpRequestRetryHandler retryHandler = new DefaultHttpRequestRetryHandler(HttpConf.RETRY_COUNT, true); + ConnectionKeepAliveStrategy connectionKeepAliveStrategy = new ConnectionKeepAliveStrategy() + { + @Override + public long getKeepAliveDuration(HttpResponse httpResponse, HttpContext httpContext) + { + return HttpConf.KEEP_ALIVE_TIMEOUT; // tomcat默认keepAliveTimeout为20s + } + }; + httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).setConnectionManager(connMgr) + .setDefaultRequestConfig(requestConfig).setRetryHandler(retryHandler) + .setKeepAliveStrategy(connectionKeepAliveStrategy).build(); + } + catch (Exception e) + { + log.error("Create http client failed", e); + httpClient = HttpClients.createDefault(); + } + + return httpClient; + } + + /** + * 初始化 只需调用一次 + */ + public synchronized static CloseableHttpClient initClient() + { + if (httpClient == null) + { + connMgr = createConnectionManager(); + requestConfig = createRequestConfig(); + // 初始化httpClient连接池 + httpClient = createHttpClient(connMgr); + // 清理连接池 + idleThread = new IdleConnectionMonitorThread(connMgr); + idleThread.start(); + } + + return httpClient; + } + + /** + * 关闭HTTP客户端。 + * + * @param httpClient HTTP客户端。 + */ + public synchronized static void shutdown() + { + try + { + if (idleThread != null) + { + idleThread.shutdown(); + idleThread = null; + } + } + catch (Exception e) + { + log.error("httpclient connection manager close", e); + } + + try + { + if (httpClient != null) + { + httpClient.close(); + httpClient = null; + } + } + catch (IOException e) + { + log.error("httpclient close", e); + } + } + + /** + * 请求上游 GET提交 + * + * @param uri + * @throws IOException + */ + public static String getCall(final String uri) throws Exception + { + + return getCall(uri, null, Constants.UTF8); + } + + /** + * 请求上游 GET提交 + * + * @param uri + * @param contentType + * @throws IOException + */ + public static String getCall(final String uri, String contentType) throws Exception + { + + return getCall(uri, contentType, Constants.UTF8); + } + + /** + * 请求上游 GET提交 + * + * @param uri + * @param contentType + * @param charsetName + * @throws IOException + */ + public static String getCall(final String uri, String contentType, String charsetName) throws Exception + { + + final String url = uri; + final HttpGet httpGet = new HttpGet(url); + httpGet.setConfig(requestConfig); + if (!StringUtils.isEmpty(contentType)) + { + httpGet.addHeader("Content-Type", contentType); + } + final CloseableHttpResponse httpRsp = getHttpClient().execute(httpGet); + try + { + if (httpRsp.getStatusLine().getStatusCode() == HttpStatus.SC_OK + || httpRsp.getStatusLine().getStatusCode() == HttpStatus.SC_FORBIDDEN) + { + final HttpEntity entity = httpRsp.getEntity(); + final String rspText = EntityUtils.toString(entity, charsetName); + EntityUtils.consume(entity); + return rspText; + } + else + { + throw new IOException("HTTP StatusCode=" + httpRsp.getStatusLine().getStatusCode()); + } + } + finally + { + try + { + httpRsp.close(); + } + catch (Exception e) + { + log.error("关闭httpRsp异常", e); + } + } + } + + /** + * 请求上游 POST提交 + * + * @param uri + * @param paramsMap + * @throws IOException + */ + public static String postCall(final String uri, Map paramsMap) throws Exception + { + return postCall(uri, null, paramsMap, Constants.UTF8); + } + + /** + * 请求上游 POST提交 + * + * @param uri + * @param contentType + * @param paramsMap + * @throws IOException + */ + public static String postCall(final String uri, String contentType, Map paramsMap) throws Exception + { + + return postCall(uri, contentType, paramsMap, Constants.UTF8); + } + + /** + * 请求上游 POST提交 + * + * @param uri + * @param contentType + * @param paramsMap + * @param charsetName + * @throws IOException + */ + public static String postCall(final String uri, String contentType, Map paramsMap, + String charsetName) throws Exception + { + + final String url = uri; + final HttpPost httpPost = new HttpPost(url); + httpPost.setConfig(requestConfig); + if (!StringUtils.isEmpty(contentType)) + { + httpPost.addHeader("Content-Type", contentType); + } + // 添加参数 + List list = new ArrayList(); + if (paramsMap != null) + { + for (Map.Entry entry : paramsMap.entrySet()) + { + list.add(new BasicNameValuePair(entry.getKey(), (String) entry.getValue())); + } + } + httpPost.setEntity(new UrlEncodedFormEntity(list, charsetName)); + + final CloseableHttpResponse httpRsp = getHttpClient().execute(httpPost); + + try + { + if (httpRsp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) + { + final HttpEntity entity = httpRsp.getEntity(); + final String rspText = EntityUtils.toString(entity, charsetName); + EntityUtils.consume(entity); + return rspText; + } + else + { + throw new IOException("HTTP StatusCode=" + httpRsp.getStatusLine().getStatusCode()); + } + } + finally + { + try + { + httpRsp.close(); + } + catch (Exception e) + { + log.error("关闭httpRsp异常", e); + } + } + } + + /** + * 请求上游 POST提交 + * + * @param uri + * @param param + * @throws IOException + */ + public static String postCall(final String uri, String param) throws Exception + { + + return postCall(uri, null, param, Constants.UTF8); + } + + /** + * 请求上游 POST提交 + * + * @param uri + * @param contentType + * @param param + * @throws IOException + */ + public static String postCall(final String uri, String contentType, String param) throws Exception + { + + return postCall(uri, contentType, param, Constants.UTF8); + } + + /** + * 请求上游 POST提交 + * + * @param uri + * @param contentType + * @param param + * @param charsetName + * @throws IOException + */ + public static String postCall(final String uri, String contentType, String param, String charsetName) + throws Exception + { + + final String url = uri; + final HttpPost httpPost = new HttpPost(url); + httpPost.setConfig(requestConfig); + if (!StringUtils.isEmpty(contentType)) + { + httpPost.addHeader("Content-Type", contentType); + } + else + { + httpPost.addHeader("Content-Type", "application/json"); + } + // 添加参数 + StringEntity paramEntity = new StringEntity(param, charsetName); + httpPost.setEntity(paramEntity); + + final CloseableHttpResponse httpRsp = getHttpClient().execute(httpPost); + + try + { + if (httpRsp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) + { + final HttpEntity entity = httpRsp.getEntity(); + final String rspText = EntityUtils.toString(entity, charsetName); + EntityUtils.consume(entity); + return rspText; + } + else + { + throw new IOException("HTTP StatusCode=" + httpRsp.getStatusLine().getStatusCode()); + } + } + finally + { + try + { + httpRsp.close(); + } + catch (Exception e) + { + log.error("关闭httpRsp异常", e); + } + } + } + + /** + * 判断HTTP异常是否为读取超时。 + * + * @param e 异常对象。 + * @return 如果是读取引起的异常(而非连接),则返回true;否则返回false。 + */ + public static boolean isReadTimeout(final Throwable e) + { + return (!isCausedBy(e, ConnectTimeoutException.class) && isCausedBy(e, SocketTimeoutException.class)); + } + + /** + * 检测异常e被触发的原因是不是因为异常cause。检测被封装的异常。 + * + * @param e 捕获的异常。 + * @param cause 异常触发原因。 + * @return 如果异常e是由cause类异常触发,则返回true;否则返回false。 + */ + public static boolean isCausedBy(final Throwable e, final Class cause) + { + if (cause.isAssignableFrom(e.getClass())) + { + return true; + } + else + { + Throwable t = e.getCause(); + while (t != null && t != e) + { + if (cause.isAssignableFrom(t.getClass())) + { + return true; + } + t = t.getCause(); + } + return false; + } + } +} \ No newline at end of file diff --git a/boyue-common/src/main/java/com/boyue/common/utils/http/IdleConnectionMonitorThread.java b/boyue-common/src/main/java/com/boyue/common/utils/http/IdleConnectionMonitorThread.java new file mode 100644 index 0000000..1b8398e --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/http/IdleConnectionMonitorThread.java @@ -0,0 +1,73 @@ +package com.boyue.common.utils.http; + +import java.util.concurrent.TimeUnit; + +import org.apache.http.conn.HttpClientConnectionManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 连接池清理 + * + * @author boyue + */ +public class IdleConnectionMonitorThread extends Thread +{ + private static final Logger log = LoggerFactory.getLogger(IdleConnectionMonitorThread.class); + + private final HttpClientConnectionManager connMgr; + + private volatile boolean shutdown; + + public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr) + { + super(); + this.shutdown = false; + this.connMgr = connMgr; + } + + @Override + public void run() + { + while (!shutdown) + { + try + { + synchronized (this) + { + // 每5秒检查一次关闭连接 + wait(HttpConf.KEEP_ALIVE_TIMEOUT / 4); + // 关闭失效的连接 + connMgr.closeExpiredConnections(); + // 可选的, 关闭20秒内不活动的连接 + connMgr.closeIdleConnections(HttpConf.KEEP_ALIVE_TIMEOUT, TimeUnit.MILLISECONDS); + // log.debug("关闭失效的连接"); + } + } + catch (Exception e) + { + log.error("关闭失效连接异常", e); + } + } + } + + public void shutdown() + { + shutdown = true; + if (connMgr != null) + { + try + { + connMgr.shutdown(); + } + catch (Exception e) + { + log.error("连接池异常", e); + } + } + synchronized (this) + { + notifyAll(); + } + } +} \ No newline at end of file diff --git a/boyue-common/src/main/java/com/boyue/common/utils/ip/AddressUtils.java b/boyue-common/src/main/java/com/boyue/common/utils/ip/AddressUtils.java new file mode 100644 index 0000000..1769467 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/ip/AddressUtils.java @@ -0,0 +1,56 @@ +package com.boyue.common.utils.ip; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.boyue.common.config.BoYueConfig; +import com.boyue.common.constant.Constants; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.http.HttpUtils; + +/** + * 获取地址类 + * + * @author boyue + */ +public class AddressUtils +{ + private static final Logger log = LoggerFactory.getLogger(AddressUtils.class); + + // IP地址查询 + public static final String IP_URL = "http://whois.pconline.com.cn/ipJson.jsp"; + + // 未知地址 + public static final String UNKNOWN = "XX XX"; + + public static String getRealAddressByIP(String ip) + { + // 内网不查询 + if (IpUtils.internalIp(ip)) + { + return "内网IP"; + } + if (BoYueConfig.isAddressEnabled()) + { + try + { + String rspStr = HttpUtils.sendGet(IP_URL, "ip=" + ip + "&json=true", Constants.GBK); + if (StringUtils.isEmpty(rspStr)) + { + log.error("获取地理位置异常 {}", ip); + return UNKNOWN; + } + JSONObject obj = JSON.parseObject(rspStr); + String region = obj.getString("pro"); + String city = obj.getString("city"); + return String.format("%s %s", region, city); + } + catch (Exception e) + { + log.error("获取地理位置异常 {}", ip); + } + } + return UNKNOWN; + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/ip/IpUtils.java b/boyue-common/src/main/java/com/boyue/common/utils/ip/IpUtils.java new file mode 100644 index 0000000..5057fb4 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/ip/IpUtils.java @@ -0,0 +1,382 @@ +package com.boyue.common.utils.ip; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import jakarta.servlet.http.HttpServletRequest; +import com.boyue.common.utils.ServletUtils; +import com.boyue.common.utils.StringUtils; + +/** + * 获取IP方法 + * + * @author boyue + */ +public class IpUtils +{ + public final static String REGX_0_255 = "(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)"; + // 匹配 ip + public final static String REGX_IP = "((" + REGX_0_255 + "\\.){3}" + REGX_0_255 + ")"; + public final static String REGX_IP_WILDCARD = "(((\\*\\.){3}\\*)|(" + REGX_0_255 + "(\\.\\*){3})|(" + REGX_0_255 + "\\." + REGX_0_255 + ")(\\.\\*){2}" + "|((" + REGX_0_255 + "\\.){3}\\*))"; + // 匹配网段 + public final static String REGX_IP_SEG = "(" + REGX_IP + "\\-" + REGX_IP + ")"; + + /** + * 获取客户端IP + * + * @return IP地址 + */ + public static String getIpAddr() + { + return getIpAddr(ServletUtils.getRequest()); + } + + /** + * 获取客户端IP + * + * @param request 请求对象 + * @return IP地址 + */ + public static String getIpAddr(HttpServletRequest request) + { + if (request == null) + { + return "unknown"; + } + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("X-Forwarded-For"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("X-Real-IP"); + } + + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getRemoteAddr(); + } + + return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip); + } + + /** + * 检查是否为内部IP地址 + * + * @param ip IP地址 + * @return 结果 + */ + public static boolean internalIp(String ip) + { + byte[] addr = textToNumericFormatV4(ip); + return internalIp(addr) || "127.0.0.1".equals(ip); + } + + /** + * 检查是否为内部IP地址 + * + * @param addr byte地址 + * @return 结果 + */ + private static boolean internalIp(byte[] addr) + { + if (StringUtils.isNull(addr) || addr.length < 2) + { + return true; + } + final byte b0 = addr[0]; + final byte b1 = addr[1]; + // 10.x.x.x/8 + final byte SECTION_1 = 0x0A; + // 172.16.x.x/12 + final byte SECTION_2 = (byte) 0xAC; + final byte SECTION_3 = (byte) 0x10; + final byte SECTION_4 = (byte) 0x1F; + // 192.168.x.x/16 + final byte SECTION_5 = (byte) 0xC0; + final byte SECTION_6 = (byte) 0xA8; + switch (b0) + { + case SECTION_1: + return true; + case SECTION_2: + if (b1 >= SECTION_3 && b1 <= SECTION_4) + { + return true; + } + case SECTION_5: + switch (b1) + { + case SECTION_6: + return true; + } + default: + return false; + } + } + + /** + * 将IPv4地址转换成字节 + * + * @param text IPv4地址 + * @return byte 字节 + */ + public static byte[] textToNumericFormatV4(String text) + { + if (text.length() == 0) + { + return null; + } + + byte[] bytes = new byte[4]; + String[] elements = text.split("\\.", -1); + try + { + long l; + int i; + switch (elements.length) + { + case 1: + l = Long.parseLong(elements[0]); + if ((l < 0L) || (l > 4294967295L)) + { + return null; + } + bytes[0] = (byte) (int) (l >> 24 & 0xFF); + bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF); + bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 2: + l = Integer.parseInt(elements[0]); + if ((l < 0L) || (l > 255L)) + { + return null; + } + bytes[0] = (byte) (int) (l & 0xFF); + l = Integer.parseInt(elements[1]); + if ((l < 0L) || (l > 16777215L)) + { + return null; + } + bytes[1] = (byte) (int) (l >> 16 & 0xFF); + bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 3: + for (i = 0; i < 2; ++i) + { + l = Integer.parseInt(elements[i]); + if ((l < 0L) || (l > 255L)) + { + return null; + } + bytes[i] = (byte) (int) (l & 0xFF); + } + l = Integer.parseInt(elements[2]); + if ((l < 0L) || (l > 65535L)) + { + return null; + } + bytes[2] = (byte) (int) (l >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 4: + for (i = 0; i < 4; ++i) + { + l = Integer.parseInt(elements[i]); + if ((l < 0L) || (l > 255L)) + { + return null; + } + bytes[i] = (byte) (int) (l & 0xFF); + } + break; + default: + return null; + } + } + catch (NumberFormatException e) + { + return null; + } + return bytes; + } + + /** + * 获取IP地址 + * + * @return 本地IP地址 + */ + public static String getHostIp() + { + try + { + return InetAddress.getLocalHost().getHostAddress(); + } + catch (UnknownHostException e) + { + } + return "127.0.0.1"; + } + + /** + * 获取主机名 + * + * @return 本地主机名 + */ + public static String getHostName() + { + try + { + return InetAddress.getLocalHost().getHostName(); + } + catch (UnknownHostException e) + { + } + return "未知"; + } + + /** + * 从多级反向代理中获得第一个非unknown IP地址 + * + * @param ip 获得的IP地址 + * @return 第一个非unknown IP地址 + */ + public static String getMultistageReverseProxyIp(String ip) + { + // 多级反向代理检测 + if (ip != null && ip.indexOf(",") > 0) + { + final String[] ips = ip.trim().split(","); + for (String subIp : ips) + { + if (false == isUnknown(subIp)) + { + ip = subIp; + break; + } + } + } + return StringUtils.substring(ip, 0, 255); + } + + /** + * 检测给定字符串是否为未知,多用于检测HTTP请求相关 + * + * @param checkString 被检测的字符串 + * @return 是否未知 + */ + public static boolean isUnknown(String checkString) + { + return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString); + } + + /** + * 是否为IP + */ + public static boolean isIP(String ip) + { + return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP); + } + + /** + * 是否为IP,或 *为间隔的通配符地址 + */ + public static boolean isIpWildCard(String ip) + { + return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP_WILDCARD); + } + + /** + * 检测参数是否在ip通配符里 + */ + public static boolean ipIsInWildCardNoCheck(String ipWildCard, String ip) + { + String[] s1 = ipWildCard.split("\\."); + String[] s2 = ip.split("\\."); + boolean isMatchedSeg = true; + for (int i = 0; i < s1.length && !s1[i].equals("*"); i++) + { + if (!s1[i].equals(s2[i])) + { + isMatchedSeg = false; + break; + } + } + return isMatchedSeg; + } + + /** + * 是否为特定格式如:“10.10.10.1-10.10.10.99”的ip段字符串 + */ + public static boolean isIPSegment(String ipSeg) + { + return StringUtils.isNotBlank(ipSeg) && ipSeg.matches(REGX_IP_SEG); + } + + /** + * 判断ip是否在指定网段中 + */ + public static boolean ipIsInNetNoCheck(String iparea, String ip) + { + int idx = iparea.indexOf('-'); + String[] sips = iparea.substring(0, idx).split("\\."); + String[] sipe = iparea.substring(idx + 1).split("\\."); + String[] sipt = ip.split("\\."); + long ips = 0L, ipe = 0L, ipt = 0L; + for (int i = 0; i < 4; ++i) + { + ips = ips << 8 | Integer.parseInt(sips[i]); + ipe = ipe << 8 | Integer.parseInt(sipe[i]); + ipt = ipt << 8 | Integer.parseInt(sipt[i]); + } + if (ips > ipe) + { + long t = ips; + ips = ipe; + ipe = t; + } + return ips <= ipt && ipt <= ipe; + } + + /** + * 校验ip是否符合过滤串规则 + * + * @param filter 过滤IP列表,支持后缀'*'通配,支持网段如:`10.10.10.1-10.10.10.99` + * @param ip 校验IP地址 + * @return boolean 结果 + */ + public static boolean isMatchedIp(String filter, String ip) + { + if (StringUtils.isEmpty(filter) || StringUtils.isEmpty(ip)) + { + return false; + } + String[] ips = filter.split(";"); + for (String iStr : ips) + { + if (isIP(iStr) && iStr.equals(ip)) + { + return true; + } + else if (isIpWildCard(iStr) && ipIsInWildCardNoCheck(iStr, ip)) + { + return true; + } + else if (isIPSegment(iStr) && ipIsInNetNoCheck(iStr, ip)) + { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/boyue-common/src/main/java/com/boyue/common/utils/poi/ExcelHandlerAdapter.java b/boyue-common/src/main/java/com/boyue/common/utils/poi/ExcelHandlerAdapter.java new file mode 100644 index 0000000..aef9055 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/poi/ExcelHandlerAdapter.java @@ -0,0 +1,19 @@ +package com.boyue.common.utils.poi; + +/** + * Excel数据格式处理适配器 + * + * @author boyue + */ +public interface ExcelHandlerAdapter +{ + /** + * 格式化 + * + * @param value 单元格数据值 + * @param args excel注解args参数组 + * + * @return 处理后的值 + */ + Object format(Object value, String[] args); +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/poi/ExcelUtil.java b/boyue-common/src/main/java/com/boyue/common/utils/poi/ExcelUtil.java new file mode 100644 index 0000000..0ef92c4 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/poi/ExcelUtil.java @@ -0,0 +1,1903 @@ +package com.boyue.common.utils.poi; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.RegExUtils; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.poi.hssf.usermodel.HSSFClientAnchor; +import org.apache.poi.hssf.usermodel.HSSFPicture; +import org.apache.poi.hssf.usermodel.HSSFPictureData; +import org.apache.poi.hssf.usermodel.HSSFShape; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ooxml.POIXMLDocumentPart; +import org.apache.poi.ss.usermodel.BorderStyle; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.ClientAnchor; +import org.apache.poi.ss.usermodel.DataFormat; +import org.apache.poi.ss.usermodel.DataValidation; +import org.apache.poi.ss.usermodel.DataValidationConstraint; +import org.apache.poi.ss.usermodel.DataValidationHelper; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.Drawing; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.Name; +import org.apache.poi.ss.usermodel.PictureData; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.VerticalAlignment; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.util.CellRangeAddressList; +import org.apache.poi.util.IOUtils; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.apache.poi.xssf.usermodel.XSSFClientAnchor; +import org.apache.poi.xssf.usermodel.XSSFDataValidation; +import org.apache.poi.xssf.usermodel.XSSFDrawing; +import org.apache.poi.xssf.usermodel.XSSFPicture; +import org.apache.poi.xssf.usermodel.XSSFShape; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.boyue.common.annotation.Excel; +import com.boyue.common.annotation.Excel.ColumnType; +import com.boyue.common.annotation.Excel.Type; +import com.boyue.common.annotation.Excels; +import com.boyue.common.config.BoYueConfig; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.text.Convert; +import com.boyue.common.exception.UtilException; +import com.boyue.common.utils.DateUtils; +import com.boyue.common.utils.DictUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.file.FileTypeUtils; +import com.boyue.common.utils.file.FileUtils; +import com.boyue.common.utils.file.ImageUtils; +import com.boyue.common.utils.reflect.ReflectUtils; + +import jakarta.servlet.http.HttpServletResponse; + +/** + * Excel相关处理 + * + * @author boyue + */ +public class ExcelUtil +{ + private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class); + + public static final String FORMULA_REGEX_STR = "=|-|\\+|@"; + + public static final String[] FORMULA_STR = { "=", "-", "+", "@" }; + + /** + * 用于dictType属性数据存储,避免重复查缓存 + */ + public Map sysDictMap = new HashMap(); + + /** + * Excel sheet最大行数,默认65536 + */ + public static final int sheetSize = 65536; + + /** + * 工作表名称 + */ + private String sheetName; + + /** + * 导出类型(EXPORT:导出数据;IMPORT:导入模板) + */ + private Type type; + + /** + * 工作薄对象 + */ + private Workbook wb; + + /** + * 工作表对象 + */ + private Sheet sheet; + + /** + * 样式列表 + */ + private Map styles; + + /** + * 导入导出数据列表 + */ + private List list; + + /** + * 注解列表 + */ + private List fields; + + /** + * 当前行号 + */ + private int rownum; + + /** + * 标题 + */ + private String title; + + /** + * 最大高度 + */ + private short maxHeight; + + /** + * 合并后最后行数 + */ + private int subMergedLastRowNum = 0; + + /** + * 合并后开始行数 + */ + private int subMergedFirstRowNum = 1; + + /** + * 对象的子列表方法 + */ + private Method subMethod; + + /** + * 对象的子列表属性 + */ + private List subFields; + + /** + * 统计列表 + */ + private Map statistics = new HashMap(); + + /** + * 数字格式 + */ + private static final DecimalFormat DOUBLE_FORMAT = new DecimalFormat("######0.00"); + + /** + * 实体对象 + */ + public Class clazz; + + /** + * 需要显示列属性 + */ + public String[] includeFields; + + /** + * 需要排除列属性 + */ + public String[] excludeFields; + + public ExcelUtil(Class clazz) + { + this.clazz = clazz; + } + + /** + * 仅在Excel中显示列属性 + * + * @param fields 列属性名 示例[单个"name"/多个"id","name"] + */ + public void showColumn(String... fields) + { + this.includeFields = fields; + } + + /** + * 隐藏Excel中列属性 + * + * @param fields 列属性名 示例[单个"name"/多个"id","name"] + */ + public void hideColumn(String... fields) + { + this.excludeFields = fields; + } + + public void init(List list, String sheetName, String title, Type type) + { + if (list == null) + { + list = new ArrayList(); + } + this.list = list; + this.sheetName = sheetName; + this.type = type; + this.title = title; + createExcelField(); + createWorkbook(); + createTitle(); + createSubHead(); + } + + /** + * 创建excel第一行标题 + */ + public void createTitle() + { + if (StringUtils.isNotEmpty(title)) + { + int titleLastCol = this.fields.size() - 1; + if (isSubList()) + { + titleLastCol = titleLastCol + subFields.size() - 1; + } + Row titleRow = sheet.createRow(rownum == 0 ? rownum++ : 0); + titleRow.setHeightInPoints(30); + Cell titleCell = titleRow.createCell(0); + titleCell.setCellStyle(styles.get("title")); + titleCell.setCellValue(title); + sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), 0, titleLastCol)); + } + } + + /** + * 创建对象的子列表名称 + */ + public void createSubHead() + { + if (isSubList()) + { + Row subRow = sheet.createRow(rownum); + int column = 0; + int subFieldSize = subFields != null ? subFields.size() : 0; + for (Object[] objects : fields) + { + Field field = (Field) objects[0]; + Excel attr = (Excel) objects[1]; + if (Collection.class.isAssignableFrom(field.getType())) + { + Cell cell = subRow.createCell(column); + cell.setCellValue(attr.name()); + cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()))); + if (subFieldSize > 1) + { + CellRangeAddress cellAddress = new CellRangeAddress(rownum, rownum, column, column + subFieldSize - 1); + sheet.addMergedRegion(cellAddress); + } + column += subFieldSize; + } + else + { + Cell cell = subRow.createCell(column++); + cell.setCellValue(attr.name()); + cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()))); + } + } + rownum++; + } + } + + /** + * 对excel表单默认第一个索引名转换成list + * + * @param is 输入流 + * @return 转换后集合 + */ + public List importExcel(InputStream is) + { + List list = null; + try + { + list = importExcel(is, 0); + } + catch (Exception e) + { + log.error("导入Excel异常{}", e.getMessage()); + throw new UtilException(e.getMessage()); + } + finally + { + IOUtils.closeQuietly(is); + } + return list; + } + + /** + * 对excel表单默认第一个索引名转换成list + * + * @param is 输入流 + * @param titleNum 标题占用行数 + * @return 转换后集合 + */ + public List importExcel(InputStream is, int titleNum) throws Exception + { + return importExcel(StringUtils.EMPTY, is, titleNum); + } + + /** + * 对excel表单指定表格索引名转换成list + * + * @param sheetName 表格索引名 + * @param titleNum 标题占用行数 + * @param is 输入流 + * @return 转换后集合 + */ + public List importExcel(String sheetName, InputStream is, int titleNum) throws Exception + { + this.type = Type.IMPORT; + this.wb = WorkbookFactory.create(is); + List list = new ArrayList(); + // 如果指定sheet名,则取指定sheet中的内容 否则默认指向第1个sheet + Sheet sheet = StringUtils.isNotEmpty(sheetName) ? wb.getSheet(sheetName) : wb.getSheetAt(0); + if (sheet == null) + { + throw new IOException("文件sheet不存在"); + } + boolean isXSSFWorkbook = !(wb instanceof HSSFWorkbook); + Map pictures; + if (isXSSFWorkbook) + { + pictures = getSheetPictures07((XSSFSheet) sheet, (XSSFWorkbook) wb); + } + else + { + pictures = getSheetPictures03((HSSFSheet) sheet, (HSSFWorkbook) wb); + } + // 获取最后一个非空行的行下标,比如总行数为n,则返回的为n-1 + int rows = sheet.getLastRowNum(); + if (rows > 0) + { + // 定义一个map用于存放excel列的序号和field. + Map cellMap = new HashMap(); + // 获取表头 + Row heard = sheet.getRow(titleNum); + for (int i = 0; i < heard.getPhysicalNumberOfCells(); i++) + { + Cell cell = heard.getCell(i); + if (StringUtils.isNotNull(cell)) + { + String value = this.getCellValue(heard, i).toString(); + cellMap.put(value, i); + } + else + { + cellMap.put(null, i); + } + } + // 有数据时才处理 得到类的所有field. + List fields = this.getFields(); + Map fieldsMap = new HashMap(); + for (Object[] objects : fields) + { + Excel attr = (Excel) objects[1]; + Integer column = cellMap.get(attr.name()); + if (column != null) + { + fieldsMap.put(column, objects); + } + } + for (int i = titleNum + 1; i <= rows; i++) + { + // 从第2行开始取数据,默认第一行是表头. + Row row = sheet.getRow(i); + // 判断当前行是否是空行 + if (isRowEmpty(row)) + { + continue; + } + T entity = null; + for (Map.Entry entry : fieldsMap.entrySet()) + { + Object val = this.getCellValue(row, entry.getKey()); + + // 如果不存在实例则新建. + entity = (entity == null ? clazz.getDeclaredConstructor().newInstance() : entity); + // 从map中得到对应列的field. + Field field = (Field) entry.getValue()[0]; + Excel attr = (Excel) entry.getValue()[1]; + // 取得类型,并根据对象类型设置值. + Class fieldType = field.getType(); + if (String.class == fieldType) + { + String s = Convert.toStr(val); + if (StringUtils.endsWith(s, ".0")) + { + val = StringUtils.substringBefore(s, ".0"); + } + else + { + String dateFormat = field.getAnnotation(Excel.class).dateFormat(); + if (StringUtils.isNotEmpty(dateFormat)) + { + val = parseDateToStr(dateFormat, val); + } + else + { + val = Convert.toStr(val); + } + } + } + else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) + { + val = Convert.toInt(val); + } + else if ((Long.TYPE == fieldType || Long.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) + { + val = Convert.toLong(val); + } + else if (Double.TYPE == fieldType || Double.class == fieldType) + { + val = Convert.toDouble(val); + } + else if (Float.TYPE == fieldType || Float.class == fieldType) + { + val = Convert.toFloat(val); + } + else if (BigDecimal.class == fieldType) + { + val = Convert.toBigDecimal(val); + } + else if (Date.class == fieldType) + { + if (val instanceof String) + { + val = DateUtils.parseDate(val); + } + else if (val instanceof Double) + { + val = DateUtil.getJavaDate((Double) val); + } + } + else if (Boolean.TYPE == fieldType || Boolean.class == fieldType) + { + val = Convert.toBool(val, false); + } + if (StringUtils.isNotNull(fieldType)) + { + String propertyName = field.getName(); + if (StringUtils.isNotEmpty(attr.targetAttr())) + { + propertyName = field.getName() + "." + attr.targetAttr(); + } + if (StringUtils.isNotEmpty(attr.readConverterExp())) + { + val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator()); + } + else if (StringUtils.isNotEmpty(attr.dictType())) + { + if (!sysDictMap.containsKey(attr.dictType() + val)) + { + String dictValue = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator()); + sysDictMap.put(attr.dictType() + val, dictValue); + } + val = sysDictMap.get(attr.dictType() + val); + } + else if (!attr.handler().equals(ExcelHandlerAdapter.class)) + { + val = dataFormatHandlerAdapter(val, attr, null); + } + else if (ColumnType.IMAGE == attr.cellType() && StringUtils.isNotEmpty(pictures)) + { + PictureData image = pictures.get(row.getRowNum() + "_" + entry.getKey()); + if (image == null) + { + val = ""; + } + else + { + byte[] data = image.getData(); + val = FileUtils.writeImportBytes(data); + } + } + ReflectUtils.invokeSetter(entity, propertyName, val); + } + } + list.add(entity); + } + } + return list; + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @return 结果 + */ + public AjaxResult exportExcel(List list, String sheetName) + { + return exportExcel(list, sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public AjaxResult exportExcel(List list, String sheetName, String title) + { + this.init(list, sheetName, title, Type.EXPORT); + return exportExcel(); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param response 返回数据 + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @return 结果 + */ + public void exportExcel(HttpServletResponse response, List list, String sheetName) + { + exportExcel(response, list, sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param response 返回数据 + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public void exportExcel(HttpServletResponse response, List list, String sheetName, String title) + { + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + this.init(list, sheetName, title, Type.EXPORT); + exportExcel(response); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @return 结果 + */ + public AjaxResult importTemplateExcel(String sheetName) + { + return importTemplateExcel(sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public AjaxResult importTemplateExcel(String sheetName, String title) + { + this.init(null, sheetName, title, Type.IMPORT); + return exportExcel(); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @return 结果 + */ + public void importTemplateExcel(HttpServletResponse response, String sheetName) + { + importTemplateExcel(response, sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public void importTemplateExcel(HttpServletResponse response, String sheetName, String title) + { + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + this.init(null, sheetName, title, Type.IMPORT); + exportExcel(response); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @return 结果 + */ + public void exportExcel(HttpServletResponse response) + { + try + { + writeSheet(); + wb.write(response.getOutputStream()); + } + catch (Exception e) + { + log.error("导出Excel异常{}", e.getMessage()); + } + finally + { + IOUtils.closeQuietly(wb); + } + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @return 结果 + */ + public AjaxResult exportExcel() + { + OutputStream out = null; + try + { + writeSheet(); + String filename = encodingFilename(sheetName); + out = new FileOutputStream(getAbsoluteFile(filename)); + wb.write(out); + return AjaxResult.success(filename); + } + catch (Exception e) + { + log.error("导出Excel异常{}", e.getMessage()); + throw new UtilException("导出Excel失败,请联系网站管理员!"); + } + finally + { + IOUtils.closeQuietly(wb); + IOUtils.closeQuietly(out); + } + } + + /** + * 创建写入数据到Sheet + */ + public void writeSheet() + { + // 取出一共有多少个sheet. + int sheetNo = Math.max(1, (int) Math.ceil(list.size() * 1.0 / sheetSize)); + for (int index = 0; index < sheetNo; index++) + { + createSheet(sheetNo, index); + + // 产生一行 + Row row = sheet.createRow(rownum); + int column = 0; + // 写入各个字段的列头名称 + for (Object[] os : fields) + { + Field field = (Field) os[0]; + Excel excel = (Excel) os[1]; + if (Collection.class.isAssignableFrom(field.getType())) + { + for (Field subField : subFields) + { + Excel subExcel = subField.getAnnotation(Excel.class); + this.createHeadCell(subExcel, row, column++); + } + } + else + { + this.createHeadCell(excel, row, column++); + } + } + if (Type.EXPORT.equals(type)) + { + fillExcelData(index, row); + addStatisticsRow(); + } + } + } + + /** + * 填充excel数据 + * + * @param index 序号 + * @param row 单元格行 + */ + @SuppressWarnings("unchecked") + public void fillExcelData(int index, Row row) + { + int startNo = index * sheetSize; + int endNo = Math.min(startNo + sheetSize, list.size()); + int currentRowNum = rownum + 1; // 从标题行后开始 + + for (int i = startNo; i < endNo; i++) + { + row = sheet.createRow(currentRowNum); + T vo = (T) list.get(i); + int column = 0; + int maxSubListSize = getCurrentMaxSubListSize(vo); + for (Object[] os : fields) + { + Field field = (Field) os[0]; + Excel excel = (Excel) os[1]; + if (Collection.class.isAssignableFrom(field.getType())) + { + try + { + Collection subList = (Collection) getTargetValue(vo, field, excel); + if (subList != null && !subList.isEmpty()) + { + int subIndex = 0; + for (Object subVo : subList) + { + Row subRow = sheet.getRow(currentRowNum + subIndex); + if (subRow == null) + { + subRow = sheet.createRow(currentRowNum + subIndex); + } + + int subColumn = column; + for (Field subField : subFields) + { + Excel subExcel = subField.getAnnotation(Excel.class); + addCell(subExcel, subRow, (T) subVo, subField, subColumn++); + } + subIndex++; + } + column += subFields.size(); + } + } + catch (Exception e) + { + log.error("填充集合数据失败", e); + } + } + else + { + // 创建单元格并设置值 + addCell(excel, row, vo, field, column); + if (maxSubListSize > 1 && excel.needMerge()) + { + sheet.addMergedRegion(new CellRangeAddress(currentRowNum, currentRowNum + maxSubListSize - 1, column, column)); + } + column++; + } + } + currentRowNum += maxSubListSize; + } + } + + /** + * 获取子列表最大数 + */ + private int getCurrentMaxSubListSize(T vo) + { + int maxSubListSize = 1; + for (Object[] os : fields) + { + Field field = (Field) os[0]; + if (Collection.class.isAssignableFrom(field.getType())) + { + try + { + Collection subList = (Collection) getTargetValue(vo, field, (Excel) os[1]); + if (subList != null && !subList.isEmpty()) + { + maxSubListSize = Math.max(maxSubListSize, subList.size()); + } + } + catch (Exception e) + { + log.error("获取集合大小失败", e); + } + } + } + return maxSubListSize; + } + + /** + * 创建表格样式 + * + * @param wb 工作薄对象 + * @return 样式列表 + */ + private Map createStyles(Workbook wb) + { + // 写入各条记录,每条记录对应excel表中的一行 + Map styles = new HashMap(); + CellStyle style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + Font titleFont = wb.createFont(); + titleFont.setFontName("Arial"); + titleFont.setFontHeightInPoints((short) 16); + titleFont.setBold(true); + style.setFont(titleFont); + DataFormat dataFormat = wb.createDataFormat(); + style.setDataFormat(dataFormat.getFormat("@")); + styles.put("title", style); + + style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setBorderRight(BorderStyle.THIN); + style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderLeft(BorderStyle.THIN); + style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderTop(BorderStyle.THIN); + style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderBottom(BorderStyle.THIN); + style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + Font dataFont = wb.createFont(); + dataFont.setFontName("Arial"); + dataFont.setFontHeightInPoints((short) 10); + style.setFont(dataFont); + styles.put("data", style); + + style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + Font totalFont = wb.createFont(); + totalFont.setFontName("Arial"); + totalFont.setFontHeightInPoints((short) 10); + style.setFont(totalFont); + styles.put("total", style); + + styles.putAll(annotationHeaderStyles(wb, styles)); + + styles.putAll(annotationDataStyles(wb)); + + return styles; + } + + /** + * 根据Excel注解创建表格头样式 + * + * @param wb 工作薄对象 + * @return 自定义样式列表 + */ + private Map annotationHeaderStyles(Workbook wb, Map styles) + { + Map headerStyles = new HashMap(); + for (Object[] os : fields) + { + Excel excel = (Excel) os[1]; + String key = StringUtils.format("header_{}_{}", excel.headerColor(), excel.headerBackgroundColor()); + if (!headerStyles.containsKey(key)) + { + CellStyle style = wb.createCellStyle(); + style.cloneStyleFrom(styles.get("data")); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setFillForegroundColor(excel.headerBackgroundColor().index); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + Font headerFont = wb.createFont(); + headerFont.setFontName("Arial"); + headerFont.setFontHeightInPoints((short) 10); + headerFont.setBold(true); + headerFont.setColor(excel.headerColor().index); + style.setFont(headerFont); + // 设置表格头单元格文本形式 + DataFormat dataFormat = wb.createDataFormat(); + style.setDataFormat(dataFormat.getFormat("@")); + headerStyles.put(key, style); + } + } + return headerStyles; + } + + /** + * 根据Excel注解创建表格列样式 + * + * @param wb 工作薄对象 + * @return 自定义样式列表 + */ + private Map annotationDataStyles(Workbook wb) + { + Map styles = new HashMap(); + for (Object[] os : fields) + { + Field field = (Field) os[0]; + Excel excel = (Excel) os[1]; + if (Collection.class.isAssignableFrom(field.getType())) + { + ParameterizedType pt = (ParameterizedType) field.getGenericType(); + Class subClass = (Class) pt.getActualTypeArguments()[0]; + List subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class); + for (Field subField : subFields) + { + Excel subExcel = subField.getAnnotation(Excel.class); + annotationDataStyles(styles, subField, subExcel); + } + } + else + { + annotationDataStyles(styles, field, excel); + } + } + return styles; + } + + /** + * 根据Excel注解创建表格列样式 + * + * @param styles 自定义样式列表 + * @param field 属性列信息 + * @param excel 注解信息 + */ + public void annotationDataStyles(Map styles, Field field, Excel excel) + { + String key = StringUtils.format("data_{}_{}_{}_{}", excel.align(), excel.color(), excel.backgroundColor(), excel.cellType()); + if (!styles.containsKey(key)) + { + CellStyle style = wb.createCellStyle(); + style.setAlignment(excel.align()); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setBorderRight(BorderStyle.THIN); + style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderLeft(BorderStyle.THIN); + style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderTop(BorderStyle.THIN); + style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderBottom(BorderStyle.THIN); + style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + style.setFillForegroundColor(excel.backgroundColor().getIndex()); + Font dataFont = wb.createFont(); + dataFont.setFontName("Arial"); + dataFont.setFontHeightInPoints((short) 10); + dataFont.setColor(excel.color().index); + style.setFont(dataFont); + if (ColumnType.TEXT == excel.cellType()) + { + DataFormat dataFormat = wb.createDataFormat(); + style.setDataFormat(dataFormat.getFormat("@")); + } + styles.put(key, style); + } + } + + /** + * 创建单元格 + */ + public Cell createHeadCell(Excel attr, Row row, int column) + { + // 创建列 + Cell cell = row.createCell(column); + // 写入列信息 + cell.setCellValue(attr.name()); + setDataValidation(attr, row, column); + cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()))); + if (isSubList()) + { + // 填充默认样式,防止合并单元格样式失效 + sheet.setDefaultColumnStyle(column, styles.get(StringUtils.format("data_{}_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor(), attr.cellType()))); + if (attr.needMerge()) + { + sheet.addMergedRegion(new CellRangeAddress(rownum - 1, rownum, column, column)); + } + } + return cell; + } + + /** + * 设置单元格信息 + * + * @param value 单元格值 + * @param attr 注解相关 + * @param cell 单元格信息 + */ + public void setCellVo(Object value, Excel attr, Cell cell) + { + if (ColumnType.STRING == attr.cellType() || ColumnType.TEXT == attr.cellType()) + { + String cellValue = Convert.toStr(value); + // 对于任何以表达式触发字符 =-+@开头的单元格,直接使用tab字符作为前缀,防止CSV注入。 + if (StringUtils.startsWithAny(cellValue, FORMULA_STR)) + { + cellValue = RegExUtils.replaceFirst(cellValue, FORMULA_REGEX_STR, "\t$0"); + } + if (value instanceof Collection && StringUtils.equals("[]", cellValue)) + { + cellValue = StringUtils.EMPTY; + } + cell.setCellValue(StringUtils.isNull(cellValue) ? attr.defaultValue() : cellValue + attr.suffix()); + } + else if (ColumnType.NUMERIC == attr.cellType()) + { + if (StringUtils.isNotNull(value)) + { + cell.setCellValue(StringUtils.contains(Convert.toStr(value), ".") ? Convert.toDouble(value) : Convert.toInt(value)); + } + } + else if (ColumnType.IMAGE == attr.cellType()) + { + ClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), cell.getRow().getRowNum(), (short) (cell.getColumnIndex() + 1), cell.getRow().getRowNum() + 1); + String imagePath = Convert.toStr(value); + if (StringUtils.isNotEmpty(imagePath)) + { + byte[] data = ImageUtils.getImage(imagePath); + getDrawingPatriarch(cell.getSheet()).createPicture(anchor, + cell.getSheet().getWorkbook().addPicture(data, getImageType(data))); + } + } + } + + /** + * 获取画布 + */ + public static Drawing getDrawingPatriarch(Sheet sheet) + { + if (sheet.getDrawingPatriarch() == null) + { + sheet.createDrawingPatriarch(); + } + return sheet.getDrawingPatriarch(); + } + + /** + * 获取图片类型,设置图片插入类型 + */ + public int getImageType(byte[] value) + { + String type = FileTypeUtils.getFileExtendName(value); + if ("JPG".equalsIgnoreCase(type)) + { + return Workbook.PICTURE_TYPE_JPEG; + } + else if ("PNG".equalsIgnoreCase(type)) + { + return Workbook.PICTURE_TYPE_PNG; + } + return Workbook.PICTURE_TYPE_JPEG; + } + + /** + * 创建表格样式 + */ + public void setDataValidation(Excel attr, Row row, int column) + { + if (attr.name().indexOf("注:") >= 0) + { + sheet.setColumnWidth(column, 6000); + } + else + { + // 设置列宽 + sheet.setColumnWidth(column, (int) ((attr.width() + 0.72) * 256)); + } + if (StringUtils.isNotEmpty(attr.prompt()) || attr.combo().length > 0 || attr.comboReadDict()) + { + String[] comboArray = attr.combo(); + if (attr.comboReadDict()) + { + if (!sysDictMap.containsKey("combo_" + attr.dictType())) + { + String labels = DictUtils.getDictLabels(attr.dictType()); + sysDictMap.put("combo_" + attr.dictType(), labels); + } + String val = sysDictMap.get("combo_" + attr.dictType()); + comboArray = StringUtils.split(val, DictUtils.SEPARATOR); + } + if (comboArray.length > 15 || StringUtils.join(comboArray).length() > 255) + { + // 如果下拉数大于15或字符串长度大于255,则使用一个新sheet存储,避免生成的模板下拉值获取不到 + setXSSFValidationWithHidden(sheet, comboArray, attr.prompt(), 1, 100, column, column); + } + else + { + // 提示信息或只能选择不能输入的列内容. + setPromptOrValidation(sheet, comboArray, attr.prompt(), 1, 100, column, column); + } + } + } + + /** + * 添加单元格 + */ + public Cell addCell(Excel attr, Row row, T vo, Field field, int column) + { + Cell cell = null; + try + { + // 设置行高 + row.setHeight(maxHeight); + // 根据Excel中设置情况决定是否导出,有些情况需要保持为空,希望用户填写这一列. + if (attr.isExport()) + { + // 创建cell + cell = row.createCell(column); + if (isSubListValue(vo) && getListCellValue(vo).size() > 1 && attr.needMerge()) + { + if (subMergedLastRowNum >= subMergedFirstRowNum) + { + sheet.addMergedRegion(new CellRangeAddress(subMergedFirstRowNum, subMergedLastRowNum, column, column)); + } + } + cell.setCellStyle(styles.get(StringUtils.format("data_{}_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor(), attr.cellType()))); + + // 用于读取对象中的属性 + Object value = getTargetValue(vo, field, attr); + String dateFormat = attr.dateFormat(); + String readConverterExp = attr.readConverterExp(); + String separator = attr.separator(); + String dictType = attr.dictType(); + if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value)) + { + cell.getCellStyle().setDataFormat(this.wb.getCreationHelper().createDataFormat().getFormat(dateFormat)); + cell.setCellValue(parseDateToStr(dateFormat, value)); + } + else if (StringUtils.isNotEmpty(readConverterExp) && StringUtils.isNotNull(value)) + { + cell.setCellValue(convertByExp(Convert.toStr(value), readConverterExp, separator)); + } + else if (StringUtils.isNotEmpty(dictType) && StringUtils.isNotNull(value)) + { + if (!sysDictMap.containsKey(dictType + value)) + { + String lable = convertDictByExp(Convert.toStr(value), dictType, separator); + sysDictMap.put(dictType + value, lable); + } + cell.setCellValue(sysDictMap.get(dictType + value)); + } + else if (value instanceof BigDecimal && -1 != attr.scale()) + { + cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).doubleValue()); + } + else if (!attr.handler().equals(ExcelHandlerAdapter.class)) + { + cell.setCellValue(dataFormatHandlerAdapter(value, attr, cell)); + } + else + { + // 设置列类型 + setCellVo(value, attr, cell); + } + addStatisticsData(column, Convert.toStr(value), attr); + } + } + catch (Exception e) + { + log.error("导出Excel失败{}", e); + } + return cell; + } + + /** + * 设置 POI XSSFSheet 单元格提示或选择框 + * + * @param sheet 表单 + * @param textlist 下拉框显示的内容 + * @param promptContent 提示内容 + * @param firstRow 开始行 + * @param endRow 结束行 + * @param firstCol 开始列 + * @param endCol 结束列 + */ + public void setPromptOrValidation(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow, + int firstCol, int endCol) + { + DataValidationHelper helper = sheet.getDataValidationHelper(); + DataValidationConstraint constraint = textlist.length > 0 ? helper.createExplicitListConstraint(textlist) : helper.createCustomConstraint("DD1"); + CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol); + DataValidation dataValidation = helper.createValidation(constraint, regions); + if (StringUtils.isNotEmpty(promptContent)) + { + // 如果设置了提示信息则鼠标放上去提示 + dataValidation.createPromptBox("", promptContent); + dataValidation.setShowPromptBox(true); + } + // 处理Excel兼容性问题 + if (dataValidation instanceof XSSFDataValidation) + { + dataValidation.setSuppressDropDownArrow(true); + dataValidation.setShowErrorBox(true); + } + else + { + dataValidation.setSuppressDropDownArrow(false); + } + sheet.addValidationData(dataValidation); + } + + /** + * 设置某些列的值只能输入预制的数据,显示下拉框(兼容超出一定数量的下拉框). + * + * @param sheet 要设置的sheet. + * @param textlist 下拉框显示的内容 + * @param promptContent 提示内容 + * @param firstRow 开始行 + * @param endRow 结束行 + * @param firstCol 开始列 + * @param endCol 结束列 + */ + public void setXSSFValidationWithHidden(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow, int firstCol, int endCol) + { + String hideSheetName = "combo_" + firstCol + "_" + endCol; + Sheet hideSheet = wb.createSheet(hideSheetName); // 用于存储 下拉菜单数据 + for (int i = 0; i < textlist.length; i++) + { + hideSheet.createRow(i).createCell(0).setCellValue(textlist[i]); + } + // 创建名称,可被其他单元格引用 + Name name = wb.createName(); + name.setNameName(hideSheetName + "_data"); + name.setRefersToFormula(hideSheetName + "!$A$1:$A$" + textlist.length); + DataValidationHelper helper = sheet.getDataValidationHelper(); + // 加载下拉列表内容 + DataValidationConstraint constraint = helper.createFormulaListConstraint(hideSheetName + "_data"); + // 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列 + CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol); + // 数据有效性对象 + DataValidation dataValidation = helper.createValidation(constraint, regions); + if (StringUtils.isNotEmpty(promptContent)) + { + // 如果设置了提示信息则鼠标放上去提示 + dataValidation.createPromptBox("", promptContent); + dataValidation.setShowPromptBox(true); + } + // 处理Excel兼容性问题 + if (dataValidation instanceof XSSFDataValidation) + { + dataValidation.setSuppressDropDownArrow(true); + dataValidation.setShowErrorBox(true); + } + else + { + dataValidation.setSuppressDropDownArrow(false); + } + + sheet.addValidationData(dataValidation); + // 设置hiddenSheet隐藏 + wb.setSheetHidden(wb.getSheetIndex(hideSheet), true); + } + + /** + * 解析导出值 0=男,1=女,2=未知 + * + * @param propertyValue 参数值 + * @param converterExp 翻译注解 + * @param separator 分隔符 + * @return 解析后值 + */ + public static String convertByExp(String propertyValue, String converterExp, String separator) + { + StringBuilder propertyString = new StringBuilder(); + String[] convertSource = converterExp.split(","); + for (String item : convertSource) + { + String[] itemArray = item.split("="); + if (StringUtils.containsAny(propertyValue, separator)) + { + for (String value : propertyValue.split(separator)) + { + if (itemArray[0].equals(value)) + { + propertyString.append(itemArray[1] + separator); + break; + } + } + } + else + { + if (itemArray[0].equals(propertyValue)) + { + return itemArray[1]; + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + /** + * 反向解析值 男=0,女=1,未知=2 + * + * @param propertyValue 参数值 + * @param converterExp 翻译注解 + * @param separator 分隔符 + * @return 解析后值 + */ + public static String reverseByExp(String propertyValue, String converterExp, String separator) + { + StringBuilder propertyString = new StringBuilder(); + String[] convertSource = converterExp.split(","); + for (String item : convertSource) + { + String[] itemArray = item.split("="); + if (StringUtils.containsAny(propertyValue, separator)) + { + for (String value : propertyValue.split(separator)) + { + if (itemArray[1].equals(value)) + { + propertyString.append(itemArray[0] + separator); + break; + } + } + } + else + { + if (itemArray[1].equals(propertyValue)) + { + return itemArray[0]; + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + /** + * 解析字典值 + * + * @param dictValue 字典值 + * @param dictType 字典类型 + * @param separator 分隔符 + * @return 字典标签 + */ + public static String convertDictByExp(String dictValue, String dictType, String separator) + { + return DictUtils.getDictLabel(dictType, dictValue, separator); + } + + /** + * 反向解析值字典值 + * + * @param dictLabel 字典标签 + * @param dictType 字典类型 + * @param separator 分隔符 + * @return 字典值 + */ + public static String reverseDictByExp(String dictLabel, String dictType, String separator) + { + return DictUtils.getDictValue(dictType, dictLabel, separator); + } + + /** + * 数据处理器 + * + * @param value 数据值 + * @param excel 数据注解 + * @return + */ + public String dataFormatHandlerAdapter(Object value, Excel excel, Cell cell) + { + try + { + Object instance = excel.handler().getDeclaredConstructor().newInstance(); + Method formatMethod = excel.handler().getMethod("format", new Class[] { Object.class, String[].class, Cell.class, Workbook.class }); + value = formatMethod.invoke(instance, value, excel.args(), cell, this.wb); + } + catch (Exception e) + { + log.error("不能格式化数据 " + excel.handler(), e.getMessage()); + } + return Convert.toStr(value); + } + + /** + * 合计统计信息 + */ + private void addStatisticsData(Integer index, String text, Excel entity) + { + if (entity != null && entity.isStatistics()) + { + Double temp = 0D; + if (!statistics.containsKey(index)) + { + statistics.put(index, temp); + } + try + { + temp = Double.valueOf(text); + } + catch (NumberFormatException e) + { + } + statistics.put(index, statistics.get(index) + temp); + } + } + + /** + * 创建统计行 + */ + public void addStatisticsRow() + { + if (statistics.size() > 0) + { + Row row = sheet.createRow(sheet.getLastRowNum() + 1); + Set keys = statistics.keySet(); + Cell cell = row.createCell(0); + cell.setCellStyle(styles.get("total")); + cell.setCellValue("合计"); + + for (Integer key : keys) + { + cell = row.createCell(key); + cell.setCellStyle(styles.get("total")); + cell.setCellValue(DOUBLE_FORMAT.format(statistics.get(key))); + } + statistics.clear(); + } + } + + /** + * 编码文件名 + */ + public String encodingFilename(String filename) + { + filename = UUID.randomUUID() + "_" + filename + ".xlsx"; + return filename; + } + + /** + * 获取下载路径 + * + * @param filename 文件名称 + */ + public String getAbsoluteFile(String filename) + { + String downloadPath = BoYueConfig.getDownloadPath() + filename; + File desc = new File(downloadPath); + if (!desc.getParentFile().exists()) + { + desc.getParentFile().mkdirs(); + } + return downloadPath; + } + + /** + * 获取bean中的属性值 + * + * @param vo 实体对象 + * @param field 字段 + * @param excel 注解 + * @return 最终的属性值 + * @throws Exception + */ + private Object getTargetValue(T vo, Field field, Excel excel) throws Exception + { + field.setAccessible(true); + Object o = field.get(vo); + if (StringUtils.isNotEmpty(excel.targetAttr())) + { + String target = excel.targetAttr(); + if (target.contains(".")) + { + String[] targets = target.split("[.]"); + for (String name : targets) + { + o = getValue(o, name); + } + } + else + { + o = getValue(o, target); + } + } + return o; + } + + /** + * 以类的属性的get方法方法形式获取值 + * + * @param o + * @param name + * @return value + * @throws Exception + */ + private Object getValue(Object o, String name) throws Exception + { + if (StringUtils.isNotNull(o) && StringUtils.isNotEmpty(name)) + { + Class clazz = o.getClass(); + Field field = clazz.getDeclaredField(name); + field.setAccessible(true); + o = field.get(o); + } + return o; + } + + /** + * 得到所有定义字段 + */ + private void createExcelField() + { + this.fields = getFields(); + this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList()); + this.maxHeight = getRowHeight(); + } + + /** + * 获取字段注解信息 + */ + public List getFields() + { + List fields = new ArrayList(); + List tempFields = new ArrayList<>(); + tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields())); + tempFields.addAll(Arrays.asList(clazz.getDeclaredFields())); + if (StringUtils.isNotEmpty(includeFields)) + { + for (Field field : tempFields) + { + if (ArrayUtils.contains(this.includeFields, field.getName()) || field.isAnnotationPresent(Excels.class)) + { + addField(fields, field); + } + } + } + else if (StringUtils.isNotEmpty(excludeFields)) + { + for (Field field : tempFields) + { + if (!ArrayUtils.contains(this.excludeFields, field.getName())) + { + addField(fields, field); + } + } + } + else + { + for (Field field : tempFields) + { + addField(fields, field); + } + } + return fields; + } + + /** + * 添加字段信息 + */ + public void addField(List fields, Field field) + { + // 单注解 + if (field.isAnnotationPresent(Excel.class)) + { + Excel attr = field.getAnnotation(Excel.class); + if (attr != null && (attr.type() == Type.ALL || attr.type() == type)) + { + fields.add(new Object[] { field, attr }); + } + if (Collection.class.isAssignableFrom(field.getType())) + { + subMethod = getSubMethod(field.getName(), clazz); + ParameterizedType pt = (ParameterizedType) field.getGenericType(); + Class subClass = (Class) pt.getActualTypeArguments()[0]; + this.subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class); + } + } + + // 多注解 + if (field.isAnnotationPresent(Excels.class)) + { + Excels attrs = field.getAnnotation(Excels.class); + Excel[] excels = attrs.value(); + for (Excel attr : excels) + { + if (StringUtils.isNotEmpty(includeFields)) + { + if (ArrayUtils.contains(this.includeFields, field.getName() + "." + attr.targetAttr()) + && (attr != null && (attr.type() == Type.ALL || attr.type() == type))) + { + fields.add(new Object[] { field, attr }); + } + } + else + { + if (!ArrayUtils.contains(this.excludeFields, field.getName() + "." + attr.targetAttr()) + && (attr != null && (attr.type() == Type.ALL || attr.type() == type))) + { + fields.add(new Object[] { field, attr }); + } + } + } + } + } + + /** + * 根据注解获取最大行高 + */ + public short getRowHeight() + { + double maxHeight = 0; + for (Object[] os : this.fields) + { + Excel excel = (Excel) os[1]; + maxHeight = Math.max(maxHeight, excel.height()); + } + return (short) (maxHeight * 20); + } + + /** + * 创建一个工作簿 + */ + public void createWorkbook() + { + this.wb = new SXSSFWorkbook(500); + this.sheet = wb.createSheet(); + wb.setSheetName(0, sheetName); + this.styles = createStyles(wb); + } + + /** + * 创建工作表 + * + * @param sheetNo sheet数量 + * @param index 序号 + */ + public void createSheet(int sheetNo, int index) + { + // 设置工作表的名称. + if (sheetNo > 1 && index > 0) + { + this.sheet = wb.createSheet(); + this.createTitle(); + wb.setSheetName(index, sheetName + index); + } + } + + /** + * 获取单元格值 + * + * @param row 获取的行 + * @param column 获取单元格列号 + * @return 单元格值 + */ + public Object getCellValue(Row row, int column) + { + if (row == null) + { + return row; + } + Object val = ""; + try + { + Cell cell = row.getCell(column); + if (StringUtils.isNotNull(cell)) + { + if (cell.getCellType() == CellType.NUMERIC || cell.getCellType() == CellType.FORMULA) + { + val = cell.getNumericCellValue(); + if (DateUtil.isCellDateFormatted(cell)) + { + val = DateUtil.getJavaDate((Double) val); // POI Excel 日期格式转换 + } + else + { + if ((Double) val % 1 != 0) + { + val = new BigDecimal(val.toString()); + } + else + { + val = new DecimalFormat("0").format(val); + } + } + } + else if (cell.getCellType() == CellType.STRING) + { + val = cell.getStringCellValue(); + } + else if (cell.getCellType() == CellType.BOOLEAN) + { + val = cell.getBooleanCellValue(); + } + else if (cell.getCellType() == CellType.ERROR) + { + val = cell.getErrorCellValue(); + } + + } + } + catch (Exception e) + { + return val; + } + return val; + } + + /** + * 判断是否是空行 + * + * @param row 判断的行 + * @return + */ + private boolean isRowEmpty(Row row) + { + if (row == null) + { + return true; + } + for (int i = row.getFirstCellNum(); i < row.getLastCellNum(); i++) + { + Cell cell = row.getCell(i); + if (cell != null && cell.getCellType() != CellType.BLANK) + { + return false; + } + } + return true; + } + + /** + * 获取Excel2003图片 + * + * @param sheet 当前sheet对象 + * @param workbook 工作簿对象 + * @return Map key:图片单元格索引(1_1)String,value:图片流PictureData + */ + public static Map getSheetPictures03(HSSFSheet sheet, HSSFWorkbook workbook) + { + Map sheetIndexPicMap = new HashMap(); + List pictures = workbook.getAllPictures(); + if (!pictures.isEmpty()) + { + for (HSSFShape shape : sheet.getDrawingPatriarch().getChildren()) + { + HSSFClientAnchor anchor = (HSSFClientAnchor) shape.getAnchor(); + if (shape instanceof HSSFPicture) + { + HSSFPicture pic = (HSSFPicture) shape; + int pictureIndex = pic.getPictureIndex() - 1; + HSSFPictureData picData = pictures.get(pictureIndex); + String picIndex = anchor.getRow1() + "_" + anchor.getCol1(); + sheetIndexPicMap.put(picIndex, picData); + } + } + return sheetIndexPicMap; + } + else + { + return sheetIndexPicMap; + } + } + + /** + * 获取Excel2007图片 + * + * @param sheet 当前sheet对象 + * @param workbook 工作簿对象 + * @return Map key:图片单元格索引(1_1)String,value:图片流PictureData + */ + public static Map getSheetPictures07(XSSFSheet sheet, XSSFWorkbook workbook) + { + Map sheetIndexPicMap = new HashMap(); + for (POIXMLDocumentPart dr : sheet.getRelations()) + { + if (dr instanceof XSSFDrawing) + { + XSSFDrawing drawing = (XSSFDrawing) dr; + List shapes = drawing.getShapes(); + for (XSSFShape shape : shapes) + { + if (shape instanceof XSSFPicture) + { + XSSFPicture pic = (XSSFPicture) shape; + XSSFClientAnchor anchor = pic.getPreferredSize(); + CTMarker ctMarker = anchor.getFrom(); + String picIndex = ctMarker.getRow() + "_" + ctMarker.getCol(); + sheetIndexPicMap.put(picIndex, pic.getPictureData()); + } + } + } + } + return sheetIndexPicMap; + } + + /** + * 格式化不同类型的日期对象 + * + * @param dateFormat 日期格式 + * @param val 被格式化的日期对象 + * @return 格式化后的日期字符 + */ + public String parseDateToStr(String dateFormat, Object val) + { + if (val == null) + { + return ""; + } + String str; + if (val instanceof Date) + { + str = DateUtils.parseDateToStr(dateFormat, (Date) val); + } + else if (val instanceof LocalDateTime) + { + str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDateTime) val)); + } + else if (val instanceof LocalDate) + { + str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDate) val)); + } + else + { + str = val.toString(); + } + return str; + } + + /** + * 是否有对象的子列表 + */ + public boolean isSubList() + { + return StringUtils.isNotNull(subFields) && subFields.size() > 0; + } + + /** + * 是否有对象的子列表,集合不为空 + */ + public boolean isSubListValue(T vo) + { + return StringUtils.isNotNull(subFields) && subFields.size() > 0 && StringUtils.isNotNull(getListCellValue(vo)) && getListCellValue(vo).size() > 0; + } + + /** + * 获取集合的值 + */ + public Collection getListCellValue(Object obj) + { + Object value; + try + { + value = subMethod.invoke(obj, new Object[] {}); + } + catch (Exception e) + { + return new ArrayList(); + } + return (Collection) value; + } + + /** + * 获取对象的子列表方法 + * + * @param name 名称 + * @param pojoClass 类对象 + * @return 子列表方法 + */ + public Method getSubMethod(String name, Class pojoClass) + { + StringBuffer getMethodName = new StringBuffer("get"); + getMethodName.append(name.substring(0, 1).toUpperCase()); + getMethodName.append(name.substring(1)); + Method method = null; + try + { + method = pojoClass.getMethod(getMethodName.toString(), new Class[] {}); + } + catch (Exception e) + { + log.error("获取对象异常{}", e.getMessage()); + } + return method; + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/reflect/ReflectUtils.java b/boyue-common/src/main/java/com/boyue/common/utils/reflect/ReflectUtils.java new file mode 100644 index 0000000..1a067de --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/reflect/ReflectUtils.java @@ -0,0 +1,410 @@ +package com.boyue.common.utils.reflect; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Date; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.apache.poi.ss.usermodel.DateUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.boyue.common.core.text.Convert; +import com.boyue.common.utils.DateUtils; + +/** + * 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数. + * + * @author boyue + */ +@SuppressWarnings("rawtypes") +public class ReflectUtils +{ + private static final String SETTER_PREFIX = "set"; + + private static final String GETTER_PREFIX = "get"; + + private static final String CGLIB_CLASS_SEPARATOR = "$$"; + + private static Logger logger = LoggerFactory.getLogger(ReflectUtils.class); + + /** + * 调用Getter方法. + * 支持多级,如:对象名.对象名.方法 + */ + @SuppressWarnings("unchecked") + public static E invokeGetter(Object obj, String propertyName) + { + Object object = obj; + for (String name : StringUtils.split(propertyName, ".")) + { + String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name); + object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {}); + } + return (E) object; + } + + /** + * 调用Setter方法, 仅匹配方法名。 + * 支持多级,如:对象名.对象名.方法 + */ + public static void invokeSetter(Object obj, String propertyName, E value) + { + Object object = obj; + String[] names = StringUtils.split(propertyName, "."); + for (int i = 0; i < names.length; i++) + { + if (i < names.length - 1) + { + String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]); + object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {}); + } + else + { + String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]); + invokeMethodByName(object, setterMethodName, new Object[] { value }); + } + } + } + + /** + * 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数. + */ + @SuppressWarnings("unchecked") + public static E getFieldValue(final Object obj, final String fieldName) + { + Field field = getAccessibleField(obj, fieldName); + if (field == null) + { + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); + return null; + } + E result = null; + try + { + result = (E) field.get(obj); + } + catch (IllegalAccessException e) + { + logger.error("不可能抛出的异常{}", e.getMessage()); + } + return result; + } + + /** + * 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数. + */ + public static void setFieldValue(final Object obj, final String fieldName, final E value) + { + Field field = getAccessibleField(obj, fieldName); + if (field == null) + { + // throw new IllegalArgumentException("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); + return; + } + try + { + field.set(obj, value); + } + catch (IllegalAccessException e) + { + logger.error("不可能抛出的异常: {}", e.getMessage()); + } + } + + /** + * 直接调用对象方法, 无视private/protected修饰符. + * 用于一次性调用的情况,否则应使用getAccessibleMethod()函数获得Method后反复调用. + * 同时匹配方法名+参数类型, + */ + @SuppressWarnings("unchecked") + public static E invokeMethod(final Object obj, final String methodName, final Class[] parameterTypes, + final Object[] args) + { + if (obj == null || methodName == null) + { + return null; + } + Method method = getAccessibleMethod(obj, methodName, parameterTypes); + if (method == null) + { + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 "); + return null; + } + try + { + return (E) method.invoke(obj, args); + } + catch (Exception e) + { + String msg = "method: " + method + ", obj: " + obj + ", args: " + args + ""; + throw convertReflectionExceptionToUnchecked(msg, e); + } + } + + /** + * 直接调用对象方法, 无视private/protected修饰符, + * 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用. + * 只匹配函数名,如果有多个同名函数调用第一个。 + */ + @SuppressWarnings("unchecked") + public static E invokeMethodByName(final Object obj, final String methodName, final Object[] args) + { + Method method = getAccessibleMethodByName(obj, methodName, args.length); + if (method == null) + { + // 如果为空不报错,直接返回空。 + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 "); + return null; + } + try + { + // 类型转换(将参数数据类型转换为目标方法参数类型) + Class[] cs = method.getParameterTypes(); + for (int i = 0; i < cs.length; i++) + { + if (args[i] != null && !args[i].getClass().equals(cs[i])) + { + if (cs[i] == String.class) + { + args[i] = Convert.toStr(args[i]); + if (StringUtils.endsWith((String) args[i], ".0")) + { + args[i] = StringUtils.substringBefore((String) args[i], ".0"); + } + } + else if (cs[i] == Integer.class) + { + args[i] = Convert.toInt(args[i]); + } + else if (cs[i] == Long.class) + { + args[i] = Convert.toLong(args[i]); + } + else if (cs[i] == Double.class) + { + args[i] = Convert.toDouble(args[i]); + } + else if (cs[i] == Float.class) + { + args[i] = Convert.toFloat(args[i]); + } + else if (cs[i] == Date.class) + { + if (args[i] instanceof String) + { + args[i] = DateUtils.parseDate(args[i]); + } + else + { + args[i] = DateUtil.getJavaDate((Double) args[i]); + } + } + else if (cs[i] == boolean.class || cs[i] == Boolean.class) + { + args[i] = Convert.toBool(args[i]); + } + } + } + return (E) method.invoke(obj, args); + } + catch (Exception e) + { + String msg = "method: " + method + ", obj: " + obj + ", args: " + args + ""; + throw convertReflectionExceptionToUnchecked(msg, e); + } + } + + /** + * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问. + * 如向上转型到Object仍无法找到, 返回null. + */ + public static Field getAccessibleField(final Object obj, final String fieldName) + { + // 为空不报错。直接返回 null + if (obj == null) + { + return null; + } + Validate.notBlank(fieldName, "fieldName can't be blank"); + for (Class superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) + { + try + { + Field field = superClass.getDeclaredField(fieldName); + makeAccessible(field); + return field; + } + catch (NoSuchFieldException e) + { + continue; + } + } + return null; + } + + /** + * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. + * 如向上转型到Object仍无法找到, 返回null. + * 匹配函数名+参数类型。 + * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) + */ + public static Method getAccessibleMethod(final Object obj, final String methodName, + final Class... parameterTypes) + { + // 为空不报错。直接返回 null + if (obj == null) + { + return null; + } + Validate.notBlank(methodName, "methodName can't be blank"); + for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) + { + try + { + Method method = searchType.getDeclaredMethod(methodName, parameterTypes); + makeAccessible(method); + return method; + } + catch (NoSuchMethodException e) + { + continue; + } + } + return null; + } + + /** + * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. + * 如向上转型到Object仍无法找到, 返回null. + * 只匹配函数名。 + * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) + */ + public static Method getAccessibleMethodByName(final Object obj, final String methodName, int argsNum) + { + // 为空不报错。直接返回 null + if (obj == null) + { + return null; + } + Validate.notBlank(methodName, "methodName can't be blank"); + for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) + { + Method[] methods = searchType.getDeclaredMethods(); + for (Method method : methods) + { + if (method.getName().equals(methodName) && method.getParameterTypes().length == argsNum) + { + makeAccessible(method); + return method; + } + } + } + return null; + } + + /** + * 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 + */ + public static void makeAccessible(Method method) + { + if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) + && !method.canAccess(null)) + { + method.setAccessible(true); + } + } + + /** + * 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 + */ + public static void makeAccessible(Field field) + { + if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) + || Modifier.isFinal(field.getModifiers())) && !field.canAccess(null)) + { + field.setAccessible(true); + } + } + + /** + * 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处 + * 如无法找到, 返回Object.class. + */ + @SuppressWarnings("unchecked") + public static Class getClassGenricType(final Class clazz) + { + return getClassGenricType(clazz, 0); + } + + /** + * 通过反射, 获得Class定义中声明的父类的泛型参数的类型. + * 如无法找到, 返回Object.class. + */ + public static Class getClassGenricType(final Class clazz, final int index) + { + Type genType = clazz.getGenericSuperclass(); + + if (!(genType instanceof ParameterizedType)) + { + logger.debug(clazz.getSimpleName() + "'s superclass not ParameterizedType"); + return Object.class; + } + + Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); + + if (index >= params.length || index < 0) + { + logger.debug("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: " + + params.length); + return Object.class; + } + if (!(params[index] instanceof Class)) + { + logger.debug(clazz.getSimpleName() + " not set the actual class on superclass generic parameter"); + return Object.class; + } + + return (Class) params[index]; + } + + public static Class getUserClass(Object instance) + { + if (instance == null) + { + throw new RuntimeException("Instance must not be null"); + } + Class clazz = instance.getClass(); + if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) + { + Class superClass = clazz.getSuperclass(); + if (superClass != null && !Object.class.equals(superClass)) + { + return superClass; + } + } + return clazz; + + } + + /** + * 将反射时的checked exception转换为unchecked exception. + */ + public static RuntimeException convertReflectionExceptionToUnchecked(String msg, Exception e) + { + if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException + || e instanceof NoSuchMethodException) + { + return new IllegalArgumentException(msg, e); + } + else if (e instanceof InvocationTargetException) + { + return new RuntimeException(msg, ((InvocationTargetException) e).getTargetException()); + } + return new RuntimeException(msg, e); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/sign/Base64.java b/boyue-common/src/main/java/com/boyue/common/utils/sign/Base64.java new file mode 100644 index 0000000..6821ec2 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/sign/Base64.java @@ -0,0 +1,291 @@ +package com.boyue.common.utils.sign; + +/** + * Base64工具类 + * + * @author boyue + */ +public final class Base64 +{ + static private final int BASELENGTH = 128; + static private final int LOOKUPLENGTH = 64; + static private final int TWENTYFOURBITGROUP = 24; + static private final int EIGHTBIT = 8; + static private final int SIXTEENBIT = 16; + static private final int FOURBYTE = 4; + static private final int SIGN = -128; + static private final char PAD = '='; + static final private byte[] base64Alphabet = new byte[BASELENGTH]; + static final private char[] lookUpBase64Alphabet = new char[LOOKUPLENGTH]; + + static + { + for (int i = 0; i < BASELENGTH; ++i) + { + base64Alphabet[i] = -1; + } + for (int i = 'Z'; i >= 'A'; i--) + { + base64Alphabet[i] = (byte) (i - 'A'); + } + for (int i = 'z'; i >= 'a'; i--) + { + base64Alphabet[i] = (byte) (i - 'a' + 26); + } + + for (int i = '9'; i >= '0'; i--) + { + base64Alphabet[i] = (byte) (i - '0' + 52); + } + + base64Alphabet['+'] = 62; + base64Alphabet['/'] = 63; + + for (int i = 0; i <= 25; i++) + { + lookUpBase64Alphabet[i] = (char) ('A' + i); + } + + for (int i = 26, j = 0; i <= 51; i++, j++) + { + lookUpBase64Alphabet[i] = (char) ('a' + j); + } + + for (int i = 52, j = 0; i <= 61; i++, j++) + { + lookUpBase64Alphabet[i] = (char) ('0' + j); + } + lookUpBase64Alphabet[62] = (char) '+'; + lookUpBase64Alphabet[63] = (char) '/'; + } + + private static boolean isWhiteSpace(char octect) + { + return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9); + } + + private static boolean isPad(char octect) + { + return (octect == PAD); + } + + private static boolean isData(char octect) + { + return (octect < BASELENGTH && base64Alphabet[octect] != -1); + } + + /** + * Encodes hex octects into Base64 + * + * @param binaryData Array containing binaryData + * @return Encoded Base64 array + */ + public static String encode(byte[] binaryData) + { + if (binaryData == null) + { + return null; + } + + int lengthDataBits = binaryData.length * EIGHTBIT; + if (lengthDataBits == 0) + { + return ""; + } + + int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP; + int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP; + int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1 : numberTriplets; + char encodedData[] = null; + + encodedData = new char[numberQuartet * 4]; + + byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0; + + int encodedIndex = 0; + int dataIndex = 0; + + for (int i = 0; i < numberTriplets; i++) + { + b1 = binaryData[dataIndex++]; + b2 = binaryData[dataIndex++]; + b3 = binaryData[dataIndex++]; + + l = (byte) (b2 & 0x0f); + k = (byte) (b1 & 0x03); + + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); + byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc); + + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f]; + } + + // form integral number of 6-bit groups + if (fewerThan24bits == EIGHTBIT) + { + b1 = binaryData[dataIndex]; + k = (byte) (b1 & 0x03); + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[k << 4]; + encodedData[encodedIndex++] = PAD; + encodedData[encodedIndex++] = PAD; + } + else if (fewerThan24bits == SIXTEENBIT) + { + b1 = binaryData[dataIndex]; + b2 = binaryData[dataIndex + 1]; + l = (byte) (b2 & 0x0f); + k = (byte) (b1 & 0x03); + + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); + + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[l << 2]; + encodedData[encodedIndex++] = PAD; + } + return new String(encodedData); + } + + /** + * Decodes Base64 data into octects + * + * @param encoded string containing Base64 data + * @return Array containind decoded data. + */ + public static byte[] decode(String encoded) + { + if (encoded == null) + { + return null; + } + + char[] base64Data = encoded.toCharArray(); + // remove white spaces + int len = removeWhiteSpace(base64Data); + + if (len % FOURBYTE != 0) + { + return null;// should be divisible by four + } + + int numberQuadruple = (len / FOURBYTE); + + if (numberQuadruple == 0) + { + return new byte[0]; + } + + byte decodedData[] = null; + byte b1 = 0, b2 = 0, b3 = 0, b4 = 0; + char d1 = 0, d2 = 0, d3 = 0, d4 = 0; + + int i = 0; + int encodedIndex = 0; + int dataIndex = 0; + decodedData = new byte[(numberQuadruple) * 3]; + + for (; i < numberQuadruple - 1; i++) + { + + if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++])) + || !isData((d3 = base64Data[dataIndex++])) || !isData((d4 = base64Data[dataIndex++]))) + { + return null; + } // if found "no data" just return null + + b1 = base64Alphabet[d1]; + b2 = base64Alphabet[d2]; + b3 = base64Alphabet[d3]; + b4 = base64Alphabet[d4]; + + decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); + } + + if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))) + { + return null;// if found "no data" just return null + } + + b1 = base64Alphabet[d1]; + b2 = base64Alphabet[d2]; + + d3 = base64Data[dataIndex++]; + d4 = base64Data[dataIndex++]; + if (!isData((d3)) || !isData((d4))) + {// Check if they are PAD characters + if (isPad(d3) && isPad(d4)) + { + if ((b2 & 0xf) != 0)// last 4 bits should be zero + { + return null; + } + byte[] tmp = new byte[i * 3 + 1]; + System.arraycopy(decodedData, 0, tmp, 0, i * 3); + tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); + return tmp; + } + else if (!isPad(d3) && isPad(d4)) + { + b3 = base64Alphabet[d3]; + if ((b3 & 0x3) != 0)// last 2 bits should be zero + { + return null; + } + byte[] tmp = new byte[i * 3 + 2]; + System.arraycopy(decodedData, 0, tmp, 0, i * 3); + tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + return tmp; + } + else + { + return null; + } + } + else + { // No PAD e.g 3cQl + b3 = base64Alphabet[d3]; + b4 = base64Alphabet[d4]; + decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); + + } + return decodedData; + } + + /** + * remove WhiteSpace from MIME containing encoded Base64 data. + * + * @param data the byte array of base64 data (with WS) + * @return the new length + */ + private static int removeWhiteSpace(char[] data) + { + if (data == null) + { + return 0; + } + + // count characters that's not whitespace + int newSize = 0; + int len = data.length; + for (int i = 0; i < len; i++) + { + if (!isWhiteSpace(data[i])) + { + data[newSize++] = data[i]; + } + } + return newSize; + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/sign/Md5Utils.java b/boyue-common/src/main/java/com/boyue/common/utils/sign/Md5Utils.java new file mode 100644 index 0000000..2e87e38 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/sign/Md5Utils.java @@ -0,0 +1,166 @@ +package com.boyue.common.utils.sign; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import org.apache.commons.codec.digest.DigestUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.multipart.MultipartFile; + +/** + * Md5加密方法 + * + * @author boyue + */ +public class Md5Utils { + private static final Logger log = LoggerFactory.getLogger(Md5Utils.class); + + private static final ThreadLocal md5DigestThreadLocal = ThreadLocal.withInitial(() -> { + try { + return MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("MD5 algorithm not found", e); + } + }); + + private static byte[] md5(String s) { + MessageDigest algorithm; + try { + algorithm = MessageDigest.getInstance("MD5"); + algorithm.reset(); + algorithm.update(s.getBytes("UTF-8")); + byte[] messageDigest = algorithm.digest(); + return messageDigest; + } catch (Exception e) { + log.error("MD5 Error...", e); + } + return null; + } + + private static final String toHex(byte hash[]) { + if (hash == null) { + return null; + } + StringBuffer buf = new StringBuffer(hash.length * 2); + int i; + + for (i = 0; i < hash.length; i++) { + if ((hash[i] & 0xff) < 0x10) { + buf.append("0"); + } + buf.append(Long.toString(hash[i] & 0xff, 16)); + } + return buf.toString(); + } + + public static String hash(String s) { + try { + return new String(toHex(md5(s)).getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); + } catch (Exception e) { + log.error("not supported charset...{}", e); + return s; + } + } + + public static String encryptMd5(String string) throws UnsupportedEncodingException { + return encryptMd5(string, "UTF-8"); + } + + public static String encryptMd5(String string, String charSet) throws UnsupportedEncodingException { + return DigestUtils.md5Hex(string.getBytes(charSet)); + } + + /** + * 计算文件的md5 + * @param file 文件,可以是 MultipartFile 或 File + * @return + */ + public static String getMd5(Object file) { + try (InputStream inputStream = getInputStream(file)) { + long fileSize = getFileSize(file); + // 10MB作为分界点 + if (fileSize < 10 * 1024 * 1024) { + return getMd5ForSmallFile(inputStream); + } else { + return getMd5ForLargeFile(inputStream); + } + } catch (Exception e) { + log.error(e.getMessage()); + } + return null; + } + + private static InputStream getInputStream(Object file) throws IOException { + if (file instanceof MultipartFile) { + return ((MultipartFile) file).getInputStream(); + } else if (file instanceof File) { + return new FileInputStream((File) file); + } + throw new IllegalArgumentException("Unsupported file type"); + } + + private static long getFileSize(Object file) throws IOException { + if (file instanceof MultipartFile) { + return ((MultipartFile) file).getSize(); + } else if (file instanceof File) { + return ((File) file).length(); + } + throw new IllegalArgumentException("Unsupported file type"); + } + + /** + * 计算小文件的md5 + * + * @param inputStream 文件输入流 + * @return + */ + private static String getMd5ForSmallFile(InputStream inputStream) { + try { + byte[] uploadBytes = inputStream.readAllBytes(); + MessageDigest md5 = md5DigestThreadLocal.get(); + byte[] digest = md5.digest(uploadBytes); + String md5Hex = new BigInteger(1, digest).toString(16); + while (md5Hex.length() < 32) { + md5Hex = "0" + md5Hex; + } + return md5Hex; + } catch (Exception e) { + log.error(e.getMessage()); + } + return null; + } + + /** + * 计算大文件的md5 + * + * @param inputStream 文件输入流 + * @return + */ + private static String getMd5ForLargeFile(InputStream inputStream) { + try (InputStream is = inputStream) { + MessageDigest md = md5DigestThreadLocal.get(); + byte[] buffer = new byte[81920]; + int read; + while ((read = is.read(buffer)) != -1) { + md.update(buffer, 0, read); + } + byte[] digest = md.digest(); + StringBuilder sb = new StringBuilder(); + for (byte b : digest) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } catch (Exception e) { + log.error(e.getMessage()); + } + return null; + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/spring/SpringUtils.java b/boyue-common/src/main/java/com/boyue/common/utils/spring/SpringUtils.java new file mode 100644 index 0000000..d718e6c --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/spring/SpringUtils.java @@ -0,0 +1,158 @@ +package com.boyue.common.utils.spring; + +import org.springframework.aop.framework.AopContext; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; +import com.boyue.common.utils.StringUtils; + +/** + * spring工具类 方便在非spring管理环境中获取bean + * + * @author boyue + */ +@Component +public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware +{ + /** Spring应用上下文环境 */ + private static ConfigurableListableBeanFactory beanFactory; + + private static ApplicationContext applicationContext; + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException + { + SpringUtils.beanFactory = beanFactory; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + SpringUtils.applicationContext = applicationContext; + } + + /** + * 获取对象 + * + * @param name + * @return Object 一个以所给名字注册的bean的实例 + * @throws org.springframework.beans.BeansException + * + */ + @SuppressWarnings("unchecked") + public static T getBean(String name) throws BeansException + { + return (T) beanFactory.getBean(name); + } + + /** + * 获取类型为requiredType的对象 + * + * @param clz + * @return + * @throws org.springframework.beans.BeansException + * + */ + public static T getBean(Class clz) throws BeansException + { + T result = (T) beanFactory.getBean(clz); + return result; + } + + /** + * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true + * + * @param name + * @return boolean + */ + public static boolean containsBean(String name) + { + return beanFactory.containsBean(name); + } + + /** + * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) + * + * @param name + * @return boolean + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * + */ + public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.isSingleton(name); + } + + /** + * @param name + * @return Class 注册对象的类型 + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * + */ + public static Class getType(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.getType(name); + } + + /** + * 如果给定的bean名字在bean定义中有别名,则返回这些别名 + * + * @param name + * @return + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * + */ + public static String[] getAliases(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.getAliases(name); + } + + /** + * 获取aop代理对象 + * + * @param invoker + * @return + */ + @SuppressWarnings("unchecked") + public static T getAopProxy(T invoker) + { + return (T) AopContext.currentProxy(); + } + + /** + * 获取当前的环境配置,无配置返回null + * + * @return 当前的环境配置 + */ + public static String[] getActiveProfiles() + { + return applicationContext.getEnvironment().getActiveProfiles(); + } + + /** + * 获取当前的环境配置,当有多个环境配置时,只获取第一个 + * + * @return 当前的环境配置 + */ + public static String getActiveProfile() + { + final String[] activeProfiles = getActiveProfiles(); + return StringUtils.isNotEmpty(activeProfiles) ? activeProfiles[0] : null; + } + + /** + * 获取配置文件中的值 + * + * @param key 配置文件的key + * @return 当前的配置文件的值 + * + */ + public static String getRequiredProperty(String key) + { + return applicationContext.getEnvironment().getRequiredProperty(key); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/sql/SqlUtil.java b/boyue-common/src/main/java/com/boyue/common/utils/sql/SqlUtil.java new file mode 100644 index 0000000..b758920 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/sql/SqlUtil.java @@ -0,0 +1,66 @@ +package com.boyue.common.utils.sql; + +import java.io.StringReader; + +import com.boyue.common.exception.UtilException; +import com.boyue.common.utils.StringUtils; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserManager; +import net.sf.jsqlparser.statement.Statement; + +/** + * sql操作工具类 + * + * @author boyue + */ +public class SqlUtil { + + private static final CCJSqlParserManager parserManager = new CCJSqlParserManager(); + + /** + * 定义常用的 sql关键字 + */ + public static String SQL_REGEX = "and |extractvalue|updatexml|sleep|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()"; + + /** + * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) + */ + public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+"; + + /** + * 检查字符,防止注入绕过 + */ + public static String escapeOrderBySql(String value) { + if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) { + throw new UtilException("参数不符合规范,不能进行查询"); + } + return value; + } + + /** + * 验证 order by 语法是否符合规范 + */ + public static boolean isValidOrderBySql(String value) { + return value.matches(SQL_PATTERN); + } + + /** + * SQL关键字检查 + */ + public static void filterKeyword(String value) { + if (StringUtils.isEmpty(value)) { + return; + } + String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|"); + for (String sqlKeyword : sqlKeywords) { + if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1) { + throw new UtilException("参数存在SQL注入风险"); + } + } + } + + public static Statement parseSql(String sql) throws JSQLParserException { + return parserManager.parse(new StringReader(sql)); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/uuid/IdUtils.java b/boyue-common/src/main/java/com/boyue/common/utils/uuid/IdUtils.java new file mode 100644 index 0000000..76b0a0a --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/uuid/IdUtils.java @@ -0,0 +1,49 @@ +package com.boyue.common.utils.uuid; + +/** + * ID生成器工具类 + * + * @author boyue + */ +public class IdUtils +{ + /** + * 获取随机UUID + * + * @return 随机UUID + */ + public static String randomUUID() + { + return UUID.randomUUID().toString(); + } + + /** + * 简化的UUID,去掉了横线 + * + * @return 简化的UUID,去掉了横线 + */ + public static String simpleUUID() + { + return UUID.randomUUID().toString(true); + } + + /** + * 获取随机UUID,使用性能更好的ThreadLocalRandom生成UUID + * + * @return 随机UUID + */ + public static String fastUUID() + { + return UUID.fastUUID().toString(); + } + + /** + * 简化的UUID,去掉了横线,使用性能更好的ThreadLocalRandom生成UUID + * + * @return 简化的UUID,去掉了横线 + */ + public static String fastSimpleUUID() + { + return UUID.fastUUID().toString(true); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/uuid/Seq.java b/boyue-common/src/main/java/com/boyue/common/utils/uuid/Seq.java new file mode 100644 index 0000000..de15768 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/uuid/Seq.java @@ -0,0 +1,86 @@ +package com.boyue.common.utils.uuid; + +import java.util.concurrent.atomic.AtomicInteger; +import com.boyue.common.utils.DateUtils; +import com.boyue.common.utils.StringUtils; + +/** + * @author boyue 序列生成类 + */ +public class Seq +{ + // 通用序列类型 + public static final String commSeqType = "COMMON"; + + // 上传序列类型 + public static final String uploadSeqType = "UPLOAD"; + + // 通用接口序列数 + private static AtomicInteger commSeq = new AtomicInteger(1); + + // 上传接口序列数 + private static AtomicInteger uploadSeq = new AtomicInteger(1); + + // 机器标识 + private static final String machineCode = "A"; + + /** + * 获取通用序列号 + * + * @return 序列值 + */ + public static String getId() + { + return getId(commSeqType); + } + + /** + * 默认16位序列号 yyMMddHHmmss + 一位机器标识 + 3长度循环递增字符串 + * + * @return 序列值 + */ + public static String getId(String type) + { + AtomicInteger atomicInt = commSeq; + if (uploadSeqType.equals(type)) + { + atomicInt = uploadSeq; + } + return getId(atomicInt, 3); + } + + /** + * 通用接口序列号 yyMMddHHmmss + 一位机器标识 + length长度循环递增字符串 + * + * @param atomicInt 序列数 + * @param length 数值长度 + * @return 序列值 + */ + public static String getId(AtomicInteger atomicInt, int length) + { + String result = DateUtils.dateTimeNow(); + result += machineCode; + result += getSeq(atomicInt, length); + return result; + } + + /** + * 序列循环递增字符串[1, 10 的 (length)幂次方), 用0左补齐length位数 + * + * @return 序列值 + */ + private synchronized static String getSeq(AtomicInteger atomicInt, int length) + { + // 先取值再+1 + int value = atomicInt.getAndIncrement(); + + // 如果更新后值>=10 的 (length)幂次方则重置为1 + int maxSeq = (int) Math.pow(10, length); + if (atomicInt.get() >= maxSeq) + { + atomicInt.set(1); + } + // 转字符串,用0左补齐 + return StringUtils.padl(value, length); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/utils/uuid/UUID.java b/boyue-common/src/main/java/com/boyue/common/utils/uuid/UUID.java new file mode 100644 index 0000000..1e53d95 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/utils/uuid/UUID.java @@ -0,0 +1,484 @@ +package com.boyue.common.utils.uuid; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; +import com.boyue.common.exception.UtilException; + +/** + * 提供通用唯一识别码(universally unique identifier)(UUID)实现 + * + * @author boyue + */ +public final class UUID implements java.io.Serializable, Comparable +{ + private static final long serialVersionUID = -1185015143654744140L; + + /** + * SecureRandom 的单例 + * + */ + private static class Holder + { + static final SecureRandom numberGenerator = getSecureRandom(); + } + + /** 此UUID的最高64有效位 */ + private final long mostSigBits; + + /** 此UUID的最低64有效位 */ + private final long leastSigBits; + + /** + * 私有构造 + * + * @param data 数据 + */ + private UUID(byte[] data) + { + long msb = 0; + long lsb = 0; + assert data.length == 16 : "data must be 16 bytes in length"; + for (int i = 0; i < 8; i++) + { + msb = (msb << 8) | (data[i] & 0xff); + } + for (int i = 8; i < 16; i++) + { + lsb = (lsb << 8) | (data[i] & 0xff); + } + this.mostSigBits = msb; + this.leastSigBits = lsb; + } + + /** + * 使用指定的数据构造新的 UUID。 + * + * @param mostSigBits 用于 {@code UUID} 的最高有效 64 位 + * @param leastSigBits 用于 {@code UUID} 的最低有效 64 位 + */ + public UUID(long mostSigBits, long leastSigBits) + { + this.mostSigBits = mostSigBits; + this.leastSigBits = leastSigBits; + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的本地线程伪随机数生成器生成该 UUID。 + * + * @return 随机生成的 {@code UUID} + */ + public static UUID fastUUID() + { + return randomUUID(false); + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。 + * + * @return 随机生成的 {@code UUID} + */ + public static UUID randomUUID() + { + return randomUUID(true); + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。 + * + * @param isSecure 是否使用{@link SecureRandom}如果是可以获得更安全的随机码,否则可以得到更好的性能 + * @return 随机生成的 {@code UUID} + */ + public static UUID randomUUID(boolean isSecure) + { + final Random ng = isSecure ? Holder.numberGenerator : getRandom(); + + byte[] randomBytes = new byte[16]; + ng.nextBytes(randomBytes); + randomBytes[6] &= 0x0f; /* clear version */ + randomBytes[6] |= 0x40; /* set to version 4 */ + randomBytes[8] &= 0x3f; /* clear variant */ + randomBytes[8] |= 0x80; /* set to IETF variant */ + return new UUID(randomBytes); + } + + /** + * 根据指定的字节数组获取类型 3(基于名称的)UUID 的静态工厂。 + * + * @param name 用于构造 UUID 的字节数组。 + * + * @return 根据指定数组生成的 {@code UUID} + */ + public static UUID nameUUIDFromBytes(byte[] name) + { + MessageDigest md; + try + { + md = MessageDigest.getInstance("MD5"); + } + catch (NoSuchAlgorithmException nsae) + { + throw new InternalError("MD5 not supported"); + } + byte[] md5Bytes = md.digest(name); + md5Bytes[6] &= 0x0f; /* clear version */ + md5Bytes[6] |= 0x30; /* set to version 3 */ + md5Bytes[8] &= 0x3f; /* clear variant */ + md5Bytes[8] |= 0x80; /* set to IETF variant */ + return new UUID(md5Bytes); + } + + /** + * 根据 {@link #toString()} 方法中描述的字符串标准表示形式创建{@code UUID}。 + * + * @param name 指定 {@code UUID} 字符串 + * @return 具有指定值的 {@code UUID} + * @throws IllegalArgumentException 如果 name 与 {@link #toString} 中描述的字符串表示形式不符抛出此异常 + * + */ + public static UUID fromString(String name) + { + String[] components = name.split("-"); + if (components.length != 5) + { + throw new IllegalArgumentException("Invalid UUID string: " + name); + } + for (int i = 0; i < 5; i++) + { + components[i] = "0x" + components[i]; + } + + long mostSigBits = Long.decode(components[0]).longValue(); + mostSigBits <<= 16; + mostSigBits |= Long.decode(components[1]).longValue(); + mostSigBits <<= 16; + mostSigBits |= Long.decode(components[2]).longValue(); + + long leastSigBits = Long.decode(components[3]).longValue(); + leastSigBits <<= 48; + leastSigBits |= Long.decode(components[4]).longValue(); + + return new UUID(mostSigBits, leastSigBits); + } + + /** + * 返回此 UUID 的 128 位值中的最低有效 64 位。 + * + * @return 此 UUID 的 128 位值中的最低有效 64 位。 + */ + public long getLeastSignificantBits() + { + return leastSigBits; + } + + /** + * 返回此 UUID 的 128 位值中的最高有效 64 位。 + * + * @return 此 UUID 的 128 位值中最高有效 64 位。 + */ + public long getMostSignificantBits() + { + return mostSigBits; + } + + /** + * 与此 {@code UUID} 相关联的版本号. 版本号描述此 {@code UUID} 是如何生成的。 + *

+ * 版本号具有以下含意: + *

    + *
  • 1 基于时间的 UUID + *
  • 2 DCE 安全 UUID + *
  • 3 基于名称的 UUID + *
  • 4 随机生成的 UUID + *
+ * + * @return 此 {@code UUID} 的版本号 + */ + public int version() + { + // Version is bits masked by 0x000000000000F000 in MS long + return (int) ((mostSigBits >> 12) & 0x0f); + } + + /** + * 与此 {@code UUID} 相关联的变体号。变体号描述 {@code UUID} 的布局。 + *

+ * 变体号具有以下含意: + *

    + *
  • 0 为 NCS 向后兼容保留 + *
  • 2 IETF RFC 4122(Leach-Salz), 用于此类 + *
  • 6 保留,微软向后兼容 + *
  • 7 保留供以后定义使用 + *
+ * + * @return 此 {@code UUID} 相关联的变体号 + */ + public int variant() + { + // This field is composed of a varying number of bits. + // 0 - - Reserved for NCS backward compatibility + // 1 0 - The IETF aka Leach-Salz variant (used by this class) + // 1 1 0 Reserved, Microsoft backward compatibility + // 1 1 1 Reserved for future definition. + return (int) ((leastSigBits >>> (64 - (leastSigBits >>> 62))) & (leastSigBits >> 63)); + } + + /** + * 与此 UUID 相关联的时间戳值。 + * + *

+ * 60 位的时间戳值根据此 {@code UUID} 的 time_low、time_mid 和 time_hi 字段构造。
+ * 所得到的时间戳以 100 毫微秒为单位,从 UTC(通用协调时间) 1582 年 10 月 15 日零时开始。 + * + *

+ * 时间戳值仅在在基于时间的 UUID(其 version 类型为 1)中才有意义。
+ * 如果此 {@code UUID} 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 + * + * @throws UnsupportedOperationException 如果此 {@code UUID} 不是 version 为 1 的 UUID。 + */ + public long timestamp() throws UnsupportedOperationException + { + checkTimeBase(); + return (mostSigBits & 0x0FFFL) << 48// + | ((mostSigBits >> 16) & 0x0FFFFL) << 32// + | mostSigBits >>> 32; + } + + /** + * 与此 UUID 相关联的时钟序列值。 + * + *

+ * 14 位的时钟序列值根据此 UUID 的 clock_seq 字段构造。clock_seq 字段用于保证在基于时间的 UUID 中的时间唯一性。 + *

+ * {@code clockSequence} 值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。 如果此 UUID 不是基于时间的 UUID,则此方法抛出 + * UnsupportedOperationException。 + * + * @return 此 {@code UUID} 的时钟序列 + * + * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 + */ + public int clockSequence() throws UnsupportedOperationException + { + checkTimeBase(); + return (int) ((leastSigBits & 0x3FFF000000000000L) >>> 48); + } + + /** + * 与此 UUID 相关的节点值。 + * + *

+ * 48 位的节点值根据此 UUID 的 node 字段构造。此字段旨在用于保存机器的 IEEE 802 地址,该地址用于生成此 UUID 以保证空间唯一性。 + *

+ * 节点值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。
+ * 如果此 UUID 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 + * + * @return 此 {@code UUID} 的节点值 + * + * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 + */ + public long node() throws UnsupportedOperationException + { + checkTimeBase(); + return leastSigBits & 0x0000FFFFFFFFFFFFL; + } + + /** + * 返回此{@code UUID} 的字符串表现形式。 + * + *

+ * UUID 的字符串表示形式由此 BNF 描述: + * + *

+     * {@code
+     * UUID                   = ----
+     * time_low               = 4*
+     * time_mid               = 2*
+     * time_high_and_version  = 2*
+     * variant_and_sequence   = 2*
+     * node                   = 6*
+     * hexOctet               = 
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * 
+ * + * + * + * @return 此{@code UUID} 的字符串表现形式 + * @see #toString(boolean) + */ + @Override + public String toString() + { + return toString(false); + } + + /** + * 返回此{@code UUID} 的字符串表现形式。 + * + *

+ * UUID 的字符串表示形式由此 BNF 描述: + * + *

+     * {@code
+     * UUID                   = ----
+     * time_low               = 4*
+     * time_mid               = 2*
+     * time_high_and_version  = 2*
+     * variant_and_sequence   = 2*
+     * node                   = 6*
+     * hexOctet               = 
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * 
+ * + * + * + * @param isSimple 是否简单模式,简单模式为不带'-'的UUID字符串 + * @return 此{@code UUID} 的字符串表现形式 + */ + public String toString(boolean isSimple) + { + final StringBuilder builder = new StringBuilder(isSimple ? 32 : 36); + // time_low + builder.append(digits(mostSigBits >> 32, 8)); + if (!isSimple) + { + builder.append('-'); + } + // time_mid + builder.append(digits(mostSigBits >> 16, 4)); + if (!isSimple) + { + builder.append('-'); + } + // time_high_and_version + builder.append(digits(mostSigBits, 4)); + if (!isSimple) + { + builder.append('-'); + } + // variant_and_sequence + builder.append(digits(leastSigBits >> 48, 4)); + if (!isSimple) + { + builder.append('-'); + } + // node + builder.append(digits(leastSigBits, 12)); + + return builder.toString(); + } + + /** + * 返回此 UUID 的哈希码。 + * + * @return UUID 的哈希码值。 + */ + @Override + public int hashCode() + { + long hilo = mostSigBits ^ leastSigBits; + return ((int) (hilo >> 32)) ^ (int) hilo; + } + + /** + * 将此对象与指定对象比较。 + *

+ * 当且仅当参数不为 {@code null}、而是一个 UUID 对象、具有与此 UUID 相同的 varriant、包含相同的值(每一位均相同)时,结果才为 {@code true}。 + * + * @param obj 要与之比较的对象 + * + * @return 如果对象相同,则返回 {@code true};否则返回 {@code false} + */ + @Override + public boolean equals(Object obj) + { + if ((null == obj) || (obj.getClass() != UUID.class)) + { + return false; + } + UUID id = (UUID) obj; + return (mostSigBits == id.mostSigBits && leastSigBits == id.leastSigBits); + } + + // Comparison Operations + + /** + * 将此 UUID 与指定的 UUID 比较。 + * + *

+ * 如果两个 UUID 不同,且第一个 UUID 的最高有效字段大于第二个 UUID 的对应字段,则第一个 UUID 大于第二个 UUID。 + * + * @param val 与此 UUID 比较的 UUID + * + * @return 在此 UUID 小于、等于或大于 val 时,分别返回 -1、0 或 1。 + * + */ + @Override + public int compareTo(UUID val) + { + // The ordering is intentionally set up so that the UUIDs + // can simply be numerically compared as two numbers + return (this.mostSigBits < val.mostSigBits ? -1 : // + (this.mostSigBits > val.mostSigBits ? 1 : // + (this.leastSigBits < val.leastSigBits ? -1 : // + (this.leastSigBits > val.leastSigBits ? 1 : // + 0)))); + } + + // ------------------------------------------------------------------------------------------------------------------- + // Private method start + /** + * 返回指定数字对应的hex值 + * + * @param val 值 + * @param digits 位 + * @return 值 + */ + private static String digits(long val, int digits) + { + long hi = 1L << (digits * 4); + return Long.toHexString(hi | (val & (hi - 1))).substring(1); + } + + /** + * 检查是否为time-based版本UUID + */ + private void checkTimeBase() + { + if (version() != 1) + { + throw new UnsupportedOperationException("Not a time-based UUID"); + } + } + + /** + * 获取{@link SecureRandom},类提供加密的强随机数生成器 (RNG) + * + * @return {@link SecureRandom} + */ + public static SecureRandom getSecureRandom() + { + try + { + return SecureRandom.getInstance("SHA1PRNG"); + } + catch (NoSuchAlgorithmException e) + { + throw new UtilException(e); + } + } + + /** + * 获取随机数生成器对象
+ * ThreadLocalRandom是JDK 7之后提供并发产生随机数,能够解决多个线程发生的竞争争夺。 + * + * @return {@link ThreadLocalRandom} + */ + public static ThreadLocalRandom getRandom() + { + return ThreadLocalRandom.current(); + } +} diff --git a/boyue-common/src/main/java/com/boyue/common/xss/Xss.java b/boyue-common/src/main/java/com/boyue/common/xss/Xss.java new file mode 100644 index 0000000..7f831ff --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/xss/Xss.java @@ -0,0 +1,27 @@ +package com.boyue.common.xss; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义xss校验注解 + * + * @author boyue + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = { ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER }) +@Constraint(validatedBy = { XssValidator.class }) +public @interface Xss +{ + String message() + + default "不允许任何脚本运行"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/boyue-common/src/main/java/com/boyue/common/xss/XssValidator.java b/boyue-common/src/main/java/com/boyue/common/xss/XssValidator.java new file mode 100644 index 0000000..2dce621 --- /dev/null +++ b/boyue-common/src/main/java/com/boyue/common/xss/XssValidator.java @@ -0,0 +1,34 @@ +package com.boyue.common.xss; + +import com.boyue.common.utils.StringUtils; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 自定义xss校验注解实现 + * + * @author boyue + */ +public class XssValidator implements ConstraintValidator +{ + private static final String HTML_PATTERN = "<(\\S*?)[^>]*>.*?|<.*? />"; + + @Override + public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) + { + if (StringUtils.isBlank(value)) + { + return true; + } + return !containsHtml(value); + } + + public static boolean containsHtml(String value) + { + Pattern pattern = Pattern.compile(HTML_PATTERN); + Matcher matcher = pattern.matcher(value); + return matcher.matches(); + } +} \ No newline at end of file diff --git a/boyue-file/boyue-file-common/pom.xml b/boyue-file/boyue-file-common/pom.xml new file mode 100644 index 0000000..204f8bf --- /dev/null +++ b/boyue-file/boyue-file-common/pom.xml @@ -0,0 +1,24 @@ + + + + boyue-file + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-file-common + + + 文件模块公共依赖 + + + + + com.boyue + boyue-common + + + + \ No newline at end of file diff --git a/boyue-file/boyue-file-common/src/main/java/com/boyue/file/domain/SysFileInfo.java b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/domain/SysFileInfo.java new file mode 100644 index 0000000..bb5f95c --- /dev/null +++ b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/domain/SysFileInfo.java @@ -0,0 +1,60 @@ +package com.boyue.file.domain; + +import com.boyue.common.annotation.Excel; +import com.boyue.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 文件信息对象 sys_file_info + * + * @author boyue + * @date 2025-04-25 + */ +@Schema(description = "文件信息对象") +@Data +@EqualsAndHashCode(callSuper = true) +public class SysFileInfo extends BaseEntity { + private static final long serialVersionUID = 1L; + + /** 文件主键 */ + @Schema(title = "文件主键") + private Long fileId; + + /** 原始文件名 */ + @Schema(title = "原始文件名") + @Excel(name = "原始文件名") + private String fileName; + + /** 统一逻辑路径(/开头) */ + @Schema(title = "统一逻辑路径(/开头)") + @Excel(name = "统一逻辑路径", readConverterExp = "/=开头") + private String filePath; + + /** 存储类型(local/minio/oss) */ + @Schema(title = "存储类型(local/minio/oss)") + @Excel(name = "存储类型", readConverterExp = "l=ocal/minio/oss") + private String storageType; + + /** 文件类型/后缀 */ + @Schema(title = "文件类型/后缀") + @Excel(name = "文件类型/后缀") + private String fileType; + + /** 文件大小(字节) */ + @Schema(title = "文件大小(字节)") + @Excel(name = "文件大小", readConverterExp = "字=节") + private Long fileSize; + + /** 文件MD5 */ + @Schema(title = "文件MD5") + @Excel(name = "文件MD5") + private String md5; + + /** 删除标志(0代表存在 2代表删除) */ + @Schema(title = "删除标志(0代表存在 2代表删除)") + private String delFlag; + +} diff --git a/boyue-file/boyue-file-common/src/main/java/com/boyue/file/domain/SysFilePartETag.java b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/domain/SysFilePartETag.java new file mode 100644 index 0000000..9499888 --- /dev/null +++ b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/domain/SysFilePartETag.java @@ -0,0 +1,55 @@ +package com.boyue.file.domain; + +import java.io.Serializable; + +import lombok.Data; + +@Data +public class SysFilePartETag implements Serializable { + private static final long serialVersionUID = 2471854027355307627L; + private Integer partNumber; + private String eTag; + private Long partSize; + private Long partCRC; + + public void seteTag(String eTag) { + this.eTag = eTag; + } + + public SysFilePartETag() { + } + + public SysFilePartETag(Integer partNumber, String eTag) { + this.partNumber = partNumber; + this.eTag = eTag; + } + + public SysFilePartETag(Integer partNumber, String eTag, long partSize, Long partCRC) { + this.partNumber = partNumber; + this.eTag = eTag; + this.partSize = partSize; + this.partCRC = partCRC; + } + + public int hashCode() { + int result = 1; + result = 31 * result + (this.eTag == null ? 0 : this.eTag.hashCode()); + result = 31 * result + this.partNumber; + return result; + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (!(obj instanceof SysFilePartETag)) { + return false; + } else { + SysFilePartETag other = (SysFilePartETag) obj; + if (this.partNumber != other.partNumber) { + return false; + } else { + return this.eTag == null ? other.eTag == null : this.eTag.equals(other.eTag); + } + } + } +} diff --git a/boyue-file/boyue-file-common/src/main/java/com/boyue/file/local/config/LocalBucketProperties.java b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/local/config/LocalBucketProperties.java new file mode 100644 index 0000000..bc2ee59 --- /dev/null +++ b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/local/config/LocalBucketProperties.java @@ -0,0 +1,10 @@ +package com.boyue.file.local.config; + +import lombok.Data; + +@Data +public class LocalBucketProperties { + private String path; + private String permission; + private String api; +} diff --git a/boyue-file/boyue-file-common/src/main/java/com/boyue/file/local/config/LocalManagement.java b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/local/config/LocalManagement.java new file mode 100644 index 0000000..25ec2a1 --- /dev/null +++ b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/local/config/LocalManagement.java @@ -0,0 +1,81 @@ +package com.boyue.file.local.config; + +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import com.boyue.file.local.domain.LocalBucket; +import com.boyue.file.storage.StorageManagement; + +@Configuration("local") +@ConditionalOnProperty(prefix = "local", name = { "enable" }, havingValue = "true", matchIfMissing = false) +@ConfigurationProperties("local") +public class LocalManagement implements StorageManagement, WebMvcConfigurer { + private static final Logger logger = LoggerFactory.getLogger(LocalManagement.class); + private Map client; + private String primary; + private Map targetLocalBucket = new HashMap<>(); + private LocalBucket primaryBucket; + + @Override + public void afterPropertiesSet() throws Exception { + if (client == null || client.isEmpty()) { + throw new RuntimeException("Local client properties cannot be null or empty"); + } + client.forEach((name, props) -> { + targetLocalBucket.put(name, LocalBucket.builder() + .clientName(name) + .basePath(props.getPath()) + .permission(props.getPermission()) + .api(props.getApi()) + .build()); + logger.info("本地存储目录:{} - 配置成功,路径:{}", name, props.getPath()); + }); + if (targetLocalBucket.get(primary) == null) { + throw new RuntimeException("Primary local client " + primary + " does not exist"); + } + primaryBucket = targetLocalBucket.get(primary); + } + + @Override + public LocalBucket getBucket(String clientName) { + return targetLocalBucket.get(clientName); + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + client.forEach((name, props) -> { + if ("public".equals(props.getPermission())) { + registry.addResourceHandler(props.getApi() + "/**") + .addResourceLocations("file:" + props.getPath() + "/"); + } + }); + } + + public LocalBucket getPrimaryBucket() { + return this.primaryBucket; + } + + public Map getClient() { + return client; + } + + public void setClient(Map client) { + this.client = client; + } + + public String getPrimary() { + return primary; + } + + public void setPrimary(String primary) { + this.primary = primary; + } +} diff --git a/boyue-file/boyue-file-common/src/main/java/com/boyue/file/local/domain/LocalBucket.java b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/local/domain/LocalBucket.java new file mode 100644 index 0000000..5c042ed --- /dev/null +++ b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/local/domain/LocalBucket.java @@ -0,0 +1,218 @@ +package com.boyue.file.local.domain; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URI; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.channels.FileChannel; +import java.nio.channels.WritableByteChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.web.multipart.MultipartFile; + +import com.boyue.common.exception.ServiceException; +import com.boyue.common.utils.ServletUtils; +import com.boyue.common.utils.sign.Md5Utils; +import com.boyue.common.utils.uuid.UUID; +import com.boyue.file.domain.SysFilePartETag; +import com.boyue.file.storage.StorageBucket; +import com.boyue.file.storage.StorageEntity; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.Builder; +import lombok.extern.slf4j.Slf4j; + +@Builder +@Slf4j +public class LocalBucket implements StorageBucket { + + private String clientName; + private String basePath; + private String permission; + private String api; + + @Override + public void put(String filePath, MultipartFile file) { + Path dest = Paths.get(basePath, filePath); + try (InputStream inputStream = file.getInputStream()) { + Files.createDirectories(dest.getParent()); + Files.copy(inputStream, dest); + } catch (Exception e) { + throw new ServiceException("Failed to upload file: " + e.getMessage()); + } + } + + @Override + public StorageEntity get(String filePath) throws IOException { + Path file = Paths.get(basePath, filePath); + StorageEntity fileEntity = new StorageEntity(); + fileEntity.setFilePath(filePath); + fileEntity.setInputStream(new FileInputStream(file.toFile())); + fileEntity.setByteCount(file.toFile().length()); + return fileEntity; + } + + @Override + public void remove(String filePath) throws IOException { + Path file = Paths.get(basePath, filePath); + Files.deleteIfExists(file); + } + + @Override + public URL generatePresignedUrl(String filePath, int expireTime) throws Exception { + HttpServletRequest request = ServletUtils.getRequest(); + StringBuffer url = request.getRequestURL(); + String contextPath = request.getSession().getServletContext().getContextPath(); + String toHex = Md5Utils.hash(filePath + expireTime); + StringBuilder sb = new StringBuilder(); + sb.append(url.delete(url.length() - request.getRequestURI().length(), url.length()) + .append(contextPath).toString()) + .append(getApi()).append("?") + .append("filePath=").append(URLEncoder.encode(filePath, "UTF-8")) + .append("&toHex=").append(toHex); + return URI.create(sb.toString()).toURL(); + } + + @Override + public URL generatePublicURL(String filePath) throws Exception { + HttpServletRequest request = ServletUtils.getRequest(); + StringBuffer url = request.getRequestURL(); + String contextPath = request.getSession().getServletContext().getContextPath(); + StringBuilder sb = new StringBuilder(); + sb.append(url.delete(url.length() - request.getRequestURI().length(), url.length()) + .append(contextPath).toString()) + .append(getApi()).append("/") + .append(filePath.replace("\\", "/")); + return new URI(sb.toString()).toURL(); + } + + // 存储分片上传的元数据 + private final ConcurrentHashMap>> uploadMetadata = new ConcurrentHashMap<>(); + + public String initMultipartUpload(String filePath) throws Exception { + try { + String uploadId = UUID.randomUUID().toString(); + uploadMetadata.put(uploadId, new ArrayList<>()); + + // 创建临时上传目录 + Path tempDir = Paths.get(basePath, "temp_uploads", uploadId); + Files.createDirectories(tempDir); + return uploadId; + } catch (Exception e) { + log.error("初始化失败: 文件={}, 错误={}", filePath, e.getMessage()); + throw new ServiceException("初始化分片上传失败: " + e.getMessage()); + } + } + + public SysFilePartETag uploadPart(String filePath, String uploadId, int partNumber, long partSize, + InputStream inputStream) + throws Exception { + if (!uploadMetadata.containsKey(uploadId)) { + throw new ServiceException("无效的 uploadId: " + uploadId); + } + Path tempDir = Paths.get(basePath, "temp_uploads", uploadId); + Path partPath = tempDir.resolve("part_" + partNumber); + try (OutputStream fos = Files.newOutputStream(partPath)) { + byte[] buffer = new byte[8192]; + int bytesRead; + long totalBytesWritten = 0; + while ((bytesRead = inputStream.read(buffer)) != -1 && totalBytesWritten < partSize) { + int writeSize = (int) Math.min(bytesRead, partSize - totalBytesWritten); + fos.write(buffer, 0, writeSize); + totalBytesWritten += writeSize; + } + if (totalBytesWritten != partSize) { + throw new ServiceException("分片大小不匹配: 预期=" + partSize + ", 实际=" + totalBytesWritten); + } + } + String etag = Md5Utils.getMd5(partPath.toFile()); + if (etag == null) { + throw new ServiceException("计算分片 MD5 失败"); + } + etag = etag.toUpperCase(); + Map partInfo = Map.of( + "partNumber", partNumber, + "etag", etag, + "size", partSize, + "path", partPath.toString()); + synchronized (uploadMetadata) { + List> parts = uploadMetadata.get(uploadId); + int insertPos = 0; + while (insertPos < parts.size() + && ((Number) parts.get(insertPos).get("partNumber")).intValue() < partNumber) { + insertPos++; + } + parts.add(insertPos, partInfo); + } + return new SysFilePartETag(partNumber, etag, partSize, null); + } + + public String completeMultipartUpload(String filePath, String uploadId, List partETags) + throws Exception { + List> storedParts = uploadMetadata.get(uploadId); + if (storedParts == null) { + throw new ServiceException("无效的 uploadId: " + uploadId); + } + if (partETags.size() != storedParts.size()) { + throw new ServiceException("分片数量不匹配: 预期=" + storedParts.size() + ", 实际=" + partETags.size()); + } + // 验证每个分片的 ETag + for (int i = 0; i < partETags.size(); i++) { + Map expected = storedParts.get(i); + SysFilePartETag actual = partETags.get(i); + + if (!expected.get("etag").equals(actual.getETag()) || + !expected.get("partNumber").equals(actual.getPartNumber())) { + throw new ServiceException("分片验证失败: 序号=" + actual.getPartNumber()); + } + } + Path destPath = Paths.get(basePath, filePath); + Files.createDirectories(destPath.getParent()); + try (WritableByteChannel outChannel = Files.newByteChannel( + destPath, + StandardOpenOption.CREATE, + StandardOpenOption.WRITE, + StandardOpenOption.TRUNCATE_EXISTING)) { + for (Map part : storedParts) { + Path partPath = Paths.get((String) part.get("path")); + try (FileChannel inChannel = FileChannel.open(partPath, StandardOpenOption.READ)) { + inChannel.transferTo(0, inChannel.size(), outChannel); + } + } + } + // 清理临时文件和元数据 + Path tempDir = Paths.get(basePath, "temp_uploads", uploadId); + Files.walk(tempDir).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); + uploadMetadata.remove(uploadId); + log.info("分片合并完成: 文件={}, uploadId={}, 分片数={}", filePath, uploadId, storedParts.size()); + return filePath; + } + + public String getClientName() { + return clientName; + } + + public String getBasePath() { + return basePath; + } + + public String getPermission() { + return permission; + } + + public String getApi() { + return api; + } +} diff --git a/boyue-file/boyue-file-common/src/main/java/com/boyue/file/local/service/LocalFileService.java b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/local/service/LocalFileService.java new file mode 100644 index 0000000..df72f1e --- /dev/null +++ b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/local/service/LocalFileService.java @@ -0,0 +1,82 @@ +package com.boyue.file.local.service; + +import java.io.InputStream; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import com.boyue.file.domain.SysFilePartETag; +import com.boyue.file.local.config.LocalManagement; +import com.boyue.file.local.domain.LocalBucket; +import com.boyue.file.storage.StorageEntity; +import com.boyue.file.storage.StorageService; + +/** + * 磁盘文件操作实现类 + */ +@Component("file:strategy:local") +@ConditionalOnProperty(prefix = "local", name = { "enable" }, havingValue = "true", matchIfMissing = false) +public class LocalFileService implements StorageService { + + @Autowired + LocalManagement localConfig; + + @Override + public String upload(String filePath, MultipartFile file) throws Exception { + LocalBucket primaryBucket = localConfig.getPrimaryBucket(); + primaryBucket.put(filePath, file); + return generateUrl(filePath); + } + + @Override + public InputStream downLoad(String filePath) throws Exception { + LocalBucket primaryBucket = localConfig.getPrimaryBucket(); + return primaryBucket.get(filePath).getInputStream(); + } + + @Override + public StorageEntity getFile(String filePath) throws Exception { + LocalBucket primaryBucket = localConfig.getPrimaryBucket(); + return primaryBucket.get(filePath); + } + + @Override + public boolean deleteFile(String filePath) throws Exception { + LocalBucket primaryBucket = localConfig.getPrimaryBucket(); + primaryBucket.remove(filePath); + return true; + } + + @Override + public String generateUrl(String filePath) throws Exception { + LocalBucket primaryBucket = localConfig.getPrimaryBucket(); + if ("public".equals(primaryBucket.getPermission())) { + return primaryBucket.generatePublicURL(filePath).toString(); + } else { + return primaryBucket.generatePresignedUrl(filePath, 3600).toString(); + } + } + + @Override + public String initMultipartUpload(String filePath) throws Exception { + LocalBucket primaryBucket = localConfig.getPrimaryBucket(); + return primaryBucket.initMultipartUpload(filePath); + } + + @Override + public String uploadPart(String filePath, String uploadId, int partNumber, long partSize, InputStream inputStream) + throws Exception { + LocalBucket primaryBucket = localConfig.getPrimaryBucket(); + return primaryBucket.uploadPart(filePath, uploadId, partNumber, partSize, inputStream).getETag(); + } + + @Override + public String completeMultipartUpload(String filePath, String uploadId, List partETags) + throws Exception { + LocalBucket primaryBucket = localConfig.getPrimaryBucket(); + return primaryBucket.completeMultipartUpload(filePath, uploadId, partETags); + } +} diff --git a/boyue-file/boyue-file-common/src/main/java/com/boyue/file/mapper/SysFileInfoMapper.java b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/mapper/SysFileInfoMapper.java new file mode 100644 index 0000000..4c6c1f7 --- /dev/null +++ b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/mapper/SysFileInfoMapper.java @@ -0,0 +1,61 @@ +package com.boyue.file.mapper; + +import java.util.List; +import com.boyue.file.domain.SysFileInfo; + +/** + * 文件信息Mapper接口 + * + * @author boyue + * @date 2025-04-25 + */ +public interface SysFileInfoMapper +{ + /** + * 查询文件信息 + * + * @param fileId 文件信息主键 + * @return 文件信息 + */ + public SysFileInfo selectSysFileInfoByFileId(Long fileId); + + /** + * 查询文件信息列表 + * + * @param sysFileInfo 文件信息 + * @return 文件信息集合 + */ + public List selectSysFileInfoList(SysFileInfo sysFileInfo); + + /** + * 新增文件信息 + * + * @param sysFileInfo 文件信息 + * @return 结果 + */ + public int insertSysFileInfo(SysFileInfo sysFileInfo); + + /** + * 修改文件信息 + * + * @param sysFileInfo 文件信息 + * @return 结果 + */ + public int updateSysFileInfo(SysFileInfo sysFileInfo); + + /** + * 删除文件信息 + * + * @param fileId 文件信息主键 + * @return 结果 + */ + public int deleteSysFileInfoByFileId(Long fileId); + + /** + * 批量删除文件信息 + * + * @param fileIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteSysFileInfoByFileIds(Long[] fileIds); +} diff --git a/boyue-file/boyue-file-common/src/main/java/com/boyue/file/service/ISysFileInfoService.java b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/service/ISysFileInfoService.java new file mode 100644 index 0000000..fa2e89e --- /dev/null +++ b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/service/ISysFileInfoService.java @@ -0,0 +1,61 @@ +package com.boyue.file.service; + +import java.util.List; +import com.boyue.file.domain.SysFileInfo; + +/** + * 文件信息Service接口 + * + * @author boyue + * @date 2025-04-25 + */ +public interface ISysFileInfoService +{ + /** + * 查询文件信息 + * + * @param fileId 文件信息主键 + * @return 文件信息 + */ + public SysFileInfo selectSysFileInfoByFileId(Long fileId); + + /** + * 查询文件信息列表 + * + * @param sysFileInfo 文件信息 + * @return 文件信息集合 + */ + public List selectSysFileInfoList(SysFileInfo sysFileInfo); + + /** + * 新增文件信息 + * + * @param sysFileInfo 文件信息 + * @return 结果 + */ + public int insertSysFileInfo(SysFileInfo sysFileInfo); + + /** + * 修改文件信息 + * + * @param sysFileInfo 文件信息 + * @return 结果 + */ + public int updateSysFileInfo(SysFileInfo sysFileInfo); + + /** + * 批量删除文件信息 + * + * @param fileIds 需要删除的文件信息主键集合 + * @return 结果 + */ + public int deleteSysFileInfoByFileIds(Long[] fileIds); + + /** + * 删除文件信息信息 + * + * @param fileId 文件信息主键 + * @return 结果 + */ + public int deleteSysFileInfoByFileId(Long fileId); +} diff --git a/boyue-file/boyue-file-common/src/main/java/com/boyue/file/service/impl/SysFileInfoServiceImpl.java b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/service/impl/SysFileInfoServiceImpl.java new file mode 100644 index 0000000..d81f4ac --- /dev/null +++ b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/service/impl/SysFileInfoServiceImpl.java @@ -0,0 +1,96 @@ +package com.boyue.file.service.impl; + +import java.util.List; +import com.boyue.common.utils.DateUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.boyue.file.mapper.SysFileInfoMapper; +import com.boyue.file.domain.SysFileInfo; +import com.boyue.file.service.ISysFileInfoService; + +/** + * 文件信息Service业务层处理 + * + * @author boyue + * @date 2025-04-25 + */ +@Service +public class SysFileInfoServiceImpl implements ISysFileInfoService +{ + @Autowired + private SysFileInfoMapper sysFileInfoMapper; + + /** + * 查询文件信息 + * + * @param fileId 文件信息主键 + * @return 文件信息 + */ + @Override + public SysFileInfo selectSysFileInfoByFileId(Long fileId) + { + return sysFileInfoMapper.selectSysFileInfoByFileId(fileId); + } + + /** + * 查询文件信息列表 + * + * @param sysFileInfo 文件信息 + * @return 文件信息 + */ + @Override + public List selectSysFileInfoList(SysFileInfo sysFileInfo) + { + return sysFileInfoMapper.selectSysFileInfoList(sysFileInfo); + } + + /** + * 新增文件信息 + * + * @param sysFileInfo 文件信息 + * @return 结果 + */ + @Override + public int insertSysFileInfo(SysFileInfo sysFileInfo) + { + sysFileInfo.setCreateTime(DateUtils.getNowDate()); + return sysFileInfoMapper.insertSysFileInfo(sysFileInfo); + } + + /** + * 修改文件信息 + * + * @param sysFileInfo 文件信息 + * @return 结果 + */ + @Override + public int updateSysFileInfo(SysFileInfo sysFileInfo) + { + sysFileInfo.setUpdateTime(DateUtils.getNowDate()); + return sysFileInfoMapper.updateSysFileInfo(sysFileInfo); + } + + /** + * 批量删除文件信息 + * + * @param fileIds 需要删除的文件信息主键 + * @return 结果 + */ + @Override + public int deleteSysFileInfoByFileIds(Long[] fileIds) + { + return sysFileInfoMapper.deleteSysFileInfoByFileIds(fileIds); + } + + /** + * 删除文件信息信息 + * + * @param fileId 文件信息主键 + * @return 结果 + */ + @Override + public int deleteSysFileInfoByFileId(Long fileId) + { + return sysFileInfoMapper.deleteSysFileInfoByFileId(fileId); + } +} diff --git a/boyue-file/boyue-file-common/src/main/java/com/boyue/file/storage/StorageBucket.java b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/storage/StorageBucket.java new file mode 100644 index 0000000..7dd4b4e --- /dev/null +++ b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/storage/StorageBucket.java @@ -0,0 +1,109 @@ +package com.boyue.file.storage; + +import java.io.InputStream; +import java.net.URL; +import java.util.List; + +import org.springframework.web.multipart.MultipartFile; + +import com.boyue.file.domain.SysFilePartETag; + +public interface StorageBucket { + + /** + * 获取文件实例 + * + * @param filePath + * @return + * @throws Exception + */ + StorageEntity get(String filepath) throws Exception; + + /** + * 上传文件 + * + * @param filePath + * @param file + * @throws Exception + */ + void put(String filePath, MultipartFile file) throws Exception; + + /** + * 删除文件 + * + * @param filePath + * @throws Exception + */ + void remove(String filePath) throws Exception; + + /** + * 生成预签名URL + * + * @param filePath + * @return + * @throws Exception + */ + URL generatePresignedUrl(String filePath, int expireTime) throws Exception; + + /** + * 获取文件的公开访问方式的URL + * + * @param filePath 文件路径 + * @return 公开访问URL + */ + URL generatePublicURL(String filePath) throws Exception; + + /** + * 获取存储渠道权限 + * + * @return public/private + */ + String getPermission(); + + /** + * 获取文件的默认访问方式的URL + * + * @param filePath + * @return + * @throws Exception + */ + default URL getUrl(String filePath) throws Exception { + if ("public".equals(getPermission())) { + return generatePublicURL(filePath); + } else { + return generatePresignedUrl(filePath, 3600); + } + }; + + /** + * 初始化分片上传 + * + * @param filePath 文件路径 + * @return 返回uploadId + */ + public String initMultipartUpload(String filePath) throws Exception; + + /** + * 上传分片 + * + * @param filePath 文件路径 + * @param uploadId 上传ID + * @param partNumber 分片序号 + * @param partSize 分片大小 + * @param inputStream 分片数据流 + * @return 分片的ETag + */ + public SysFilePartETag uploadPart(String filePath, String uploadId, int partNumber, long partSize, + InputStream inputStream) + throws Exception; + + /** + * 完成分片上传 + * + * @param filePath 文件路径 + * @param uploadId 上传ID + * @return 文件的最终路径 + */ + public String completeMultipartUpload(String filePath, String uploadId, List partETags) + throws Exception; +} diff --git a/boyue-file/boyue-file-common/src/main/java/com/boyue/file/storage/StorageEntity.java b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/storage/StorageEntity.java new file mode 100644 index 0000000..14513f7 --- /dev/null +++ b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/storage/StorageEntity.java @@ -0,0 +1,12 @@ +package com.boyue.file.storage; + +import java.io.InputStream; + +import lombok.Data; + +@Data +public class StorageEntity { + private InputStream inputStream; + private Long byteCount; + private String filePath; +} diff --git a/boyue-file/boyue-file-common/src/main/java/com/boyue/file/storage/StorageManagement.java b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/storage/StorageManagement.java new file mode 100644 index 0000000..d1cdd3d --- /dev/null +++ b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/storage/StorageManagement.java @@ -0,0 +1,25 @@ +package com.boyue.file.storage; + +import java.util.Map; + +import org.springframework.beans.factory.InitializingBean; + +public interface StorageManagement extends InitializingBean { + /** + * 获取存储桶 + * + * @param clientName 客户端名称 + * @return 存储桶 + */ + StorageBucket getBucket(String clientName); + + /** + * 获取存储桶 + * + * @param clientName 客户端名称 + * @param bucketName 存储桶名称 + * @return 存储桶 + */ + public Map getClient(); + +} diff --git a/boyue-file/boyue-file-common/src/main/java/com/boyue/file/storage/StorageManagements.java b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/storage/StorageManagements.java new file mode 100644 index 0000000..cb5de46 --- /dev/null +++ b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/storage/StorageManagements.java @@ -0,0 +1,32 @@ +package com.boyue.file.storage; + +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class StorageManagements { + @Autowired + private Map storageConfigs; + + public StorageManagement getStorageConfig(String clientName) { + return storageConfigs.get(clientName); + } + + public StorageBucket getStorageBucket(String storage, String clientName) { + StorageManagement config = getStorageConfig(storage); + if (config != null) { + return config.getBucket(clientName); + } + return null; + } + + public Map getStorageTypes() { + return storageConfigs; + } + + public void setStorageTypes(Map storageConfigs) { + this.storageConfigs = storageConfigs; + } +} diff --git a/boyue-file/boyue-file-common/src/main/java/com/boyue/file/storage/StorageService.java b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/storage/StorageService.java new file mode 100644 index 0000000..7e9d589 --- /dev/null +++ b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/storage/StorageService.java @@ -0,0 +1,122 @@ +package com.boyue.file.storage; + +import java.io.File; +import java.io.InputStream; +import java.util.List; + +import org.springframework.web.multipart.MultipartFile; + +import com.boyue.common.utils.DateUtils; +import com.boyue.common.utils.file.FileUtils; +import com.boyue.file.domain.SysFilePartETag; + +/** + * 文件操作接口 + */ +public interface StorageService { + + /** + * 上传文件 + * + * @param file 上传的文件 + * @return + * @throws Exception 比如读写文件出错时 + * + */ + default public String upload(MultipartFile file) throws Exception { + return upload(FileUtils.fastFilePath(file), file); + }; + + /** + * 上传文件(指定文件名称) + * + * @param file 上传的文件 + * @param fileName 指定上传文件的名称 + * @return + * @throws Exception 比如读写文件出错时 + * + */ + default public String upload(MultipartFile file, String fileName) throws Exception { + return upload(DateUtils.datePath() + File.separator + fileName, file); + }; + + /** + * 上传文件(指定文件路径) + * + * @param filePath 指定上传文件的路径 + * @param file 上传的文件 + * @return + * @throws Exception 比如读写文件出错时 + * + */ + public String upload(String filePath, MultipartFile file) throws Exception; + + /** + * 下载文件 + * + * @param filePath 文件路径 + * @return 返回文件输入流 + * @throws Exception 比如读写文件出错时 + * + */ + public InputStream downLoad(String filePath) throws Exception; + + /** + * 获取文件实体对象 + * + * @param filePath 文件路径 + * @return 文件对象 + * @throws Exception + */ + public StorageEntity getFile(String filePath) throws Exception; + + /** + * 删除文件 + * + * @param filePath 文件路径 + * @return 返回是否删除成功 + * @throws Exception 比如读写文件出错时 + * + */ + public boolean deleteFile(String filePath) throws Exception; + + /** + * 生成文件访问链接 + * + * @param filePath 文件路径 + * @return 返回文件访问链接 + * @throws Exception 比如读写文件出错时 + * + */ + public String generateUrl(String filePath) throws Exception; + + /** + * 初始化分片上传 + * + * @param filePath 文件路径 + * @return 返回uploadId + */ + public String initMultipartUpload(String filePath) throws Exception; + + /** + * 上传分片 + * + * @param filePath 文件路径 + * @param uploadId 上传ID + * @param partNumber 分片序号 + * @param partSize 分片大小 + * @param inputStream 分片数据流 + * @return 分片的ETag + */ + public String uploadPart(String filePath, String uploadId, int partNumber, long partSize, InputStream inputStream) + throws Exception; + + /** + * 完成分片上传 + * @param filePath 文件路径 + * @param uploadId 上传ID + * @return 文件的最终路径 + */ + public String completeMultipartUpload(String filePath, String uploadId, List partETags) + throws Exception; +} diff --git a/boyue-file/boyue-file-common/src/main/java/com/boyue/file/utils/FileOperateUtils.java b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/utils/FileOperateUtils.java new file mode 100644 index 0000000..d781289 --- /dev/null +++ b/boyue-file/boyue-file-common/src/main/java/com/boyue/file/utils/FileOperateUtils.java @@ -0,0 +1,255 @@ +package com.boyue.file.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; + +import org.springframework.http.MediaType; +import org.springframework.web.multipart.MultipartFile; + +import com.boyue.common.config.BoYueConfig; +import com.boyue.common.constant.CacheConstants; +import com.boyue.common.utils.CacheUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.file.FileUtils; +import com.boyue.common.utils.file.MimeTypeUtils; +import com.boyue.common.utils.sign.Md5Utils; +import com.boyue.common.utils.spring.SpringUtils; +import com.boyue.file.domain.SysFilePartETag; +import com.boyue.file.storage.StorageEntity; +import com.boyue.file.storage.StorageService; + +import jakarta.servlet.http.HttpServletResponse; + +/** + * 文件上传工具类 + * + * @author boyue + */ +public class FileOperateUtils { + + private static StorageService fileService = SpringUtils.getBean("file:strategy:" + BoYueConfig.getFileServer()); + + /** + * 以默认配置进行文件上传 + * + * @param file 上传的文件 + * @return 文件路径 + * @throws Exception + */ + public static final String upload(MultipartFile file) throws IOException { + return upload(file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); + } + + /** + * 以默认配置进行文件上传 + * + * @param file 上传的文件 + * @param allowedExtension 允许的扩展名 + * @return 文件路径 + * @throws Exception + */ + public static final String upload(MultipartFile file, String[] allowedExtension) + throws IOException { + try { + String md5 = Md5Utils.getMd5(file); + String pathForMd5 = getFilePathForMd5(md5); + if (StringUtils.isNotEmpty(pathForMd5)) { + return pathForMd5; + } + FileUtils.assertAllowed(file, allowedExtension); + String filePath = fileService.upload(file); + saveFilePathAndMd5(filePath, md5); + return filePath; + } catch (Exception e) { + throw new IOException(e.getMessage(), e); + } + } + + /** + * 根据文件路径上传 + * + * @param filePath 上传文件的路径 + * @param file 上传的文件 + * @return 文件名称 + * @throws IOException + */ + public static final String upload(String filePath, MultipartFile file) throws Exception { + return upload(filePath, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); + } + + /** + * 根据文件路径上传 + * + * @param filePath 上传文件的路径 + * @param file 上传的文件 + * @param allowedExtension 允许的扩展名 + * @return 访问链接 + * @throws IOException + */ + public static final String upload(String filePath, MultipartFile file, String[] allowedExtension) + throws IOException { + try { + String md5 = Md5Utils.getMd5(file); + String pathForMd5 = getFilePathForMd5(md5); + if (StringUtils.isNotEmpty(pathForMd5)) { + return pathForMd5; + } + FileUtils.assertAllowed(file, allowedExtension); + String url = fileService.upload(filePath, file); + saveFilePathAndMd5(url, md5); + return url; + } catch (Exception e) { + throw new IOException(e.getMessage(), e); + } + } + + /** + * 根据文件路径下载 + * + * @param fileUrl 下载文件路径 + * @param outputStream 需要输出到的输出流 + * @return 文件名称 + * @throws IOException + */ + public static final void downLoad(String filePath, OutputStream outputStream) throws Exception { + InputStream inputStream = fileService.downLoad(filePath); + FileUtils.writeBytes(inputStream, outputStream); + } + + /** + * 根据文件路径下载 + * + * @param filepath 下载文件路径 + * @param response 相应 + * @return 文件名称 + * @throws IOException + */ + public static final void downLoad(String filePath, HttpServletResponse response) throws Exception { + StorageEntity fileEntity = fileService.getFile(filePath); + InputStream inputStream = fileEntity.getInputStream(); + OutputStream outputStream = response.getOutputStream(); + FileUtils.setAttachmentResponseHeader(response, FileUtils.getName(fileEntity.getFilePath())); + response.setContentLengthLong(fileEntity.getByteCount()); + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + FileUtils.writeBytes(inputStream, outputStream); + } + + /** + * 根据文件路径删除 + * + * @param filePath 下载文件路径 + * @return 是否成功 + * @throws IOException + */ + public static final boolean deleteFile(String filePath) throws Exception { + deleteFileAndMd5ByFilePath(filePath); + return fileService.deleteFile(filePath); + } + + /** + * 根据md5获取文件的路径 + * + * @param md5 文件的md5 + * @return 文件路径 + */ + public static String getFilePathForMd5(String md5) { + return CacheUtils.get(CacheConstants.FILE_MD5_PATH_KEY, md5, String.class); + } + + /** + * 根据md5获取文件的路径 + * + * @param md5 文件的md5 + * @return 文件路径 + */ + public static String getMd5ForFilePath(String filePath) { + return CacheUtils.get(CacheConstants.FILE_PATH_MD5_KEY, filePath, String.class); + } + + /** + * 保存文件的md5 + * + * @param filePath 文件的路径 + * @param md5 文件的md5 + */ + public static void saveFilePathAndMd5(String filePath, String md5) { + CacheUtils.put(CacheConstants.FILE_MD5_PATH_KEY, md5, filePath); + CacheUtils.put(CacheConstants.FILE_PATH_MD5_KEY, filePath, md5); + } + + /** + * 根据md5删除文件的缓存信息 + * + * @param md5 文件的md5 + */ + public static void deleteFileAndMd5ByMd5(String md5) { + String filePathByMd5 = getFilePathForMd5(md5); + if (StringUtils.isNotEmpty(filePathByMd5)) { + CacheUtils.remove(CacheConstants.FILE_MD5_PATH_KEY, md5); + CacheUtils.remove(CacheConstants.FILE_PATH_MD5_KEY, filePathByMd5); + } + } + + /** + * 根据文件路径删除文件的缓存信息 + * + * @param filePath 文件的路径 + */ + public static void deleteFileAndMd5ByFilePath(String filePath) { + String md5ByFilePath = getMd5ForFilePath(filePath); + if (StringUtils.isNotEmpty(md5ByFilePath)) { + CacheUtils.remove(CacheConstants.FILE_PATH_MD5_KEY, filePath); + CacheUtils.remove(CacheConstants.FILE_MD5_PATH_KEY, md5ByFilePath); + } + } + + /** + * 获取文件的访问链接 + * + * @param filePath 文件路径 + * @return 访问链接 + * @throws Exception + */ + public static String getURL(String filePath) throws Exception { + return fileService.generateUrl(filePath); + } + + /** + * 初始化分片上传 + * + * @param filePath 文件路径 + * @return 返回uploadId + */ + public static String initMultipartUpload(String filePath) throws Exception { + return fileService.initMultipartUpload(filePath); + } + + /** + * 上传分片 + * + * @param filePath 文件路径 + * @param uploadId 上传ID + * @param partNumber 分片序号 + * @param partSize 分片大小 + * @param inputStream 分片数据流 + * @return 分片的ETag + */ + public static String uploadPart(String filePath, String uploadId, int partNumber, long partSize, + InputStream inputStream) throws Exception { + return fileService.uploadPart(filePath, uploadId, partNumber, partSize, inputStream); + } + + /** + * 完成分片上传 + * + * @param filePath 文件路径 + * @param uploadId 上传ID + * @return 文件的最终路径 + */ + public static String completeMultipartUpload(String filePath, String uploadId, List partETags) + throws Exception { + return fileService.completeMultipartUpload(filePath, uploadId, partETags); + } +} diff --git a/boyue-file/boyue-file-common/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/boyue-file/boyue-file-common/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000..c803b19 --- /dev/null +++ b/boyue-file/boyue-file-common/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,19 @@ +{ + "properties": [ + { + "name": "local.enable", + "type": "java.lang.Boolean", + "description": "是否开启local" + }, + { + "name": "local.primary", + "type": "java.lang.String", + "description": "默认存储名称" + }, + { + "name": "local.client", + "type": "java.util.Map", + "description": "储存桶" + } + ] +} \ No newline at end of file diff --git a/boyue-file/boyue-file-common/src/main/resources/mapper/file/SysFileInfoMapper.xml b/boyue-file/boyue-file-common/src/main/resources/mapper/file/SysFileInfoMapper.xml new file mode 100644 index 0000000..990824c --- /dev/null +++ b/boyue-file/boyue-file-common/src/main/resources/mapper/file/SysFileInfoMapper.xml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + select + sfi.file_id, + sfi.file_name, + sfi.file_path, + sfi.storage_type, + sfi.file_type, + sfi.file_size, + sfi.md5, + sfi.create_by, + sfi.create_time, + sfi.update_by, + sfi.update_time, + sfi.remark, + sfi.del_flag + from sys_file_info sfi + + + + + + + + insert into sys_file_info + + file_name, + file_path, + storage_type, + file_type, + file_size, + md5, + create_by, + create_time, + update_by, + update_time, + remark, + del_flag, + + + #{fileName}, + #{filePath}, + #{storageType}, + #{fileType}, + #{fileSize}, + #{md5}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + #{delFlag}, + + + + + update sys_file_info + + file_name = #{fileName}, + file_path = #{filePath}, + storage_type = #{storageType}, + file_type = #{fileType}, + file_size = #{fileSize}, + md5 = #{md5}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + del_flag = #{delFlag}, + + where sys_file_info.file_id = #{fileId} + + + + delete from sys_file_info where file_id = #{fileId} + + + + delete from sys_file_info where file_id in + + #{fileId} + + + \ No newline at end of file diff --git a/boyue-file/boyue-file-minio/pom.xml b/boyue-file/boyue-file-minio/pom.xml new file mode 100644 index 0000000..a0ee039 --- /dev/null +++ b/boyue-file/boyue-file-minio/pom.xml @@ -0,0 +1,33 @@ + + + + boyue-file + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-file-minio + + + 中间件 + + + + + + + com.boyue + boyue-file-common + + + + + io.minio + minio + + + + + diff --git a/boyue-file/boyue-file-minio/src/main/java/com/boyue/file/minio/config/MinioBucketProperties.java b/boyue-file/boyue-file-minio/src/main/java/com/boyue/file/minio/config/MinioBucketProperties.java new file mode 100644 index 0000000..ad81155 --- /dev/null +++ b/boyue-file/boyue-file-minio/src/main/java/com/boyue/file/minio/config/MinioBucketProperties.java @@ -0,0 +1,12 @@ +package com.boyue.file.minio.config; + +import lombok.Data; + +@Data +public class MinioBucketProperties { + private String permission; + private String url; + private String accessKey; + private String secretKey; + private String bucketName; +} diff --git a/boyue-file/boyue-file-minio/src/main/java/com/boyue/file/minio/config/MinioManagement.java b/boyue-file/boyue-file-minio/src/main/java/com/boyue/file/minio/config/MinioManagement.java new file mode 100644 index 0000000..edcd566 --- /dev/null +++ b/boyue-file/boyue-file-minio/src/main/java/com/boyue/file/minio/config/MinioManagement.java @@ -0,0 +1,117 @@ +package com.boyue.file.minio.config; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.http.impl.io.EmptyInputStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import com.boyue.common.config.BoYueConfig; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.file.FileUtils; +import com.boyue.file.minio.domain.MinioBucket; +import com.boyue.file.storage.StorageManagement; + +import io.minio.BucketExistsArgs; +import io.minio.MinioClient; +import io.minio.PutObjectArgs; + +@Configuration("minio") +@ConditionalOnProperty(prefix = "minio", name = { "enable" }, havingValue = "true", matchIfMissing = false) +@ConfigurationProperties("minio") +public class MinioManagement implements StorageManagement { + private static final Logger logger = LoggerFactory.getLogger(MinioManagement.class); + private Map client; + private String primary; + private Map targetMinioBucket = new HashMap<>(); + private MinioBucket primaryBucket; + + @Override + public void afterPropertiesSet() throws Exception { + if (client == null || client.isEmpty()) { + throw new RuntimeException("Client properties cannot be null or empty"); + } + client.forEach((name, props) -> { + try { + targetMinioBucket.put(name, createMinioClient(name, props)); + } catch (Exception e) { + logger.error("Failed to create MinIO client for {}: {}", name, e.getMessage(), e); + } + }); + + if (targetMinioBucket.get(primary) == null) { + throw new RuntimeException("Primary client " + primary + " does not exist"); + } + primaryBucket = targetMinioBucket.get(primary); + } + + private void validateMinioBucket(MinioBucket minioBucket) { + BucketExistsArgs bucketExistArgs = BucketExistsArgs.builder().bucket(minioBucket.getBucketName()).build(); + boolean b = false; + try { + b = minioBucket.getClient().bucketExists(bucketExistArgs); + PutObjectArgs putObjectArgs = PutObjectArgs.builder() + .object(FileUtils.getRelativePath(BoYueConfig.getProfile()) + "/") + .stream(EmptyInputStream.nullInputStream(), 0, -1).bucket(minioBucket.getBucketName()).build(); + minioBucket.getClient().putObject(putObjectArgs); + } catch (Exception e) { + logger.error("数据桶:{} - 链接失败", minioBucket.getName()); + throw new RuntimeException(e.getMessage()); + } + if (!b) { + throw new RuntimeException("Bucket " + minioBucket.getBucketName() + " does not exist"); + } + } + + private MinioBucket createMinioClient(String name, MinioBucketProperties props) { + MinioClient client; + if (StringUtils.isEmpty(props.getAccessKey())) { + client = MinioClient.builder() + .endpoint(props.getUrl()) + .build(); + } else { + client = MinioClient.builder() + .endpoint(props.getUrl()) + .credentials(props.getAccessKey(), props.getSecretKey()) + .build(); + } + MinioBucket minioBucket = MinioBucket.builder() + .client(client) + .bucketName(props.getBucketName()) + .permission(props.getPermission()) + .url(props.getUrl()) + .build(); + validateMinioBucket(minioBucket); + logger.info("数据桶:{} - 链接成功", name); + return minioBucket; + } + + @Override + public MinioBucket getBucket(String clent) { + return targetMinioBucket.get(clent); + } + + public MinioBucket getPrimaryBucket() { + return this.primaryBucket; + } + + public Map getClient() { + return client; + } + + public void setClient(Map client) { + this.client = client; + } + + public String getPrimary() { + return primary; + } + + public void setPrimary(String primary) { + this.primary = primary; + } +} diff --git a/boyue-file/boyue-file-minio/src/main/java/com/boyue/file/minio/domain/MinioBucket.java b/boyue-file/boyue-file-minio/src/main/java/com/boyue/file/minio/domain/MinioBucket.java new file mode 100644 index 0000000..880acae --- /dev/null +++ b/boyue-file/boyue-file-minio/src/main/java/com/boyue/file/minio/domain/MinioBucket.java @@ -0,0 +1,224 @@ +package com.boyue.file.minio.domain; + +import java.io.InputStream; +import java.net.URI; +import java.net.URL; +import java.util.Comparator; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; + +import org.springframework.web.multipart.MultipartFile; + +import com.boyue.common.core.text.Convert; +import com.boyue.file.domain.SysFilePartETag; +import com.boyue.file.minio.exception.MinioClientErrorException; +import com.boyue.file.storage.StorageBucket; + +import io.minio.ComposeObjectArgs; +import io.minio.ComposeSource; +import io.minio.GetObjectArgs; +import io.minio.GetObjectResponse; +import io.minio.GetPresignedObjectUrlArgs; +import io.minio.MinioClient; +import io.minio.PutObjectArgs; +import io.minio.RemoveObjectArgs; +import io.minio.http.Method; +import lombok.Builder; +import lombok.extern.slf4j.Slf4j; + +@Builder +@Slf4j +public class MinioBucket implements StorageBucket { + + private String url; + private String permission; + private MinioClient client; + private String bucketName; + + private static final ConcurrentHashMap uploadingParts = new ConcurrentHashMap<>(); + + @Override + public void put(String filePath, MultipartFile file) { + try (InputStream inputStream = file.getInputStream()) { + PutObjectArgs putObjectArgs = PutObjectArgs.builder() + .contentType(file.getContentType()) + .stream(inputStream, file.getSize(), -1) + .bucket(bucketName) + .object(filePath) + .build(); + client.putObject(putObjectArgs); + } catch (Exception e) { + throw new MinioClientErrorException(e.getMessage()); + } + } + + @Override + public void remove(String filePath) throws Exception { + RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder() + .object(filePath) + .bucket(bucketName) + .build(); + client.removeObject(removeObjectArgs); + } + + @Override + public MinioEntityVO get(String filePath) throws Exception { + GetObjectArgs getObjectArgs = GetObjectArgs.builder() + .object(filePath) + .bucket(bucketName) + .build(); + GetObjectResponse response = client.getObject(getObjectArgs); + MinioEntityVO minioFileVO = new MinioEntityVO(); + minioFileVO.setInputStream(response); + minioFileVO.setByteCount(Convert.toLong(response.headers().get("Content-Length"), null)); + minioFileVO.setFilePath(filePath); + minioFileVO.setObject(response.object()); + minioFileVO.setRegion(response.region()); + minioFileVO.setBuket(response.bucket()); + minioFileVO.setHeaders(response.headers()); + return minioFileVO; + } + + @Override + public URL generatePresignedUrl(String filePath, int expireTime) throws Exception { + GetPresignedObjectUrlArgs request = GetPresignedObjectUrlArgs.builder() + .method(Method.GET) + .bucket(bucketName) + .object(filePath) + .expiry(expireTime, TimeUnit.SECONDS) + .build(); + String urlString = client.getPresignedObjectUrl(request); + return URI.create(urlString).toURL(); + } + + @Override + public URL generatePublicURL(String filePath) throws Exception { + StringBuilder sb = new StringBuilder(); + sb.append(getUrl()) + .append("/").append(getBucketName()) + .append("/").append(filePath.replace("\\", "/")); + return URI.create(sb.toString()).toURL(); + } + + /** + * 初始化分片上传 + */ + public String initMultipartUpload(String filePath) throws Exception { + try { + String uploadId = UUID.randomUUID().toString().replace("-", "").toUpperCase(); + return uploadId; + } catch (Exception e) { + log.error("初始化失败: 文件={}, 错误={}", filePath, e.getMessage()); + throw new MinioClientErrorException("初始化失败", e); + } + } + + /** + * 上传单个分片 + */ + public SysFilePartETag uploadPart(String filePath, String uploadId, int partNumber, long partSize, + InputStream inputStream) + throws Exception { + // 生成唯一的上传键 + String uploadKey = String.format("%s-%s-%d", filePath, uploadId, partNumber); + AtomicBoolean isUploading = uploadingParts.computeIfAbsent(uploadKey, k -> new AtomicBoolean(false)); + // 使用 CAS 检查是否已经在上传中 + if (!isUploading.compareAndSet(false, true)) { + throw new MinioClientErrorException("分片正在上传中: " + uploadKey); + } + try { + // 构建分片存储路径 + String partPath = String.format("%s.%s.part.%d", filePath, uploadId, partNumber); + // 构造上传请求参数 + PutObjectArgs args = PutObjectArgs.builder() + .bucket(bucketName) + .object(partPath) + .stream(inputStream, partSize, -1) + .build(); + // 执行上传操作并获取 ETag + String etag = client.putObject(args).etag().replace("\"", "").toUpperCase(); + return new SysFilePartETag(partNumber, etag); + } catch (Exception e) { + log.error("分片上传失败: 文件={}, 分片={}, 错误={}", filePath, partNumber, e.getMessage()); + throw new MinioClientErrorException("上传分片失败", e); + } finally { + isUploading.set(false);// 标记为上传完成,并清理状态 + uploadingParts.remove(uploadKey); + } + } + + /** + * 完成分片上传并合并文件 + */ + public String completeMultipartUpload(String filePath, String uploadId, List filePartETags) + throws Exception { + if (filePartETags == null || filePartETags.isEmpty()) { + throw new IllegalArgumentException("分片信息不能为空"); + } + + // 按分片序号排序 + List partPaths = filePartETags.stream() + .sorted(Comparator.comparingInt(p -> p.getPartNumber())) + .map(part -> String.format("%s.%s.part.%d", filePath, uploadId, part.getPartNumber())) + .collect(Collectors.toList()); + + List sources = partPaths.stream() + .map(path -> ComposeSource.builder() + .bucket(bucketName) + .object(path) + .build()) + .toList(); + + client.composeObject(ComposeObjectArgs.builder() + .bucket(bucketName) + .object(filePath) + .sources(sources) + .build()); + + for (String partPath : partPaths) { + try { + client.removeObject(RemoveObjectArgs.builder() + .bucket(bucketName) + .object(partPath) + .build()); + } catch (Exception e) { + log.warn("清理分片失败: {}", e.getMessage()); + } + } + log.info("分片合并完成: 文件={}, uploadId={}, 分片数={}", filePath, uploadId, filePartETags.size()); + return filePath; + } + + public String getName() { + return bucketName; + } + + public MinioClient getClient() { + return client; + } + + public String getBucketName() { + return bucketName; + } + + public void setBucketName(String bucketName) { + this.bucketName = bucketName; + } + + public void setClient(MinioClient client) { + this.client = client; + } + + public String getPermission() { + return permission; + } + + public String getUrl() { + return url; + } + +} diff --git a/boyue-file/boyue-file-minio/src/main/java/com/boyue/file/minio/domain/MinioEntityVO.java b/boyue-file/boyue-file-minio/src/main/java/com/boyue/file/minio/domain/MinioEntityVO.java new file mode 100644 index 0000000..76e0c6d --- /dev/null +++ b/boyue-file/boyue-file-minio/src/main/java/com/boyue/file/minio/domain/MinioEntityVO.java @@ -0,0 +1,45 @@ +package com.boyue.file.minio.domain; + +import com.boyue.file.storage.StorageEntity; + +import okhttp3.Headers; + +public class MinioEntityVO extends StorageEntity { + private String object; + private Headers headers; + private String buket; + private String region; + + public String getObject() { + return object; + } + + public void setObject(String object) { + this.object = object; + } + + public Headers getHeaders() { + return headers; + } + + public void setHeaders(Headers headers) { + this.headers = headers; + } + + public String getBuket() { + return buket; + } + + public void setBuket(String buket) { + this.buket = buket; + } + + public String getRegion() { + return region; + } + + public void setRegion(String region) { + this.region = region; + } + +} diff --git a/boyue-file/boyue-file-minio/src/main/java/com/boyue/file/minio/exception/MinioClientErrorException.java b/boyue-file/boyue-file-minio/src/main/java/com/boyue/file/minio/exception/MinioClientErrorException.java new file mode 100644 index 0000000..4907f39 --- /dev/null +++ b/boyue-file/boyue-file-minio/src/main/java/com/boyue/file/minio/exception/MinioClientErrorException.java @@ -0,0 +1,16 @@ +package com.boyue.file.minio.exception; + +/** + * 当与MinIO客户端交互过程中发生错误时抛出此异常。 + */ +public class MinioClientErrorException extends RuntimeException { + + public MinioClientErrorException(String msg) { + super(msg); + } + + public MinioClientErrorException(String message, Throwable cause) { + super(message, cause); + } +} + diff --git a/boyue-file/boyue-file-minio/src/main/java/com/boyue/file/minio/exception/MinioClientNotFundException.java b/boyue-file/boyue-file-minio/src/main/java/com/boyue/file/minio/exception/MinioClientNotFundException.java new file mode 100644 index 0000000..096d617 --- /dev/null +++ b/boyue-file/boyue-file-minio/src/main/java/com/boyue/file/minio/exception/MinioClientNotFundException.java @@ -0,0 +1,15 @@ +package com.boyue.file.minio.exception; + +/** + * 当尝试获取MinIO客户端实例但未能找到相应的配置或客户端实例时抛出此异常。 + */ +public class MinioClientNotFundException extends RuntimeException { + + public MinioClientNotFundException(String msg) { + super(msg); + } + + public MinioClientNotFundException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/boyue-file/boyue-file-minio/src/main/java/com/boyue/file/minio/service/MinioFileService.java b/boyue-file/boyue-file-minio/src/main/java/com/boyue/file/minio/service/MinioFileService.java new file mode 100644 index 0000000..65e6d78 --- /dev/null +++ b/boyue-file/boyue-file-minio/src/main/java/com/boyue/file/minio/service/MinioFileService.java @@ -0,0 +1,87 @@ +package com.boyue.file.minio.service; + +import java.io.InputStream; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import com.boyue.file.domain.SysFilePartETag; +import com.boyue.file.minio.config.MinioManagement; +import com.boyue.file.minio.domain.MinioBucket; +import com.boyue.file.storage.StorageEntity; +import com.boyue.file.storage.StorageService; + +/** + * Minio文件操作实现类 + */ +@Component("file:strategy:minio") +@ConditionalOnProperty(prefix = "minio", name = { "enable" }, havingValue = "true", matchIfMissing = false) +public class MinioFileService implements StorageService { + + @Autowired + private MinioManagement minioConfig; + + @Override + public String upload(String filePath, MultipartFile file) throws Exception { + MinioBucket minioBucket = minioConfig.getPrimaryBucket(); + minioBucket.put(filePath, file); + return generateUrl(filePath); + } + + @Override + public InputStream downLoad(String filePath) throws Exception { + MinioBucket minioBucket = minioConfig.getPrimaryBucket(); + return minioBucket.get(filePath).getInputStream(); + } + + @Override + public StorageEntity getFile(String filePath) throws Exception { + MinioBucket minioBucket = minioConfig.getPrimaryBucket(); + return minioBucket.get(filePath); + } + + @Override + public boolean deleteFile(String filePath) throws Exception { + MinioBucket minioBucket = minioConfig.getPrimaryBucket(); + minioBucket.remove(filePath); + return true; + } + + @Override + public String generateUrl(String filePath) throws Exception { + MinioBucket minioBucket = minioConfig.getPrimaryBucket(); + if ("public".equals(minioBucket.getPermission())) { + return minioBucket.generatePublicURL(filePath).toString(); + } else { + return minioBucket.generatePresignedUrl(filePath, 3600).toString(); + } + } + + @Override + public String initMultipartUpload(String filePath) throws Exception { + MinioBucket minioBucket = minioConfig.getPrimaryBucket(); + return minioBucket.initMultipartUpload(filePath); + } + + @Override + public String uploadPart(String filePath, String uploadId, int partNumber, long partSize, InputStream inputStream) + throws Exception { + MinioBucket minioBucket = minioConfig.getPrimaryBucket(); + return minioBucket.uploadPart(filePath, uploadId, partNumber, partSize, inputStream).getETag(); + } + + @Override + public String completeMultipartUpload(String filePath, String uploadId, List partETags) + throws Exception { + if (partETags == null || partETags.isEmpty()) { + throw new IllegalArgumentException("分片标识列表不能为空"); + } + + MinioBucket minioBucket = minioConfig.getPrimaryBucket(); + return minioBucket.completeMultipartUpload(filePath, uploadId, partETags); + } + +} \ No newline at end of file diff --git a/boyue-file/boyue-file-minio/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/boyue-file/boyue-file-minio/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000..445dcfa --- /dev/null +++ b/boyue-file/boyue-file-minio/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,19 @@ +{ + "properties": [ + { + "name": "minio.enable", + "type": "java.lang.Boolean", + "description": "是否开启minio" + }, + { + "name": "minio.primary", + "type": "java.lang.String", + "description": "默认存储名称" + }, + { + "name": "minio.client", + "type": "java.util.Map", + "description": "储存桶" + } + ] +} \ No newline at end of file diff --git a/boyue-file/boyue-file-oss-alibaba/pom.xml b/boyue-file/boyue-file-oss-alibaba/pom.xml new file mode 100644 index 0000000..cf5b12c --- /dev/null +++ b/boyue-file/boyue-file-oss-alibaba/pom.xml @@ -0,0 +1,28 @@ + + + 4.0.0 + + boyue-file + com.boyue + 3.8.9-G + + com.boyue + boyue-file-oss-alibaba + oss中间件 + + + + + + com.boyue + boyue-file-common + + + + + com.aliyun.oss + aliyun-sdk-oss + + + diff --git a/boyue-file/boyue-file-oss-alibaba/src/main/java/com/boyue/file/oss/alibaba/config/AliOssBucketProperties.java b/boyue-file/boyue-file-oss-alibaba/src/main/java/com/boyue/file/oss/alibaba/config/AliOssBucketProperties.java new file mode 100644 index 0000000..4bfefc3 --- /dev/null +++ b/boyue-file/boyue-file-oss-alibaba/src/main/java/com/boyue/file/oss/alibaba/config/AliOssBucketProperties.java @@ -0,0 +1,12 @@ +package com.boyue.file.oss.alibaba.config; + +import lombok.Data; + +@Data +public class AliOssBucketProperties { + private String permission; + private String endpoint; + private String accessKeyId; + private String accessKeySecret; + private String bucketName; +} \ No newline at end of file diff --git a/boyue-file/boyue-file-oss-alibaba/src/main/java/com/boyue/file/oss/alibaba/config/AliOssManagement.java b/boyue-file/boyue-file-oss-alibaba/src/main/java/com/boyue/file/oss/alibaba/config/AliOssManagement.java new file mode 100644 index 0000000..919f618 --- /dev/null +++ b/boyue-file/boyue-file-oss-alibaba/src/main/java/com/boyue/file/oss/alibaba/config/AliOssManagement.java @@ -0,0 +1,113 @@ +package com.boyue.file.oss.alibaba.config; + +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import com.aliyun.oss.ClientException; +import com.aliyun.oss.OSS; +import com.aliyun.oss.OSSClientBuilder; +import com.aliyun.oss.OSSException; +import com.boyue.file.oss.alibaba.domain.AliOssBucket; +import com.boyue.file.storage.StorageManagement; + +/** + * 配置类用于管理阿里云OSS客户端实例及其相关属性。 + */ +@Configuration("oss") +@ConditionalOnProperty(prefix = "oss", name = "enable", havingValue = "true", matchIfMissing = false) +@ConfigurationProperties(prefix = "oss") +public class AliOssManagement implements StorageManagement { + private static final Logger logger = LoggerFactory.getLogger(AliOssManagement.class); + private Map client; + private String primary; + private Map targetAliOssBucket = new HashMap<>(); + private AliOssBucket primaryBucket; + + @Override + public void afterPropertiesSet() throws Exception { + if (client == null || client.isEmpty()) { + throw new RuntimeException("Client properties cannot be null or empty"); + } + + client.forEach((name, props) -> { + try { + AliOssBucket aliOssBucket = createOssClient(name, props); + targetAliOssBucket.put(name, aliOssBucket); + } catch (Exception e) { + logger.error("Failed to create OSS client for {}: {}", name, e.getMessage(), e); + } + }); + + if (targetAliOssBucket.get(primary) == null) { + throw new RuntimeException("Primary client " + primary + " does not exist"); + } + primaryBucket = targetAliOssBucket.get(primary); + } + + private void validateOssBucket(AliOssBucket aliOssBucket) { + OSS ossClient = aliOssBucket.getOssClient(); + String bucketName = aliOssBucket.getBucketName(); + try { + if (!ossClient.doesBucketExist(bucketName)) { + throw new RuntimeException("Bucket " + bucketName + " does not exist"); + } + } catch (OSSException oe) { + logger.error("OSSException: " + oe.getMessage(), oe); + throw new RuntimeException("OSS error: " + oe.getMessage()); + } catch (ClientException ce) { + logger.error("ClientException: " + ce.getMessage(), ce); + throw new RuntimeException("Client error: " + ce.getMessage()); + } catch (Exception e) { + logger.error("Exception: " + e.getMessage(), e); + throw new RuntimeException("Error validating OSS bucket: " + e.getMessage()); + } + } + + private AliOssBucket createOssClient(String name, AliOssBucketProperties props) { + if (props == null || props.getEndpoint() == null || props.getAccessKeyId() == null || + props.getAccessKeySecret() == null || props.getBucketName() == null) { + throw new IllegalArgumentException("AliOssProperties or its required fields cannot be null"); + } + + OSS client = new OSSClientBuilder().build(props.getEndpoint(), props.getAccessKeyId(), + props.getAccessKeySecret()); + AliOssBucket ossBucket = AliOssBucket.builder() + .ossClient(client) + .bucketName(props.getBucketName()) + .build(); + validateOssBucket(ossBucket); + logger.info("数据桶:{} - 链接成功", name); + return ossBucket; + } + + public AliOssBucket getPrimaryBucket() { + return this.primaryBucket; + } + + @Override + public AliOssBucket getBucket(String client) { + return targetAliOssBucket.get(client); + } + + public Map getClient() { + return client; + } + + public void setClient(Map client) { + this.client = client; + } + + public String getPrimary() { + return primary; + } + + public void setPrimary(String primary) { + this.primary = primary; + } +} diff --git a/boyue-file/boyue-file-oss-alibaba/src/main/java/com/boyue/file/oss/alibaba/domain/AliOssBucket.java b/boyue-file/boyue-file-oss-alibaba/src/main/java/com/boyue/file/oss/alibaba/domain/AliOssBucket.java new file mode 100644 index 0000000..f7be07d --- /dev/null +++ b/boyue-file/boyue-file-oss-alibaba/src/main/java/com/boyue/file/oss/alibaba/domain/AliOssBucket.java @@ -0,0 +1,176 @@ +package com.boyue.file.oss.alibaba.domain; + +import java.io.InputStream; +import java.net.URI; +import java.net.URL; +import java.util.Comparator; +import java.util.Date; +import java.util.List; + +import org.springframework.web.multipart.MultipartFile; + +import com.aliyun.oss.OSS; +import com.aliyun.oss.ServiceException; +import com.aliyun.oss.model.AbortMultipartUploadRequest; +import com.aliyun.oss.model.CompleteMultipartUploadRequest; +import com.aliyun.oss.model.GeneratePresignedUrlRequest; +import com.aliyun.oss.model.GetObjectRequest; +import com.aliyun.oss.model.InitiateMultipartUploadRequest; +import com.aliyun.oss.model.OSSObject; +import com.aliyun.oss.model.ObjectMetadata; +import com.aliyun.oss.model.PartETag; +import com.aliyun.oss.model.PutObjectRequest; +import com.aliyun.oss.model.UploadPartRequest; +import com.boyue.file.domain.SysFilePartETag; +import com.boyue.file.oss.alibaba.exception.AliOssClientErrorException; +import com.boyue.file.storage.StorageBucket; + +import lombok.Builder; +import lombok.extern.slf4j.Slf4j; + +@Builder +@Slf4j +public class AliOssBucket implements StorageBucket { + + private String bucketName; + private OSS ossClient; + private String permission; + private String endpoint; + + @Override + public void put(String filePath, MultipartFile file) throws Exception { + try { + InputStream inputStream = file.getInputStream(); + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentType(file.getContentType()); + metadata.setContentLength(inputStream.available()); + PutObjectRequest putRequest = new PutObjectRequest(bucketName, filePath, inputStream, metadata); + this.ossClient.putObject(putRequest); + } catch (Exception e) { + log.error("Error uploading file to OSS: {}", e.getMessage(), e); + throw new AliOssClientErrorException("Error uploading file to OSS: " + e.getMessage(), e); + } + } + + @Override + public void remove(String filePath) throws Exception { + ossClient.deleteObject(bucketName, filePath); + } + + @Override + public AliOssEntityVO get(String filePath) throws Exception { + GetObjectRequest request = new GetObjectRequest(this.bucketName, filePath); + OSSObject ossObject = this.ossClient.getObject(request); + if (ossObject == null) { + throw new Exception("Failed to retrieve object from OSS."); + } + AliOssEntityVO fileVO = new AliOssEntityVO(); + fileVO.setInputStream(ossObject.getObjectContent()); + fileVO.setKey(ossObject.getKey()); + fileVO.setBucketName(ossObject.getBucketName()); + fileVO.setByteCount(ossObject.getObjectMetadata().getContentLength()); + fileVO.setFilePath(filePath); + return fileVO; + } + + @Override + public URL generatePresignedUrl(String filePath, int expireTime) { + GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, filePath); + Date expiration = new Date(System.currentTimeMillis() + expireTime * 1000); // 设置过期时间为1小时 + request.setExpiration(expiration); + return ossClient.generatePresignedUrl(request); + } + + @Override + public URL generatePublicURL(String filePath) throws Exception { + StringBuilder sb = new StringBuilder(); + sb.append("https://").append(getBucketName()) + .append(".").append(getEndpoint()) + .append("/").append(filePath.replace("\\", "/")); + return URI.create(sb.toString()).toURL(); + } + + /** + * 初始化分片上传 + */ + public String initMultipartUpload(String filePath) throws Exception { + InitiateMultipartUploadRequest initRequest = new InitiateMultipartUploadRequest(bucketName, filePath); + String uploadId = ossClient.initiateMultipartUpload(initRequest).getUploadId(); + return uploadId; + } + + /** + * 上传单个分片 + */ + public SysFilePartETag uploadPart(String filePath, String uploadId, int partNumber, long partSize, + InputStream inputStream) + throws Exception { + UploadPartRequest uploadPartRequest = new UploadPartRequest(); + uploadPartRequest.setBucketName(bucketName); + uploadPartRequest.setKey(filePath); + uploadPartRequest.setUploadId(uploadId); + uploadPartRequest.setInputStream(inputStream); + uploadPartRequest.setPartSize(partSize); + uploadPartRequest.setPartNumber(partNumber); + PartETag partETag = ossClient.uploadPart(uploadPartRequest).getPartETag(); + return new SysFilePartETag(partETag.getPartNumber(), partETag.getETag(), partETag.getPartSize(), + partETag.getPartCRC()); + } + + /** + * 完成分片上传 + */ + public String completeMultipartUpload(String filePath, String uploadId, List sysFilePartETags) + throws Exception { + if (sysFilePartETags == null || sysFilePartETags.isEmpty()) { + throw new ServiceException("分片ETag列表不能为空"); + } + List partETags = sysFilePartETags.stream() + .map(part -> new PartETag(part.getPartNumber(), part.getETag(), part.getPartSize(), part.getPartCRC())) + .toList(); + // 确保分片按顺序排列 + partETags.sort(Comparator.comparingInt(PartETag::getPartNumber)); + try { + CompleteMultipartUploadRequest completeRequest = new CompleteMultipartUploadRequest(bucketName, filePath, + uploadId, partETags); + ossClient.completeMultipartUpload(completeRequest); + log.info("分片上传已完成并合并: 文件={}, uploadId={}, 分片数={}", + filePath, uploadId, partETags.size()); + return filePath; + } catch (Exception e) { + log.error("合并分片失败: 文件={}, uploadId={}, 错误={}", + filePath, uploadId, e.getMessage()); + try { + ossClient.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, filePath, uploadId)); + } catch (Exception abortEx) { + log.error("取消分片上传失败: {}", abortEx.getMessage()); + } + throw new AliOssClientErrorException("合并分片失败: " + e.getMessage(), e); + } + } + + public OSS getOssClient() { + return ossClient; + } + + public void setOssClient(OSS ossClient) { + this.ossClient = ossClient; + } + + public String getBucketName() { + return bucketName; + } + + public void setBucketName(String bucketName) { + this.bucketName = bucketName; + } + + public String getPermission() { + return permission; + } + + public String getEndpoint() { + return endpoint; + } + +} \ No newline at end of file diff --git a/boyue-file/boyue-file-oss-alibaba/src/main/java/com/boyue/file/oss/alibaba/domain/AliOssEntityVO.java b/boyue-file/boyue-file-oss-alibaba/src/main/java/com/boyue/file/oss/alibaba/domain/AliOssEntityVO.java new file mode 100644 index 0000000..c3a9faa --- /dev/null +++ b/boyue-file/boyue-file-oss-alibaba/src/main/java/com/boyue/file/oss/alibaba/domain/AliOssEntityVO.java @@ -0,0 +1,34 @@ +package com.boyue.file.oss.alibaba.domain; + +import com.aliyun.oss.model.ObjectMetadata; +import com.boyue.file.storage.StorageEntity; + +public class AliOssEntityVO extends StorageEntity { + private String key; + private String bucketName; + private ObjectMetadata metadata; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getBucketName() { + return bucketName; + } + + public void setBucketName(String bucketName) { + this.bucketName = bucketName; + } + + public ObjectMetadata getMetadata() { + return metadata; + } + + public void setMetadata(ObjectMetadata metadata) { + this.metadata = metadata; + } +} \ No newline at end of file diff --git a/boyue-file/boyue-file-oss-alibaba/src/main/java/com/boyue/file/oss/alibaba/exception/AliOssClientErrorException.java b/boyue-file/boyue-file-oss-alibaba/src/main/java/com/boyue/file/oss/alibaba/exception/AliOssClientErrorException.java new file mode 100644 index 0000000..18918c9 --- /dev/null +++ b/boyue-file/boyue-file-oss-alibaba/src/main/java/com/boyue/file/oss/alibaba/exception/AliOssClientErrorException.java @@ -0,0 +1,11 @@ +package com.boyue.file.oss.alibaba.exception; + +public class AliOssClientErrorException extends RuntimeException{ + public AliOssClientErrorException(String msg){ + super(msg); + } + + public AliOssClientErrorException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/boyue-file/boyue-file-oss-alibaba/src/main/java/com/boyue/file/oss/alibaba/exception/AliOssClientNotFundException.java b/boyue-file/boyue-file-oss-alibaba/src/main/java/com/boyue/file/oss/alibaba/exception/AliOssClientNotFundException.java new file mode 100644 index 0000000..35764fb --- /dev/null +++ b/boyue-file/boyue-file-oss-alibaba/src/main/java/com/boyue/file/oss/alibaba/exception/AliOssClientNotFundException.java @@ -0,0 +1,27 @@ +package com.boyue.file.oss.alibaba.exception; + +/** + * 当尝试获取阿里云OSS客户端实例但未能找到相应的配置或客户端实例时抛出此异常。 + * 此异常表明系统中存在配置问题或者客户端初始化失败的问题。 + */ +public class AliOssClientNotFundException extends RuntimeException { + + /** + * 使用指定的详细信息创建一个新的 {@code AliOssClientNotFundException} 实例。 + * + * @param msg 描述异常原因的信息。 + */ + public AliOssClientNotFundException(String msg) { + super(msg); + } + + /** + * 使用指定的详细信息和导致此异常的原因创建一个新的 {@code AliOssClientNotFundException} 实例。 + * + * @param message 描述异常原因的信息。 + * @param cause 导致此异常的根本原因。 + */ + public AliOssClientNotFundException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/boyue-file/boyue-file-oss-alibaba/src/main/java/com/boyue/file/oss/alibaba/service/AliOssFileService.java b/boyue-file/boyue-file-oss-alibaba/src/main/java/com/boyue/file/oss/alibaba/service/AliOssFileService.java new file mode 100644 index 0000000..7c9fb61 --- /dev/null +++ b/boyue-file/boyue-file-oss-alibaba/src/main/java/com/boyue/file/oss/alibaba/service/AliOssFileService.java @@ -0,0 +1,93 @@ +package com.boyue.file.oss.alibaba.service; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import com.boyue.file.domain.SysFilePartETag; +import com.boyue.file.oss.alibaba.config.AliOssManagement; +import com.boyue.file.oss.alibaba.domain.AliOssBucket; +import com.boyue.file.storage.StorageEntity; +import com.boyue.file.storage.StorageService; + +/** + * oss文件操作实现类 + */ +@Component("file:strategy:oss") +@ConditionalOnProperty(prefix = "oss", name = { "enable" }, havingValue = "true", matchIfMissing = false) +public class AliOssFileService implements StorageService { + + @Autowired + private AliOssManagement aliOssConfig; + + @Override + public String upload(String filePath, MultipartFile file) throws Exception { + AliOssBucket aliOssBucket = aliOssConfig.getPrimaryBucket(); + aliOssBucket.put(filePath, file); + return generateUrl(filePath); + } + + @Override + public InputStream downLoad(String filePath) throws Exception { + AliOssBucket aliOssBucket = aliOssConfig.getPrimaryBucket(); + return aliOssBucket.get(filePath).getInputStream(); + } + + @Override + public StorageEntity getFile(String filePath) throws Exception { + AliOssBucket aliOssBucket = aliOssConfig.getPrimaryBucket(); + return aliOssBucket.get(filePath); + }; + + @Override + public boolean deleteFile(String filePath) throws Exception { + AliOssBucket aliOssBucket = aliOssConfig.getPrimaryBucket(); + aliOssBucket.remove(filePath); + return true; + } + + @Override + public String generateUrl(String filePath) throws Exception { + AliOssBucket aliOssBucket = aliOssConfig.getPrimaryBucket(); + if ("public".equals(aliOssBucket.getPermission())) { + return aliOssBucket.generatePublicURL(filePath).toString(); + } else { + return aliOssBucket.generatePresignedUrl(filePath, 3600).toString(); + } + } + + @Override + public String initMultipartUpload(String filePath) throws Exception { + return aliOssConfig.getPrimaryBucket().initMultipartUpload(filePath); + } + + @Override + public String uploadPart(String filePath, String uploadId, int partNumber, long partSize, InputStream inputStream) + throws Exception { + AliOssBucket bucket = aliOssConfig.getPrimaryBucket(); + return bucket.uploadPart(filePath, uploadId, partNumber, partSize, inputStream).getETag(); + } + + @Override + public String completeMultipartUpload(String filePath, String uploadId, List partETags) + throws Exception { + if (partETags == null || partETags.isEmpty()) { + throw new IllegalArgumentException("分片ETag列表不能为空"); + } + + // 将Map转换为PartETag对象 + List ossPartETags = new ArrayList<>(); + for (SysFilePartETag part : partETags) { + int partNumber = ((Number) part.getPartNumber()).intValue(); + String etag = part.getETag(); + ossPartETags.add(new SysFilePartETag(partNumber, etag)); + } + return aliOssConfig.getPrimaryBucket().completeMultipartUpload(filePath, uploadId, ossPartETags); + } + +} \ No newline at end of file diff --git a/boyue-file/boyue-file-oss-alibaba/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/boyue-file/boyue-file-oss-alibaba/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000..c75c3b4 --- /dev/null +++ b/boyue-file/boyue-file-oss-alibaba/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,19 @@ +{ + "properties": [ + { + "name": "oss.enable", + "type": "java.lang.Boolean", + "description": "是否开启oss" + }, + { + "name": "oss.primary", + "type": "java.lang.String", + "description": "默认存储名称" + }, + { + "name": "oss.client", + "type": "java.util.Map", + "description": "储存桶" + } + ] +} \ No newline at end of file diff --git a/boyue-file/boyue-file-starter/pom.xml b/boyue-file/boyue-file-starter/pom.xml new file mode 100644 index 0000000..3ac117f --- /dev/null +++ b/boyue-file/boyue-file-starter/pom.xml @@ -0,0 +1,29 @@ + + + + boyue-file + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-file-starter + + + 文件 + + + + + com.boyue + boyue-file-oss-alibaba + + + + com.boyue + boyue-file-minio + + + + diff --git a/boyue-file/boyue-file-starter/src/main/java/com/boyue/file/controller/FileController.java b/boyue-file/boyue-file-starter/src/main/java/com/boyue/file/controller/FileController.java new file mode 100644 index 0000000..1163a2f --- /dev/null +++ b/boyue-file/boyue-file-starter/src/main/java/com/boyue/file/controller/FileController.java @@ -0,0 +1,314 @@ +package com.boyue.file.controller; + +import java.io.OutputStream; +import java.net.URLConnection; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +import org.apache.commons.io.IOUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.boyue.common.annotation.Anonymous; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.exception.ServiceException; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.file.FileUtils; +import com.boyue.common.utils.sign.Md5Utils; +import com.boyue.file.domain.SysFileInfo; +import com.boyue.file.domain.SysFilePartETag; +import com.boyue.file.service.ISysFileInfoService; +import com.boyue.file.storage.StorageBucket; +import com.boyue.file.storage.StorageEntity; +import com.boyue.file.storage.StorageManagement; +import com.boyue.file.storage.StorageManagements; +import com.boyue.file.utils.FileOperateUtils; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Tag(name = "默认文件存储") +@RestController +@RequestMapping("/file") +public class FileController { + + @Autowired + private ISysFileInfoService sysFileInfoService; + + @Autowired + StorageManagements storageManagement; + + /** + * 获取所有可用存储渠道及其client列表 + */ + @GetMapping("/client-list") + public AjaxResult getClientList() { + Map> result = new HashMap<>(); + Map configs = storageManagement.getStorageTypes(); + for (String storageType : configs.keySet()) { + StorageManagement config = configs.get(storageType); + result.put(storageType, new ArrayList<>(config.getClient().keySet())); + } + return AjaxResult.success(result); + } + + /** + * 统一上传接口:/file/{storageType}/{clientName}/upload + */ + @PostMapping("/{storageType}/{clientName}/upload") + public AjaxResult uploadUnified( + @PathVariable("storageType") String storageType, + @PathVariable("clientName") String clientName, + @RequestParam("file") MultipartFile file) { + try { + String md5 = Md5Utils.getMd5(file); + String fileType = null; + if (file.getOriginalFilename() != null && file.getOriginalFilename().contains(".")) { + fileType = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf('.') + 1); + } + String filePath = "upload/" + System.currentTimeMillis() + "_" + file.getOriginalFilename(); + StorageBucket storageBucket = storageManagement.getStorageBucket(storageType, clientName); + storageBucket.put(filePath, file); + String url = storageBucket.getUrl(filePath).toString(); + SysFileInfo info = new SysFileInfo(); + info.setFileName(file.getOriginalFilename()); + info.setFilePath(filePath); + info.setStorageType(storageType); + info.setFileType(fileType); + info.setFileSize(file.getSize()); + info.setMd5(md5); + sysFileInfoService.insertSysFileInfo(info); + AjaxResult ajax = AjaxResult.success(); + ajax.put("url", url); + ajax.put("info", info); + return ajax; + } catch (Exception e) { + return AjaxResult.error(e.getMessage()); + } + } + + /** + * 统一下载接口:/file/{storageType}/{clientName}/download?filePath=xxx + */ + @GetMapping("/{storageType}/{clientName}/download") + public void downloadUnified( + @PathVariable("storageType") String storageType, + @PathVariable("clientName") String clientName, + @RequestParam("filePath") String filePath, + HttpServletResponse response) throws Exception { + try { + StorageBucket storageBucket = storageManagement.getStorageBucket(storageType, clientName); + StorageEntity fileEntity = storageBucket.get(filePath); + response.setContentType("application/octet-stream"); + response.setHeader("Content-Disposition", + "attachment; filename=" + URLEncoder.encode(filePath, "UTF-8")); + IOUtils.copy(fileEntity.getInputStream(), response.getOutputStream()); + } catch (Exception e) { + response.setContentType("text/plain;charset=UTF-8"); + response.getWriter().write("下载失败: " + e.getMessage()); + } + } + + /** + * 统一预览接口:/file/{storageType}/{clientName}/preview?filePath=xxx + */ + @Anonymous + @GetMapping("/{storageType}/{clientName}/preview") + public void preview( + @PathVariable("storageType") String storageType, + @PathVariable("clientName") String clientName, + @RequestParam("filePath") String filePath, + HttpServletResponse response) throws Exception { + try { + filePath = URLDecoder.decode(filePath, "UTF-8"); + StorageBucket storageBucket = storageManagement.getStorageBucket(storageType, clientName); + StorageEntity fileEntity = storageBucket.get(filePath); + String contentType = URLConnection.guessContentTypeFromName(FileUtils.getName(filePath)); + if (contentType == null) { + contentType = "application/octet-stream"; + } + response.setContentType(contentType); + IOUtils.copy(fileEntity.getInputStream(), response.getOutputStream()); + response.flushBuffer(); + } catch (Exception e) { + response.setContentType("text/plain;charset=UTF-8"); + response.getWriter().write("预览失败: " + e.getMessage()); + } + } + + /** + * 本地资源通用下载 + */ + @Operation(summary = "本地资源通用下载") + @GetMapping("/resource") + @Anonymous + public void resourceDownload(@RequestParam String filePath, + HttpServletRequest request, HttpServletResponse response) + throws Exception { + OutputStream outputStream = response.getOutputStream(); + try { + if (!FileUtils.checkAllowDownload(filePath)) { + throw new Exception(StringUtils.format("资源文件({})非法,不允许下载。 ", filePath)); + } + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + FileUtils.setAttachmentResponseHeader(response, filePath); + FileOperateUtils.downLoad(filePath, outputStream); + } catch (Exception e) { + response.reset(); + response.setContentType(MediaType.TEXT_HTML_VALUE); + response.setCharacterEncoding("UTF-8"); + String errorMessage = "下载文件失败: " + e.getMessage(); + outputStream.write(errorMessage.getBytes("UTF-8")); + outputStream.flush(); + } finally { + outputStream.close(); + } + } + + private static final long MAX_FILE_SIZE = 500 * 1024 * 1024; // 500MB + // 存储上传会话ID与文件名的映射,用于通过uploadId查找原始文件名。 + private final ConcurrentHashMap uploadIdToFileName = new ConcurrentHashMap<>(); + // 存储上传会话ID与文件大小(字节)的映射,记录分片上传任务对应的文件总大小。 + private final ConcurrentHashMap uploadIdToFileSize = new ConcurrentHashMap<>(); + // 存储上传会话ID与文件路径的映射,标识该上传任务在存储系统中的具体位置。 + private final ConcurrentHashMap uploadIdToFilePath = new ConcurrentHashMap<>(); + // 存储上传会话ID与存储桶名称的映射,确定文件最终上传到哪个存储桶中。 + private Map uploadIdToBucketName = new ConcurrentHashMap<>(); + + /** + * 初始化分片上传 + */ + @PostMapping("/initUpload") + public AjaxResult initMultipartUpload(@RequestParam("fileName") String fileName, + @RequestParam("fileSize") Long fileSize, + @RequestParam(value = "fileType", required = false) String fileType, + @RequestParam("bucketName") String bucketName) { + try { + if (fileName == null || fileName.isEmpty() || fileSize == null || fileSize <= 0) { + throw new ServiceException("文件名或文件大小不能为空"); + } + if (fileSize > MAX_FILE_SIZE) + throw new ServiceException("文件不能超过500MB"); + String currentDate = new SimpleDateFormat("yyyy/MM/dd").format(new Date()); + String timestamp = String.valueOf(System.currentTimeMillis()); + String objectName = String.format("%s/%s/%s_%s", "/upload", currentDate, timestamp, fileName); + String uploadId = FileOperateUtils.initMultipartUpload(objectName); + // 存储上传会话信息 + uploadIdToFileName.put(uploadId, fileName); + uploadIdToFileSize.put(uploadId, fileSize); + uploadIdToFilePath.put(uploadId, objectName); + uploadIdToBucketName.put(uploadId, bucketName); + return AjaxResult.success(Map.of("uploadId", uploadId, "filePath", objectName, + "fileName", fileName, "bucketName", bucketName)); + } catch (Exception e) { + return AjaxResult.error(e.getMessage()); + } + } + + /** + * 上传文件分片 + */ + @PostMapping("/uploadChunk") + public AjaxResult uploadFileChunk(@RequestParam("uploadId") String uploadId, + @RequestParam("filePath") String filePath, @RequestParam("chunkIndex") int chunkIndex, + @RequestParam("chunk") MultipartFile chunk) { + try { + if (chunk == null || chunk.isEmpty()) + throw new ServiceException("分片数据不能为空"); + // 检查上传会话是否存在 + String fileName = uploadIdToFileName.get(uploadId); + Long fileSize = uploadIdToFileSize.get(uploadId); + if (fileName == null || fileSize == null) + throw new ServiceException("文件上传会话不存在或已过期"); + // 上传分片 + String etag = FileOperateUtils.uploadPart(filePath, uploadId, chunkIndex + 1, chunk.getSize(), + chunk.getInputStream()); + if (etag == null || etag.isEmpty()) + throw new ServiceException("上传分片失败:未获取到ETag"); + return AjaxResult.success(Map.of("etag", etag, "chunkIndex", + chunkIndex, "partNumber", chunkIndex + 1)); + } catch (Exception e) { + return AjaxResult.error(e.getMessage()); + } + } + + /** + * 完成分片上传并合并文件 + */ + @PostMapping("/completeUpload") + public AjaxResult completeMultipartUpload(@RequestParam("uploadId") String uploadId, + @RequestParam("filePath") String filePath, @RequestBody List partETags) { + try { + String bucketName = uploadIdToBucketName.get(uploadId); + if (bucketName == null || bucketName.trim().isEmpty()) + throw new ServiceException("无法获取存储桶"); + if (partETags == null || partETags.isEmpty()) + throw new ServiceException("分片信息不能为空"); + String fileName = uploadIdToFileName.get(uploadId); + Long fileSize = uploadIdToFileSize.get(uploadId); + if (fileName == null || fileSize == null) + throw new ServiceException("文件上传会话不存在或已过期"); + // 验证并排序分片信息 + List validParts = partETags.stream() + .filter(part -> part != null && part.getPartNumber() != null && part.getETag() != null) + .peek(part -> { + if (part.getPartNumber() <= 0 || StringUtils.isEmpty(part.getETag())) { + throw new ServiceException("分片序号或ETag无效"); + } + }) + .collect(Collectors.toList()); + if (validParts.size() != partETags.size()) { + throw new ServiceException("分片信息格式不正确"); + } + validParts.sort(Comparator.comparingInt(p -> p.getPartNumber())); + // 完成分片上传并合并文件 + String finalPath = FileOperateUtils.completeMultipartUpload(filePath, uploadId, validParts); + if (finalPath == null || finalPath.isEmpty()) { + throw new ServiceException("合并分片失败:未获取到最终文件路径"); + } + // 创建文件信息记录 + SysFileInfo fileInfo = new SysFileInfo(); + fileInfo.setFileName(fileName); + fileInfo.setFilePath(finalPath); + fileInfo.setFileSize(fileSize); + int dotIndex = fileName.lastIndexOf('.'); + fileInfo.setFileType(dotIndex >= 0 ? fileName.substring(dotIndex + 1) : ""); + fileInfo.setStorageType(bucketName); + fileInfo.setCreateBy(SecurityUtils.getUsername()); + fileInfo.setCreateTime(new Date()); + fileInfo.setUpdateBy(SecurityUtils.getUsername()); + fileInfo.setUpdateTime(new Date()); + fileInfo.setDelFlag("0"); + sysFileInfoService.insertSysFileInfo(fileInfo); + // 清理上传会话信息 + uploadIdToFileName.remove(uploadId); + uploadIdToFileSize.remove(uploadId); + uploadIdToFilePath.remove(uploadId); + uploadIdToBucketName.remove(uploadId); + return AjaxResult.success(Map.of("fileId", fileInfo.getFileId(), "fileName", fileName, + "filePath", finalPath, "fileSize", fileSize, "storageType", bucketName)); + } catch (Exception e) { + return AjaxResult.error(e.getMessage()); + } + } +} diff --git a/boyue-file/boyue-file-starter/src/main/java/com/boyue/file/controller/SysFileInfoController.java b/boyue-file/boyue-file-starter/src/main/java/com/boyue/file/controller/SysFileInfoController.java new file mode 100644 index 0000000..147afbb --- /dev/null +++ b/boyue-file/boyue-file-starter/src/main/java/com/boyue/file/controller/SysFileInfoController.java @@ -0,0 +1,113 @@ +package com.boyue.file.controller; + +import java.util.List; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.enums.BusinessType; +import com.boyue.file.domain.SysFileInfo; +import com.boyue.file.service.ISysFileInfoService; +import com.boyue.common.utils.poi.ExcelUtil; +import com.boyue.common.core.page.TableDataInfo; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; + +/** + * 文件信息Controller + * + * @author boyue + * @date 2025-04-25 + */ +@RestController +@RequestMapping("/file/info") +@Tag(name = "【文件信息】管理") +public class SysFileInfoController extends BaseController +{ + @Autowired + private ISysFileInfoService sysFileInfoService; + + /** + * 查询文件信息列表 + */ + @Operation(summary = "查询文件信息列表") + @PreAuthorize("@ss.hasPermi('file:info:list')") + @GetMapping("/list") + public TableDataInfo list(SysFileInfo sysFileInfo) + { + startPage(); + List list = sysFileInfoService.selectSysFileInfoList(sysFileInfo); + return getDataTable(list); + } + + /** + * 导出文件信息列表 + */ + @Operation(summary = "导出文件信息列表") + @PreAuthorize("@ss.hasPermi('file:info:export')") + @Log(title = "文件信息", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, SysFileInfo sysFileInfo) + { + List list = sysFileInfoService.selectSysFileInfoList(sysFileInfo); + ExcelUtil util = new ExcelUtil(SysFileInfo.class); + util.exportExcel(response, list, "文件信息数据"); + } + + /** + * 获取文件信息详细信息 + */ + @Operation(summary = "获取文件信息详细信息") + @PreAuthorize("@ss.hasPermi('file:info:query')") + @GetMapping(value = "/{fileId}") + public AjaxResult getInfo(@PathVariable("fileId") Long fileId) + { + return success(sysFileInfoService.selectSysFileInfoByFileId(fileId)); + } + + /** + * 新增文件信息 + */ + @Operation(summary = "新增文件信息") + @PreAuthorize("@ss.hasPermi('file:info:add')") + @Log(title = "文件信息", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody SysFileInfo sysFileInfo) + { + return toAjax(sysFileInfoService.insertSysFileInfo(sysFileInfo)); + } + + /** + * 修改文件信息 + */ + @Operation(summary = "修改文件信息") + @PreAuthorize("@ss.hasPermi('file:info:edit')") + @Log(title = "文件信息", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody SysFileInfo sysFileInfo) + { + return toAjax(sysFileInfoService.updateSysFileInfo(sysFileInfo)); + } + + /** + * 删除文件信息 + */ + @Operation(summary = "删除文件信息") + @PreAuthorize("@ss.hasPermi('file:info:remove')") + @Log(title = "文件信息", businessType = BusinessType.DELETE) + @DeleteMapping("/{fileIds}") + public AjaxResult remove(@PathVariable( name = "fileIds" ) Long[] fileIds) + { + return toAjax(sysFileInfoService.deleteSysFileInfoByFileIds(fileIds)); + } +} diff --git a/boyue-file/pom.xml b/boyue-file/pom.xml new file mode 100644 index 0000000..70971d7 --- /dev/null +++ b/boyue-file/pom.xml @@ -0,0 +1,65 @@ + + + + boyuehasfj-java + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-file + + + 3.18.1 + 8.2.1 + + + + 文件模块 + + + + + com.boyue + boyue-file-common + ${boyue.version} + + + + io.minio + minio + ${minio.version} + + + + + com.aliyun.oss + aliyun-sdk-oss + ${aliyunoss.version} + + + + com.boyue + boyue-file-oss-alibaba + ${boyue.version} + + + + com.boyue + boyue-file-minio + ${boyue.version} + + + + + + + + boyue-file-common + boyue-file-minio + boyue-file-oss-alibaba + boyue-file-starter + + pom + \ No newline at end of file diff --git a/boyue-framework/pom.xml b/boyue-framework/pom.xml new file mode 100644 index 0000000..2593b01 --- /dev/null +++ b/boyue-framework/pom.xml @@ -0,0 +1,74 @@ + + + + boyuehasfj-java + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-framework + + + framework框架核心 + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-aop + + + + + com.alibaba + druid-spring-boot-3-starter + + + + javax.transaction + jta + + + + + pro.fessional + kaptcha + + + jakarta.servlet-api + jakarta.servlet + + + + + + + com.github.oshi + oshi-core + + + + + + com.boyue + boyue-system + + + + com.boyue + boyue-plugins-starter + + + + + diff --git a/boyue-framework/src/main/java/com/boyue/framework/aspectj/DataScopeAspect.java b/boyue-framework/src/main/java/com/boyue/framework/aspectj/DataScopeAspect.java new file mode 100644 index 0000000..c311dc1 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/aspectj/DataScopeAspect.java @@ -0,0 +1,165 @@ +package com.boyue.framework.aspectj; + +import java.util.ArrayList; +import java.util.List; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.stereotype.Component; + +import com.boyue.common.annotation.DataScope; +import com.boyue.common.constant.UserConstants; +import com.boyue.common.core.domain.BaseEntity; +import com.boyue.common.core.domain.entity.SysRole; +import com.boyue.common.core.domain.entity.SysUser; +import com.boyue.common.core.domain.model.LoginUser; +import com.boyue.common.core.text.Convert; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.framework.security.context.PermissionContextHolder; + +/** + * 数据过滤处理 + * + * @author boyue + */ +@Aspect +@Component +public class DataScopeAspect { + /** + * 全部数据权限 + */ + public static final String DATA_SCOPE_ALL = "1"; + + /** + * 自定数据权限 + */ + public static final String DATA_SCOPE_CUSTOM = "2"; + + /** + * 部门数据权限 + */ + public static final String DATA_SCOPE_DEPT = "3"; + + /** + * 部门及以下数据权限 + */ + public static final String DATA_SCOPE_DEPT_AND_CHILD = "4"; + + /** + * 仅本人数据权限 + */ + public static final String DATA_SCOPE_SELF = "5"; + + /** + * 数据权限过滤关键字 + */ + public static final String DATA_SCOPE = "dataScope"; + + @Before("@annotation(controllerDataScope)") + public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable { + clearDataScope(point); + handleDataScope(point, controllerDataScope); + } + + protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope) { + // 获取当前的用户 + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNotNull(loginUser)) { + SysUser currentUser = loginUser.getUser(); + // 如果是超级管理员,则不过滤数据 + if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()) { + String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), + PermissionContextHolder.getContext()); + dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(), + controllerDataScope.userAlias(), permission); + } + } + } + + /** + * 数据范围过滤 + * + * @param joinPoint 切点 + * @param user 用户 + * @param deptAlias 部门别名 + * @param userAlias 用户别名 + * @param permission 权限字符 + */ + public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias, + String permission) { + StringBuilder sqlString = new StringBuilder(); + List conditions = new ArrayList(); + List scopeCustomIds = new ArrayList(); + user.getRoles().forEach(role -> { + if (DATA_SCOPE_CUSTOM.equals(role.getDataScope()) && StringUtils.equals(role.getStatus(), UserConstants.ROLE_NORMAL) && StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))){ + scopeCustomIds.add(Convert.toStr(role.getRoleId())); + } + }); + + for (SysRole role : user.getRoles()) { + String dataScope = role.getDataScope(); + if (conditions.contains(dataScope) || StringUtils.equals(role.getStatus(), UserConstants.ROLE_DISABLE)) { + continue; + } + if (StringUtils.isNotEmpty(permission) && !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) { + continue; + } + if (DATA_SCOPE_ALL.equals(dataScope)) { + sqlString = new StringBuilder(); + conditions.add(dataScope); + break; + } else if (DATA_SCOPE_CUSTOM.equals(dataScope)) { + if (scopeCustomIds.size() > 1) { + // 多个自定数据权限使用in查询,避免多次拼接。 + sqlString.append(StringUtils.format( + " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id in ({}) ) ", deptAlias, + String.join(",", scopeCustomIds))); + } else { + sqlString.append(StringUtils.format( + " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias, + role.getRoleId())); + } + } else if (DATA_SCOPE_DEPT.equals(dataScope)) { + sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId())); + } else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) { + sqlString.append(StringUtils.format( + " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )", + deptAlias, user.getDeptId(), user.getDeptId())); + } else if (DATA_SCOPE_SELF.equals(dataScope)) { + if (StringUtils.isNotBlank(userAlias)) { + sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId())); + } else { + // 数据权限为仅本人且没有userAlias别名不查询任何数据 + sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias)); + } + } + conditions.add(dataScope); + } + + // 多角色情况下,所有角色都不包含传递过来的权限字符,这个时候sqlString也会为空,所以要限制一下,不查询任何数据 + if (StringUtils.isEmpty(conditions)) { + sqlString.append(StringUtils.format(" OR {}.dept_id = 0 ", deptAlias)); + } + + if (StringUtils.isNotBlank(sqlString.toString())) { + Object params = joinPoint.getArgs()[0]; + if (StringUtils.isNotNull(params) && params instanceof BaseEntity) { + BaseEntity baseEntity = (BaseEntity) params; + baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")"); + } + } + } + + /** + * 拼接权限sql前先清空params.dataScope参数防止注入 + */ + private void clearDataScope(final JoinPoint joinPoint) { + Object params = joinPoint.getArgs()[0]; + if (StringUtils.isNotNull(params) && params instanceof BaseEntity) { + BaseEntity baseEntity = (BaseEntity) params; + baseEntity.getParams().put(DATA_SCOPE, ""); + } + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/aspectj/DataSourceAspect.java b/boyue-framework/src/main/java/com/boyue/framework/aspectj/DataSourceAspect.java new file mode 100644 index 0000000..0c2784b --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/aspectj/DataSourceAspect.java @@ -0,0 +1,70 @@ +package com.boyue.framework.aspectj; + +import java.util.Objects; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import com.boyue.common.annotation.DataSource; +import com.boyue.common.utils.StringUtils; +import com.boyue.framework.datasource.DynamicDataSourceContextHolder; + +/** + * 多数据源处理 + * + * @author boyue + */ +@Aspect +@Order(1) +@Component +public class DataSourceAspect { + protected Logger logger = LoggerFactory.getLogger(getClass()); + + @Pointcut("@annotation(com.boyue.common.annotation.DataSource)" + + "|| @within(com.boyue.common.annotation.DataSource)") + public void dsPointCut() { + + } + + @Around("dsPointCut()") + public Object around(ProceedingJoinPoint point) throws Throwable { + DataSource dataSource = getDataSource(point); + + if (StringUtils.isNotNull(dataSource)) { + if ("".equals(dataSource.name())) { + DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name()); + } else { + DynamicDataSourceContextHolder.setDataSourceType(dataSource.name()); + } + + } + + try { + return point.proceed(); + } finally { + // 销毁数据源 在执行方法之后 + DynamicDataSourceContextHolder.clearDataSourceType(); + } + } + + /** + * 获取需要切换的数据源 + */ + public DataSource getDataSource(ProceedingJoinPoint point) { + MethodSignature signature = (MethodSignature) point.getSignature(); + DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class); + if (Objects.nonNull(dataSource)) { + return dataSource; + } + + return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class); + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/aspectj/LogAspect.java b/boyue-framework/src/main/java/com/boyue/framework/aspectj/LogAspect.java new file mode 100644 index 0000000..421fcd3 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/aspectj/LogAspect.java @@ -0,0 +1,226 @@ +package com.boyue.framework.aspectj; + +import java.util.Collection; +import java.util.Map; + +import org.apache.commons.lang3.ArrayUtils; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.NamedThreadLocal; +import org.springframework.stereotype.Component; +import org.springframework.validation.BindingResult; +import org.springframework.web.multipart.MultipartFile; + +import com.alibaba.fastjson2.JSON; +import com.boyue.common.annotation.Log; +import com.boyue.common.core.domain.entity.SysUser; +import com.boyue.common.core.domain.model.LoginUser; +import com.boyue.common.core.text.Convert; +import com.boyue.common.enums.BusinessStatus; +import com.boyue.common.enums.HttpMethod; +import com.boyue.common.filter.PropertyPreExcludeFilter; +import com.boyue.common.utils.ExceptionUtil; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.common.utils.ServletUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.ip.IpUtils; +import com.boyue.framework.manager.AsyncManager; +import com.boyue.framework.manager.factory.AsyncFactory; +import com.boyue.system.domain.SysOperLog; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 操作日志记录处理 + * + * @author boyue + */ +@Aspect +@Component +public class LogAspect { + private static final Logger log = LoggerFactory.getLogger(LogAspect.class); + + /** 排除敏感属性字段 */ + public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" }; + + /** 计算操作消耗时间 */ + private static final ThreadLocal TIME_THREADLOCAL = new NamedThreadLocal("Cost Time"); + + /** + * 处理请求前执行 + */ + @Before(value = "@annotation(controllerLog)") + public void boBefore(JoinPoint joinPoint, Log controllerLog) { + TIME_THREADLOCAL.set(System.currentTimeMillis()); + } + + /** + * 处理完请求后执行 + * + * @param joinPoint 切点 + */ + @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult") + public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) { + handleLog(joinPoint, controllerLog, null, jsonResult); + } + + /** + * 拦截异常操作 + * + * @param joinPoint 切点 + * @param e 异常 + */ + @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e") + public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) { + handleLog(joinPoint, controllerLog, e, null); + } + + protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) { + try { + // 获取当前的用户 + LoginUser loginUser = SecurityUtils.getLoginUser(); + + // *========数据库日志=========*// + SysOperLog operLog = new SysOperLog(); + operLog.setStatus(BusinessStatus.SUCCESS.ordinal()); + // 请求的地址 + String ip = IpUtils.getIpAddr(); + operLog.setOperIp(ip); + operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255)); + if (loginUser != null) { + operLog.setOperName(loginUser.getUsername()); + SysUser currentUser = loginUser.getUser(); + if (StringUtils.isNotNull(currentUser) && StringUtils.isNotNull(currentUser.getDept())) { + operLog.setDeptName(currentUser.getDept().getDeptName()); + } + } + + if (e != null) { + operLog.setStatus(BusinessStatus.FAIL.ordinal()); + operLog.setErrorMsg(StringUtils.substring(Convert.toStr(e.getMessage(), ExceptionUtil.getExceptionMessage(e)), 0, 2000)); + } + // 设置方法名称 + String className = joinPoint.getTarget().getClass().getName(); + String methodName = joinPoint.getSignature().getName(); + operLog.setMethod(className + "." + methodName + "()"); + // 设置请求方式 + operLog.setRequestMethod(ServletUtils.getRequest().getMethod()); + // 处理设置注解上的参数 + getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult); + // 设置消耗时间 + operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get()); + // 保存数据库 + AsyncManager.me().execute(AsyncFactory.recordOper(operLog)); + } catch (Exception exp) { + // 记录本地异常日志 + log.error("异常信息:{}", exp.getMessage()); + exp.printStackTrace(); + } finally { + TIME_THREADLOCAL.remove(); + } + } + + /** + * 获取注解中对方法的描述信息 用于Controller层注解 + * + * @param log 日志 + * @param operLog 操作日志 + * @throws Exception + */ + public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) + throws Exception { + // 设置action动作 + operLog.setBusinessType(log.businessType().ordinal()); + // 设置标题 + operLog.setTitle(log.title()); + // 设置操作人类别 + operLog.setOperatorType(log.operatorType().ordinal()); + // 是否需要保存request,参数和值 + if (log.isSaveRequestData()) { + // 获取参数的信息,传入到数据库中。 + setRequestValue(joinPoint, operLog, log.excludeParamNames()); + } + // 是否需要保存response,参数和值 + if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) { + operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000)); + } + } + + /** + * 获取请求的参数,放到log中 + * + * @param operLog 操作日志 + * @throws Exception 异常 + */ + private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception { + Map paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest()); + String requestMethod = operLog.getRequestMethod(); + if (StringUtils.isEmpty(paramsMap) && StringUtils.equalsAny(requestMethod, HttpMethod.PUT.name(), + HttpMethod.POST.name(), HttpMethod.DELETE.name())) { + String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames); + operLog.setOperParam(StringUtils.substring(params, 0, 2000)); + } else { + operLog.setOperParam(StringUtils + .substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, 2000)); + } + } + + /** + * 参数拼装 + */ + private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) { + String params = ""; + if (paramsArray != null && paramsArray.length > 0) { + for (Object o : paramsArray) { + if (StringUtils.isNotNull(o) && !isFilterObject(o)) { + try { + String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames)); + params += jsonObj.toString() + " "; + } catch (Exception e) { + } + } + } + } + return params.trim(); + } + + /** + * 忽略敏感属性 + */ + public PropertyPreExcludeFilter excludePropertyPreFilter(String[] excludeParamNames) { + return new PropertyPreExcludeFilter().addExcludes(ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames)); + } + + /** + * 判断是否需要过滤的对象。 + * + * @param o 对象信息。 + * @return 如果是需要过滤的对象,则返回true;否则返回false。 + */ + @SuppressWarnings("rawtypes") + public boolean isFilterObject(final Object o) { + Class clazz = o.getClass(); + if (clazz.isArray()) { + return clazz.getComponentType().isAssignableFrom(MultipartFile.class); + } else if (Collection.class.isAssignableFrom(clazz)) { + Collection collection = (Collection) o; + for (Object value : collection) { + return value instanceof MultipartFile; + } + } else if (Map.class.isAssignableFrom(clazz)) { + Map map = (Map) o; + for (Object value : map.entrySet()) { + Map.Entry entry = (Map.Entry) value; + return entry.getValue() instanceof MultipartFile; + } + } + return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse + || o instanceof BindingResult; + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/config/ApplicationConfig.java b/boyue-framework/src/main/java/com/boyue/framework/config/ApplicationConfig.java new file mode 100644 index 0000000..2a694cb --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/config/ApplicationConfig.java @@ -0,0 +1,31 @@ +package com.boyue.framework.config; + +import java.util.TimeZone; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + +/** + * 程序注解配置 + * + * @author boyue + */ +@Configuration +// 表示通过aop框架暴露该代理对象,AopContext能够访问 +@EnableAspectJAutoProxy(exposeProxy = true) +// 指定要扫描的Mapper类的包的路径 +@MapperScan(basePackages = "com.boyue.**.mapper", sqlSessionTemplateRef = "sqlSessionTemplate") +public class ApplicationConfig +{ + /** + * 时区配置 + */ + @Bean + public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() + { + return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault()); + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/config/CaptchaConfig.java b/boyue-framework/src/main/java/com/boyue/framework/config/CaptchaConfig.java new file mode 100644 index 0000000..a4081c9 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/config/CaptchaConfig.java @@ -0,0 +1,83 @@ +package com.boyue.framework.config; + +import java.util.Properties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import com.google.code.kaptcha.impl.DefaultKaptcha; +import com.google.code.kaptcha.util.Config; +import static com.google.code.kaptcha.Constants.*; + +/** + * 验证码配置 + * + * @author boyue + */ +@Configuration +public class CaptchaConfig +{ + @Bean(name = "captchaProducer") + public DefaultKaptcha getKaptchaBean() + { + DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); + Properties properties = new Properties(); + // 是否有边框 默认为true 我们可以自己设置yes,no + properties.setProperty(KAPTCHA_BORDER, "yes"); + // 验证码文本字符颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black"); + // 验证码图片宽度 默认为200 + properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); + // 验证码图片高度 默认为50 + properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); + // 验证码文本字符大小 默认为40 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38"); + // KAPTCHA_SESSION_KEY + properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode"); + // 验证码文本字符长度 默认为5 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4"); + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); + // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy + properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); + Config config = new Config(properties); + defaultKaptcha.setConfig(config); + return defaultKaptcha; + } + + @Bean(name = "captchaProducerMath") + public DefaultKaptcha getKaptchaBeanMath() + { + DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); + Properties properties = new Properties(); + // 是否有边框 默认为true 我们可以自己设置yes,no + properties.setProperty(KAPTCHA_BORDER, "yes"); + // 边框颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90"); + // 验证码文本字符颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue"); + // 验证码图片宽度 默认为200 + properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); + // 验证码图片高度 默认为50 + properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); + // 验证码文本字符大小 默认为40 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35"); + // KAPTCHA_SESSION_KEY + properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath"); + // 验证码文本生成器 + properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.boyue.framework.config.KaptchaTextCreator"); + // 验证码文本字符间距 默认为2 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3"); + // 验证码文本字符长度 默认为5 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6"); + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); + // 验证码噪点颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_NOISE_COLOR, "white"); + // 干扰实现类 + properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise"); + // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy + properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); + Config config = new Config(properties); + defaultKaptcha.setConfig(config); + return defaultKaptcha; + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/config/DruidConfig.java b/boyue-framework/src/main/java/com/boyue/framework/config/DruidConfig.java new file mode 100644 index 0000000..941915b --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/config/DruidConfig.java @@ -0,0 +1,94 @@ +package com.boyue.framework.config; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.spring.boot3.autoconfigure.properties.DruidStatProperties; +import com.alibaba.druid.util.Utils; + +import jakarta.annotation.PreDestroy; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; + +/** + * druid 配置多数据源 + * + * @author boyue + */ +@Configuration +public class DruidConfig { + + @Autowired + DynamicDataSourceProperties dataSourceProperties; + + private List druidDataSources = new ArrayList<>(); + + Logger logger = LoggerFactory.getLogger(DruidConfig.class); + + /** + * 去除监控页面底部的广告 + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + @ConditionalOnProperty(name = "spring.datasource.druid.stat-view-servlet.enabled", havingValue = "true") + FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties) { + // 获取web监控页面的参数 + DruidStatProperties.StatViewServlet config = properties.getStatViewServlet(); + // 提取common.js的配置路径 + String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*"; + String commonJsPattern = pattern.replaceAll("\\*", "js/common.js"); + final String filePath = "support/http/resources/js/common.js"; + // 创建filter进行过滤 + Filter filter = new Filter() { + @Override + public void init(jakarta.servlet.FilterConfig filterConfig) throws ServletException { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + chain.doFilter(request, response); + // 重置缓冲区,响应头不会被重置 + response.resetBuffer(); + // 获取common.js + String text = Utils.readFromResource(filePath); + // 正则替换banner, 除去底部的广告信息 + text = text.replaceAll("
", ""); + text = text.replaceAll("powered.*?shrek.wang", ""); + response.getWriter().write(text); + } + + @Override + public void destroy() { + } + }; + FilterRegistrationBean registrationBean = new FilterRegistrationBean(); + registrationBean.setFilter(filter); + registrationBean.addUrlPatterns(commonJsPattern); + return registrationBean; + } + + public List getDruidDataSources() { + return druidDataSources; + } + + @PreDestroy + public void destroy() { + for (DruidDataSource druidDataSource : druidDataSources) { + druidDataSource.close(); + } + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/config/DynamicDataSourceProperties.java b/boyue-framework/src/main/java/com/boyue/framework/config/DynamicDataSourceProperties.java new file mode 100644 index 0000000..26c1df1 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/config/DynamicDataSourceProperties.java @@ -0,0 +1,115 @@ +package com.boyue.framework.config; + +import java.util.ArrayList; +import java.util.Map; +import java.util.Properties; + +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import com.alibaba.druid.pool.DruidDataSource; +import com.boyue.common.utils.spring.SpringUtils; +import com.boyue.framework.config.properties.DruidProperties; + +@Configuration +@ConfigurationProperties(prefix = "spring.datasource.dynamic") +public class DynamicDataSourceProperties { + + private Map datasource; + private String primary; + + public Properties build(DataSourceProperties dataSourceProperties) { + Properties prop = new Properties(); + DruidProperties druidProperties = SpringUtils.getBean(DruidProperties.class); + prop.setProperty("url", dataSourceProperties.getUrl()); + prop.setProperty("username", dataSourceProperties.getUsername()); + prop.setProperty("password", dataSourceProperties.getPassword()); + prop.setProperty("initialSize", String.valueOf(druidProperties.getInitialSize())); + prop.setProperty("minIdle", String.valueOf(druidProperties.getMinIdle())); + prop.setProperty("maxActive", String.valueOf(druidProperties.getMaxActive())); + prop.setProperty("maxWait", String.valueOf(druidProperties.getMaxWait())); + prop.setProperty("timeBetweenEvictionRunsMillis", + String.valueOf(druidProperties.getTimeBetweenEvictionRunsMillis())); + prop.setProperty("minEvictableIdleTimeMillis", String.valueOf(druidProperties.getMinEvictableIdleTimeMillis())); + prop.setProperty("maxEvictableIdleTimeMillis", String.valueOf(druidProperties.getMaxEvictableIdleTimeMillis())); + prop.setProperty("validationQuery", druidProperties.getValidationQuery()); + prop.setProperty("testWhileIdle", String.valueOf(druidProperties.isTestWhileIdle())); + prop.setProperty("testOnBorrow", String.valueOf(druidProperties.isTestOnBorrow())); + prop.setProperty("testOnReturn", String.valueOf(druidProperties.isTestOnReturn())); + // 修改filters配置,确保spring在最前面 + prop.setProperty("filters", SpringUtils.getRequiredProperty("spring.datasource.druid.filters")); + prop.setProperty("connectionProperties", SpringUtils.getRequiredProperty("spring.datasource.druid.connectionProperties")); + return prop; + } + + public void setProperties(DruidDataSource dataSource, Properties prop) { + dataSource.setUrl(prop.getProperty("url")); + dataSource.setUsername(prop.getProperty("username")); + dataSource.setPassword(prop.getProperty("password")); + if (prop.getProperty("initialSize") != null) { + dataSource.setInitialSize(Integer.parseInt(prop.getProperty("initialSize"))); + } + if (prop.getProperty("minIdle") != null) { + dataSource.setMinIdle(Integer.parseInt(prop.getProperty("minIdle"))); + } + if (prop.getProperty("maxActive") != null) { + dataSource.setMaxActive(Integer.parseInt(prop.getProperty("maxActive"))); + } + if (prop.getProperty("maxWait") != null) { + dataSource.setMaxWait(Long.parseLong(prop.getProperty("maxWait"))); + } + if (prop.getProperty("timeBetweenEvictionRunsMillis") != null) { + dataSource.setTimeBetweenEvictionRunsMillis( + Long.parseLong(prop.getProperty("timeBetweenEvictionRunsMillis"))); + } + if (prop.getProperty("minEvictableIdleTimeMillis") != null) { + dataSource.setMinEvictableIdleTimeMillis(Long.parseLong(prop.getProperty("minEvictableIdleTimeMillis"))); + } + if (prop.getProperty("maxEvictableIdleTimeMillis") != null) { + dataSource.setMaxEvictableIdleTimeMillis(Long.parseLong(prop.getProperty("maxEvictableIdleTimeMillis"))); + } + if (prop.getProperty("validationQuery") != null) { + dataSource.setValidationQuery(prop.getProperty("validationQuery")); + } + if (prop.getProperty("testWhileIdle") != null) { + dataSource.setTestWhileIdle(Boolean.parseBoolean(prop.getProperty("testWhileIdle"))); + } + if (prop.getProperty("testOnBorrow") != null) { + dataSource.setTestOnBorrow(Boolean.parseBoolean(prop.getProperty("testOnBorrow"))); + } + if (prop.getProperty("testOnReturn") != null) { + dataSource.setTestOnReturn(Boolean.parseBoolean(prop.getProperty("testOnReturn"))); + } + // 确保过滤器配置生效 + try { + if (prop.getProperty("filters") != null) { + dataSource.setFilters(prop.getProperty("filters")); + } + if (prop.getProperty("connectionProperties") != null) { + dataSource.setConnectionProperties(prop.getProperty("connectionProperties")); + } + // 启用防火墙功能 + dataSource.setProxyFilters(new ArrayList<>()); + } catch (Exception e) { + throw new RuntimeException("配置Druid过滤器失败", e); + } + } + + public Map getDatasource() { + return datasource; + } + + public void setDatasource(Map datasource) { + this.datasource = datasource; + } + + public String getPrimary() { + return primary; + } + + public void setPrimary(String primary) { + this.primary = primary; + } + +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/config/FilterConfig.java b/boyue-framework/src/main/java/com/boyue/framework/config/FilterConfig.java new file mode 100644 index 0000000..0029e16 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/config/FilterConfig.java @@ -0,0 +1,58 @@ +package com.boyue.framework.config; + +import java.util.HashMap; +import java.util.Map; +import jakarta.servlet.DispatcherType; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import com.boyue.common.filter.RepeatableFilter; +import com.boyue.common.filter.XssFilter; +import com.boyue.common.utils.StringUtils; + +/** + * Filter配置 + * + * @author boyue + */ +@Configuration +public class FilterConfig +{ + @Value("${xss.excludes}") + private String excludes; + + @Value("${xss.urlPatterns}") + private String urlPatterns; + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + @ConditionalOnProperty(value = "xss.enabled", havingValue = "true") + public FilterRegistrationBean xssFilterRegistration() + { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setDispatcherTypes(DispatcherType.REQUEST); + registration.setFilter(new XssFilter()); + registration.addUrlPatterns(StringUtils.split(urlPatterns, ",")); + registration.setName("xssFilter"); + registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE); + Map initParameters = new HashMap(); + initParameters.put("excludes", excludes); + registration.setInitParameters(initParameters); + return registration; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + public FilterRegistrationBean someFilterRegistration() + { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setFilter(new RepeatableFilter()); + registration.addUrlPatterns("/*"); + registration.setName("repeatableFilter"); + registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE); + return registration; + } + +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/config/I18nConfig.java b/boyue-framework/src/main/java/com/boyue/framework/config/I18nConfig.java new file mode 100644 index 0000000..ca2ec65 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/config/I18nConfig.java @@ -0,0 +1,44 @@ +package com.boyue.framework.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; +import org.springframework.web.servlet.i18n.SessionLocaleResolver; + +import com.boyue.common.constant.Constants; + +/** + * 资源文件配置加载 + * + * @author boyue + */ +@Configuration +public class I18nConfig implements WebMvcConfigurer +{ + @Bean + public LocaleResolver localeResolver() + { + SessionLocaleResolver slr = new SessionLocaleResolver(); + // 默认语言 + slr.setDefaultLocale(Constants.DEFAULT_LOCALE); + return slr; + } + + @Bean + public LocaleChangeInterceptor localeChangeInterceptor() + { + LocaleChangeInterceptor lci = new LocaleChangeInterceptor(); + // 参数名 + lci.setParamName("lang"); + return lci; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) + { + registry.addInterceptor(localeChangeInterceptor()); + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/config/KaptchaTextCreator.java b/boyue-framework/src/main/java/com/boyue/framework/config/KaptchaTextCreator.java new file mode 100644 index 0000000..7229641 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/config/KaptchaTextCreator.java @@ -0,0 +1,69 @@ +package com.boyue.framework.config; + +import java.security.SecureRandom; + +import com.google.code.kaptcha.text.impl.DefaultTextCreator; + +/** + * 验证码文本生成器 + * + * @author boyue + */ +public class KaptchaTextCreator extends DefaultTextCreator +{ + private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(","); + + @Override + public String getText() + { + Integer result = 0; + SecureRandom random = new SecureRandom(); + int x = random.nextInt(10); + int y = random.nextInt(10); + StringBuilder suChinese = new StringBuilder(); + int randomoperands = random.nextInt(3); + if (randomoperands == 0) + { + result = x * y; + suChinese.append(CNUMBERS[x]); + suChinese.append("*"); + suChinese.append(CNUMBERS[y]); + } + else if (randomoperands == 1) + { + if ((x != 0) && y % x == 0) + { + result = y / x; + suChinese.append(CNUMBERS[y]); + suChinese.append("/"); + suChinese.append(CNUMBERS[x]); + } + else + { + result = x + y; + suChinese.append(CNUMBERS[x]); + suChinese.append("+"); + suChinese.append(CNUMBERS[y]); + } + } + else + { + if (x >= y) + { + result = x - y; + suChinese.append(CNUMBERS[x]); + suChinese.append("-"); + suChinese.append(CNUMBERS[y]); + } + else + { + result = y - x; + suChinese.append(CNUMBERS[y]); + suChinese.append("-"); + suChinese.append(CNUMBERS[x]); + } + } + suChinese.append("=?@" + result); + return suChinese.toString(); + } +} \ No newline at end of file diff --git a/boyue-framework/src/main/java/com/boyue/framework/config/MyBatisConfig.java b/boyue-framework/src/main/java/com/boyue/framework/config/MyBatisConfig.java new file mode 100644 index 0000000..51e40b9 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/config/MyBatisConfig.java @@ -0,0 +1,58 @@ +package com.boyue.framework.config; + +import javax.sql.DataSource; + +import org.apache.ibatis.io.VFS; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.mybatis.spring.boot.autoconfigure.SpringBootVFS; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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.core.io.DefaultResourceLoader; + +import com.github.pagehelper.PageInterceptor; +import com.github.pagehelper.autoconfigure.PageHelperStandardProperties; +import com.boyue.common.service.mybatis.CreateSqlSessionFactory; +import com.boyue.common.utils.MybatisUtils; +import com.boyue.common.utils.StringUtils; + +/** + * Mybatis支持*匹配扫描包 + * + * @author boyue + */ +@Configuration +public class MyBatisConfig { + + Logger logger = LoggerFactory.getLogger(MyBatisConfig.class); + + @Bean + @ConditionalOnProperty(prefix = "createSqlSessionFactory", name = "use", havingValue = "mybatis") + public CreateSqlSessionFactory createSqlSessionFactory(PageHelperStandardProperties packageHelperStandardProperties) { + return new CreateSqlSessionFactory() { + public SqlSessionFactory createSqlSessionFactory(Environment env, DataSource dataSource) throws Exception { + String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage"); + String mapperLocations = env.getProperty("mybatis.mapperLocations"); + String configLocation = env.getProperty("mybatis.configLocation"); + typeAliasesPackage = MybatisUtils.setTypeAliasesPackage(typeAliasesPackage); + VFS.addImplClass(SpringBootVFS.class); + + final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); + sessionFactory.setDataSource(dataSource); + sessionFactory.setTypeAliasesPackage(typeAliasesPackage); + sessionFactory.setMapperLocations( + MybatisUtils.resolveMapperLocations(StringUtils.split(mapperLocations, ","))); + sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation)); + PageInterceptor interceptor = new PageInterceptor(); + interceptor.setProperties(packageHelperStandardProperties.getProperties()); + sessionFactory.addPlugins(interceptor); + return sessionFactory.getObject(); + } + }; + } + +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/config/ResourcesConfig.java b/boyue-framework/src/main/java/com/boyue/framework/config/ResourcesConfig.java new file mode 100644 index 0000000..8d423c2 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/config/ResourcesConfig.java @@ -0,0 +1,77 @@ +package com.boyue.framework.config; + +import java.util.concurrent.TimeUnit; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.CacheControl; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import com.boyue.common.config.BoYueConfig; +import com.boyue.common.constant.Constants; +import com.boyue.framework.interceptor.RepeatSubmitInterceptor; + +/** + * 通用配置 + * + * @author boyue + */ +@Configuration +public class ResourcesConfig implements WebMvcConfigurer { + @Autowired + private RepeatSubmitInterceptor repeatSubmitInterceptor; + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + /** 本地文件上传路径 */ + registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**") + .addResourceLocations("file:" + BoYueConfig.getProfile() + "/"); + + /** swagger配置 */ + registry.addResourceHandler("/swagger-ui/**") + .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/") + .setCacheControl(CacheControl.maxAge(5, TimeUnit.HOURS).cachePublic()); + registry.addResourceHandler("/**") + .addResourceLocations("classpath:/static/"); + registry.addResourceHandler("swagger-ui.html", "doc.html") + .addResourceLocations("classpath:/META-INF/resources/"); + registry.addResourceHandler("/webjars/**") + .addResourceLocations("classpath:/META-INF/resources/webjars/"); + } + + /** + * 自定义拦截规则 + */ + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**"); + } + + /** + * 跨域配置 + */ + @Bean + public CorsFilter corsFilter() { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + // 设置访问源地址 + config.addAllowedOriginPattern("*"); + // 设置访问源请求头 + config.addAllowedHeader("*"); + // 设置访问源请求方法 + config.addAllowedMethod("*"); + // 有效期 1800秒 + config.setMaxAge(1800L); + // 添加映射路径,拦截一切请求 + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + // 返回新的CorsFilter + return new CorsFilter(source); + } +} \ No newline at end of file diff --git a/boyue-framework/src/main/java/com/boyue/framework/config/SecurityConfig.java b/boyue-framework/src/main/java/com/boyue/framework/config/SecurityConfig.java new file mode 100644 index 0000000..5873eb9 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/config/SecurityConfig.java @@ -0,0 +1,146 @@ +package com.boyue.framework.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.logout.LogoutFilter; +import org.springframework.web.filter.CorsFilter; + +import com.boyue.framework.config.properties.PermitAllUrlProperties; +import com.boyue.framework.security.filter.JwtAuthenticationTokenFilter; +import com.boyue.framework.security.handle.AuthenticationEntryPointImpl; +import com.boyue.framework.security.handle.LogoutSuccessHandlerImpl; + +/** + * spring security配置 + * + * @author Dftre + */ +@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true) +@Configuration +public class SecurityConfig { + /** + * 自定义用户认证逻辑 + */ + @Autowired + private UserDetailsService userDetailsService; + + /** + * 认证失败处理类 + */ + @Autowired + private AuthenticationEntryPointImpl unauthorizedHandler; + + /** + * 退出处理类 + */ + @Autowired + private LogoutSuccessHandlerImpl logoutSuccessHandler; + + /** + * token认证过滤器 + */ + @Autowired + private JwtAuthenticationTokenFilter authenticationTokenFilter; + + /** + * 跨域过滤器 + */ + @Autowired + private CorsFilter corsFilter; + + /** + * 允许匿名访问的地址 + */ + @Autowired + private PermitAllUrlProperties permitAllUrl; + + /** + * @return + * @throws Exception + */ + @Bean + AuthenticationManager authenticationManager() { + DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(userDetailsService); + daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder()); + return new ProviderManager(daoAuthenticationProvider); + } + + /** + * anyRequest | 匹配所有请求路径 + * access | SpringEl表达式结果为true时可以访问 + * anonymous | 匿名可以访问 + * denyAll | 用户不能访问 + * fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录) + * hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问 + * hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问 + * hasAuthority | 如果有参数,参数表示权限,则其权限可以访问 + * hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问 + * hasRole | 如果有参数,参数表示角色,则其角色可以访问 + * permitAll | 用户可以任意访问 + * rememberMe | 允许通过remember-me登录的用户访问 + * authenticated | 用户登录后可访问 + */ + @Bean + SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { + return httpSecurity + // CSRF禁用,因为不使用session + .csrf(csrf -> csrf.disable()) + // 禁用HTTP响应标头 + .headers((headersCustomizer) -> { + headersCustomizer.cacheControl(cache -> cache.disable()) + .frameOptions(options -> options.sameOrigin()); + }) + // 认证失败处理类 + .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler)) + // 基于token,所以不需要session + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + // 注解标记允许匿名访问的url + .authorizeHttpRequests((requests) -> { + permitAllUrl.getUrls().forEach(url -> requests.requestMatchers(url).permitAll()); + // 对于登录login 注册register 验证码captchaImage 允许匿名访问 + requests.requestMatchers("/login", "/register", "/captchaImage").permitAll() + // 静态资源,可匿名访问 + .requestMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", + "/profile/**") + .permitAll() + .requestMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", + "/druid/**", "/*/api-docs/**") + .permitAll() + .requestMatchers("/websocket/**").permitAll() + // 淮安市司法局页面访问路径,允许匿名访问 + .requestMatchers("/hasfj/**", "/hasfjlaw", "/hasfjcase", "/hasfjform", + "/show.html**", "/showcase.html**", "/table.html**", "/api/hasfj/**", "/api/pages**") + .permitAll() + // 除上面外的所有请求全部需要鉴权认证 + .anyRequest().authenticated(); + }) + // 添加Logout filter + .logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler)) + // 添加JWT filter + .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class) + // 添加CORS filter + .addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class) + .addFilterBefore(corsFilter, LogoutFilter.class) + .build(); + } + + /** + * 强散列哈希加密实现 + */ + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/config/ServerConfig.java b/boyue-framework/src/main/java/com/boyue/framework/config/ServerConfig.java new file mode 100644 index 0000000..65c2ea2 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/config/ServerConfig.java @@ -0,0 +1,32 @@ +package com.boyue.framework.config; + +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.stereotype.Component; +import com.boyue.common.utils.ServletUtils; + +/** + * 服务相关配置 + * + * @author boyue + */ +@Component +public class ServerConfig +{ + /** + * 获取完整的请求路径,包括:域名,端口,上下文访问路径 + * + * @return 服务地址 + */ + public String getUrl() + { + HttpServletRequest request = ServletUtils.getRequest(); + return getDomain(request); + } + + public static String getDomain(HttpServletRequest request) + { + StringBuffer url = request.getRequestURL(); + String contextPath = request.getSession().getServletContext().getContextPath(); + return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString(); + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/config/SqlSessionFactoryConfig.java b/boyue-framework/src/main/java/com/boyue/framework/config/SqlSessionFactoryConfig.java new file mode 100644 index 0000000..205cd80 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/config/SqlSessionFactoryConfig.java @@ -0,0 +1,46 @@ +package com.boyue.framework.config; + +import java.util.HashMap; +import java.util.Map; + +import javax.sql.DataSource; + +import org.apache.ibatis.session.SqlSessionFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; + +import com.boyue.common.service.mybatis.CreateSqlSessionFactory; +import com.boyue.framework.datasource.DataSourceManagement; +import com.boyue.framework.datasource.DynamicSqlSessionTemplate; + +@Configuration +public class SqlSessionFactoryConfig { + + @Autowired + CreateSqlSessionFactory createSqlSessionFactory; + + @Autowired + DynamicDataSourceProperties dataSourceProperties; + + @Autowired + DataSourceManagement dataSourceManagement; + + @Bean(name = "sqlSessionTemplate") + public DynamicSqlSessionTemplate sqlSessionTemplate(Environment env) throws Exception { + Map sqlSessionFactoryMap = new HashMap<>(); + Map targetDataSources = dataSourceManagement.getDataSourcesMap(); + for (Map.Entry entry : targetDataSources.entrySet()) { + SqlSessionFactory sessionFactory = createSqlSessionFactory.createSqlSessionFactory(env, entry.getValue()); + sqlSessionFactoryMap.put(entry.getKey(), sessionFactory); + } + SqlSessionFactory factoryMaster = sqlSessionFactoryMap.get(dataSourceProperties.getPrimary()); + if (factoryMaster == null) { + throw new RuntimeException("找不到主库配置" + dataSourceProperties.getPrimary()); + } + DynamicSqlSessionTemplate customSqlSessionTemplate = new DynamicSqlSessionTemplate(factoryMaster); + customSqlSessionTemplate.setTargetSqlSessionFactorys(sqlSessionFactoryMap); + return customSqlSessionTemplate; + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/config/ThreadPoolConfig.java b/boyue-framework/src/main/java/com/boyue/framework/config/ThreadPoolConfig.java new file mode 100644 index 0000000..6ba27bc --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/config/ThreadPoolConfig.java @@ -0,0 +1,65 @@ +package com.boyue.framework.config; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; + +import org.apache.commons.lang3.concurrent.BasicThreadFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import com.boyue.common.utils.Threads; + +/** + * 线程池配置 + * + * @author boyue + **/ +@Configuration +public class ThreadPoolConfig +{ + // 核心线程池大小 + private int corePoolSize = 50; + + // 最大可创建的线程数 + private int maxPoolSize = 200; + + // 队列最大长度 + private int queueCapacity = 1000; + + // 线程池维护线程所允许的空闲时间 + private int keepAliveSeconds = 300; + + @Bean(name = "threadPoolTaskExecutor") + public ThreadPoolTaskExecutor threadPoolTaskExecutor() + { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setMaxPoolSize(maxPoolSize); + executor.setCorePoolSize(corePoolSize); + executor.setQueueCapacity(queueCapacity); + executor.setKeepAliveSeconds(keepAliveSeconds); + // 线程池对拒绝任务(无线程可用)的处理策略 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + return executor; + } + + /** + * 执行周期性或定时任务 + */ + @Bean(name = "scheduledExecutorService") + protected ScheduledExecutorService scheduledExecutorService() + { + return new ScheduledThreadPoolExecutor(corePoolSize, + new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(), + new ThreadPoolExecutor.CallerRunsPolicy()) + { + @Override + protected void afterExecute(Runnable r, Throwable t) + { + super.afterExecute(r, t); + Threads.printException(r, t); + } + }; + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/config/properties/DruidProperties.java b/boyue-framework/src/main/java/com/boyue/framework/config/properties/DruidProperties.java new file mode 100644 index 0000000..02977b3 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/config/properties/DruidProperties.java @@ -0,0 +1,195 @@ +package com.boyue.framework.config.properties; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +import com.alibaba.druid.pool.DruidDataSource; + +/** + * druid 配置属性 + * + * @author boyue + */ +@Configuration +public class DruidProperties { + @Value("${spring.datasource.druid.initialSize}") + private int initialSize; + + @Value("${spring.datasource.druid.minIdle}") + private int minIdle; + + @Value("${spring.datasource.druid.maxActive}") + private int maxActive; + + @Value("${spring.datasource.druid.maxWait}") + private int maxWait; + + @Value("${spring.datasource.druid.connectTimeout}") + private int connectTimeout; + + @Value("${spring.datasource.druid.socketTimeout}") + private int socketTimeout; + + @Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}") + private int timeBetweenEvictionRunsMillis; + + @Value("${spring.datasource.druid.minEvictableIdleTimeMillis}") + private int minEvictableIdleTimeMillis; + + @Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}") + private int maxEvictableIdleTimeMillis; + + @Value("${spring.datasource.druid.validationQuery}") + private String validationQuery; + + @Value("${spring.datasource.druid.testWhileIdle}") + private boolean testWhileIdle; + + @Value("${spring.datasource.druid.testOnBorrow}") + private boolean testOnBorrow; + + @Value("${spring.datasource.druid.testOnReturn}") + private boolean testOnReturn; + + public DruidDataSource dataSource(DruidDataSource datasource) { + /** 配置初始化大小、最小、最大 */ + datasource.setInitialSize(initialSize); + datasource.setMaxActive(maxActive); + datasource.setMinIdle(minIdle); + + /** 配置获取连接等待超时的时间 */ + datasource.setMaxWait(maxWait); + + /** 配置驱动连接超时时间,检测数据库建立连接的超时时间,单位是毫秒 */ + datasource.setConnectTimeout(connectTimeout); + + /** 配置网络超时时间,等待数据库操作完成的网络超时时间,单位是毫秒 */ + datasource.setSocketTimeout(socketTimeout); + + /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */ + datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); + + /** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */ + datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); + datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis); + + /** + * 用来检测连接是否有效的sql,要求是一个查询语句,常用select + * 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 + */ + datasource.setValidationQuery(validationQuery); + /** + * 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 + */ + datasource.setTestWhileIdle(testWhileIdle); + /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */ + datasource.setTestOnBorrow(testOnBorrow); + /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */ + datasource.setTestOnReturn(testOnReturn); + return datasource; + } + + public int getInitialSize() { + return initialSize; + } + + public void setInitialSize(int initialSize) { + this.initialSize = initialSize; + } + + public int getMinIdle() { + return minIdle; + } + + public void setMinIdle(int minIdle) { + this.minIdle = minIdle; + } + + public int getMaxActive() { + return maxActive; + } + + public void setMaxActive(int maxActive) { + this.maxActive = maxActive; + } + + public int getMaxWait() { + return maxWait; + } + + public void setMaxWait(int maxWait) { + this.maxWait = maxWait; + } + + public int getConnectTimeout() { + return connectTimeout; + } + + public void setConnectTimeout(int connectTimeout) { + this.connectTimeout = connectTimeout; + } + + public int getSocketTimeout() { + return socketTimeout; + } + + public void setSocketTimeout(int socketTimeout) { + this.socketTimeout = socketTimeout; + } + + public int getTimeBetweenEvictionRunsMillis() { + return timeBetweenEvictionRunsMillis; + } + + public void setTimeBetweenEvictionRunsMillis(int timeBetweenEvictionRunsMillis) { + this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis; + } + + public int getMinEvictableIdleTimeMillis() { + return minEvictableIdleTimeMillis; + } + + public void setMinEvictableIdleTimeMillis(int minEvictableIdleTimeMillis) { + this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis; + } + + public int getMaxEvictableIdleTimeMillis() { + return maxEvictableIdleTimeMillis; + } + + public void setMaxEvictableIdleTimeMillis(int maxEvictableIdleTimeMillis) { + this.maxEvictableIdleTimeMillis = maxEvictableIdleTimeMillis; + } + + public String getValidationQuery() { + return validationQuery; + } + + public void setValidationQuery(String validationQuery) { + this.validationQuery = validationQuery; + } + + public boolean isTestWhileIdle() { + return testWhileIdle; + } + + public void setTestWhileIdle(boolean testWhileIdle) { + this.testWhileIdle = testWhileIdle; + } + + public boolean isTestOnBorrow() { + return testOnBorrow; + } + + public void setTestOnBorrow(boolean testOnBorrow) { + this.testOnBorrow = testOnBorrow; + } + + public boolean isTestOnReturn() { + return testOnReturn; + } + + public void setTestOnReturn(boolean testOnReturn) { + this.testOnReturn = testOnReturn; + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/config/properties/PermitAllUrlProperties.java b/boyue-framework/src/main/java/com/boyue/framework/config/properties/PermitAllUrlProperties.java new file mode 100644 index 0000000..480e0a7 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/config/properties/PermitAllUrlProperties.java @@ -0,0 +1,89 @@ +package com.boyue.framework.config.properties; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.RegExUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +import com.boyue.common.annotation.Anonymous; +import com.boyue.common.utils.spring.SpringUtils; + +/** + * 设置Anonymous注解允许匿名访问的url + * + * @author boyue + */ +@Configuration +public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware { + private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}"); + + private ApplicationContext applicationContext; + + private List urls = new ArrayList<>(); + + public String ASTERISK = "*"; + + static class Pair { + public RequestMappingInfo first; + public Anonymous second; + + public Pair(RequestMappingInfo first, Anonymous second) { + this.first = first; + this.second = second; + } + } + + @Override + public void afterPropertiesSet() { + RequestMappingHandlerMapping mapping = applicationContext.getBean("requestMappingHandlerMapping",RequestMappingHandlerMapping.class); + Map map = mapping.getHandlerMethods(); + String matching = SpringUtils.getRequiredProperty("spring.mvc.pathmatch.matching-strategy").toLowerCase(); + map.keySet() + .stream() + .flatMap(info -> { + HandlerMethod handlerMethod = map.get(info); + // 获取方法上边的注解 替代path variable 为 * + Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class); + // 获取类上边的注解, 替代path variable 为 * + Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class); + return Arrays.stream(new Pair[] { new Pair(info, method), new Pair(info, controller) }); + }) + .filter(pair -> pair.second != null) + .flatMap(pair -> switch (matching) { + case "ant_path_matcher" -> + Objects.requireNonNull(pair.first.getPatternsCondition().getPatterns()).stream(); + case "path_pattern_parser" -> + Objects.requireNonNull(pair.first.getPathPatternsCondition().getPatternValues()) + .stream(); + default -> Objects.requireNonNull(pair.first.getPatternsCondition().getPatterns()).stream(); + }) + .map(url -> RegExUtils.replaceAll(url, PATTERN, ASTERISK)) + .forEach(urls::add); + } + + @Override + public void setApplicationContext(ApplicationContext context) throws BeansException { + this.applicationContext = context; + } + + public List getUrls() { + return urls; + } + + public void setUrls(List urls) { + this.urls = urls; + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/datasource/DataSourceCreate.java b/boyue-framework/src/main/java/com/boyue/framework/datasource/DataSourceCreate.java new file mode 100644 index 0000000..1d941d7 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/datasource/DataSourceCreate.java @@ -0,0 +1,43 @@ +package com.boyue.framework.datasource; + +import java.util.Properties; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.pool.xa.DruidXADataSource; +// import com.alibaba.druid.pool.xa.DruidXADataSource; +import com.boyue.common.service.datasource.CreateDataSource; +import com.boyue.framework.config.DruidConfig; +import com.boyue.framework.config.DynamicDataSourceProperties; + +@Configuration +public class DataSourceCreate implements CreateDataSource { + + @Autowired + private DynamicDataSourceProperties properties; + + @Autowired + private DruidConfig druidConfig; + + @Value("${spring.datasource.dynamic.xa}") + private boolean xa; + + public DataSource createDataSource(String name, Properties prop) { + DruidDataSource dataSource = null; + if (xa) { + dataSource = new DruidXADataSource(); + } else { + dataSource = new DruidDataSource(); + } + druidConfig.getDruidDataSources().add(dataSource); + dataSource.setConnectProperties(prop); + properties.setProperties(dataSource, prop); + return dataSource; + } + +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/datasource/DataSourceManagement.java b/boyue-framework/src/main/java/com/boyue/framework/datasource/DataSourceManagement.java new file mode 100644 index 0000000..afa18f9 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/datasource/DataSourceManagement.java @@ -0,0 +1,104 @@ +package com.boyue.framework.datasource; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import javax.sql.CommonDataSource; +import javax.sql.DataSource; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +import com.boyue.common.service.datasource.AfterCreateDataSource; +import com.boyue.common.service.datasource.CreateDataSource; +import com.boyue.framework.config.DynamicDataSourceProperties; + +@Configuration +public class DataSourceManagement implements InitializingBean { + protected final Logger logger = LoggerFactory.getLogger(DataSourceManagement.class); + private Map targetDataSources = new HashMap<>(); + + @Autowired + private DynamicDataSourceProperties dataSourceProperties; + + @Autowired + private CreateDataSource c; + + @Autowired(required = false) + private AfterCreateDataSource afterCreateDataSource; + + @Bean(name = "dynamicDataSource") + @Primary + public DynamicDataSource dataSource() { + Map objectMap = new HashMap<>(); + Map targetDataSources = this.getDataSourcesMap(); + for (Map.Entry entry : targetDataSources.entrySet()) { + objectMap.put(entry.getKey(), entry.getValue()); + } + return new DynamicDataSource(targetDataSources.get(dataSourceProperties.getPrimary()), objectMap); + } + + public void validateDataSource(DataSource dataSource) { + try (Connection conn = dataSource.getConnection()) { + String validationQuery = "SELECT 1"; + try (Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(validationQuery)) { + if (!(rs.next() && rs.getInt(1) == 1)) { + throw new RuntimeException("数据源连接验证失败:查询结果不正确"); + } + } + } catch (SQLException e) { + throw new RuntimeException("数据源连接验证失败", e); + } + } + + @Override + public void afterPropertiesSet() throws Exception { + dataSourceProperties.getDatasource() + .forEach((name, props) -> { + Properties properties = dataSourceProperties.build(props); + CommonDataSource commonDataSource = c.createDataSource(name, properties); + if (afterCreateDataSource != null) { + afterCreateDataSource.afterCreateDataSource(name, properties, commonDataSource); + } + DataSource dataSource = (DataSource) commonDataSource; + logger.info("数据源:{} 校验中.......", name); + // 计时 + long start = System.currentTimeMillis(); + validateDataSource(dataSource); + logger.info("数据源:{} 链接成功,耗时:{}ms", name, System.currentTimeMillis() - start); + this.putDataSource(name, dataSource); + }); + } + + public DataSource getPrimaryDataSource(){ + return targetDataSources.get(dataSourceProperties.getPrimary()); + } + + public DataSource getDataSource(String name) { + return targetDataSources.get(name); + } + + public Collection getDataSources() { + return targetDataSources.values(); + } + + public Map getDataSourcesMap() { + return targetDataSources; + } + + public void putDataSource(String name, DataSource dataSource) { + targetDataSources.put(name, dataSource); + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/datasource/DynamicDataSource.java b/boyue-framework/src/main/java/com/boyue/framework/datasource/DynamicDataSource.java new file mode 100644 index 0000000..479ad8b --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/datasource/DynamicDataSource.java @@ -0,0 +1,28 @@ +package com.boyue.framework.datasource; + +import java.util.Map; + +import javax.sql.CommonDataSource; + +import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; + +/** + * 动态数据源 + * + * @author boyue + */ +public class DynamicDataSource extends AbstractRoutingDataSource +{ + public DynamicDataSource(CommonDataSource defaultTargetDataSource, Map targetDataSources) + { + super.setDefaultTargetDataSource(defaultTargetDataSource); + super.setTargetDataSources(targetDataSources); + super.afterPropertiesSet(); + } + + @Override + protected Object determineCurrentLookupKey() + { + return DynamicDataSourceContextHolder.getDataSourceType(); + } +} \ No newline at end of file diff --git a/boyue-framework/src/main/java/com/boyue/framework/datasource/DynamicDataSourceContextHolder.java b/boyue-framework/src/main/java/com/boyue/framework/datasource/DynamicDataSourceContextHolder.java new file mode 100644 index 0000000..c47b3a8 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/datasource/DynamicDataSourceContextHolder.java @@ -0,0 +1,60 @@ +package com.boyue.framework.datasource; + +import java.util.Objects; +import java.util.Stack; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 数据源切换处理 + * + * @author boyue + */ +public class DynamicDataSourceContextHolder { + public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class); + + /** + * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本, + * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 + */ + private static final ThreadLocal> CONTEXT_HOLDER = new ThreadLocal<>(); + + /** + * 设置数据源的变量 + */ + public static void setDataSourceType(String dsType) { + log.info("切换到{}数据源", dsType); + Stack stack = CONTEXT_HOLDER.get(); + if (Objects.isNull(stack)) { + stack = new Stack<>(); + CONTEXT_HOLDER.set(stack); + } + stack.push(dsType); + } + + /** + * 获得数据源的变量 + */ + public static String getDataSourceType() { + Stack stack = CONTEXT_HOLDER.get(); + if (Objects.isNull(stack)) { + return null; + } else { + return stack.peek(); + } + } + + /** + * 清空数据源变量 + */ + public static void clearDataSourceType() { + Stack stack = CONTEXT_HOLDER.get(); + if (Objects.nonNull(stack) && !stack.isEmpty()) { + stack.pop(); + if (stack.isEmpty()) { + CONTEXT_HOLDER.remove(); + } + } + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/datasource/DynamicSqlSessionTemplate.java b/boyue-framework/src/main/java/com/boyue/framework/datasource/DynamicSqlSessionTemplate.java new file mode 100644 index 0000000..8aa016e --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/datasource/DynamicSqlSessionTemplate.java @@ -0,0 +1,311 @@ +package com.boyue.framework.datasource; + +import static java.lang.reflect.Proxy.*; +import static org.apache.ibatis.reflection.ExceptionUtil.*; +import static org.mybatis.spring.SqlSessionUtils.*; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.sql.Connection; +import java.util.List; +import java.util.Map; + +import org.apache.ibatis.exceptions.PersistenceException; +import org.apache.ibatis.executor.BatchResult; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.ExecutorType; +import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.session.RowBounds; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.MyBatisExceptionTranslator; +import org.mybatis.spring.SqlSessionTemplate; +import org.springframework.dao.support.PersistenceExceptionTranslator; + +/** + * 自定义SqlSessionTemplate,动态切换数据源 + * + * @author boyue + */ +public class DynamicSqlSessionTemplate extends SqlSessionTemplate { + private final SqlSessionFactory sqlSessionFactory; + private final ExecutorType executorType; + private final SqlSession sqlSessionProxy; + private final PersistenceExceptionTranslator exceptionTranslator; + private Map targetSqlSessionFactorys; + private SqlSessionFactory defaultTargetSqlSessionFactory; + + public DynamicSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { + this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType()); + } + + public DynamicSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) { + this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator( + sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true)); + } + + public DynamicSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, + PersistenceExceptionTranslator exceptionTranslator) { + super(sqlSessionFactory, executorType, exceptionTranslator); + this.sqlSessionFactory = sqlSessionFactory; + this.executorType = executorType; + this.exceptionTranslator = exceptionTranslator; + this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(), + new Class[] { SqlSession.class }, new SqlSessionInterceptor()); + this.defaultTargetSqlSessionFactory = sqlSessionFactory; + } + + public void setTargetSqlSessionFactorys(Map targetSqlSessionFactorys) { + this.targetSqlSessionFactorys = targetSqlSessionFactorys; + } + + public void setDefaultTargetSqlSessionFactory(SqlSessionFactory defaultTargetSqlSessionFactory) { + this.defaultTargetSqlSessionFactory = defaultTargetSqlSessionFactory; + } + + @Override + public SqlSessionFactory getSqlSessionFactory() { + SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys + .get(DynamicDataSourceContextHolder.getDataSourceType()); + if (targetSqlSessionFactory != null) { + return targetSqlSessionFactory; + } else if (defaultTargetSqlSessionFactory != null) { + return defaultTargetSqlSessionFactory; + } + return this.sqlSessionFactory; + } + + @Override + public Configuration getConfiguration() { + return this.getSqlSessionFactory().getConfiguration(); + } + + public ExecutorType getExecutorType() { + return this.executorType; + } + + public PersistenceExceptionTranslator getPersistenceExceptionTranslator() { + return this.exceptionTranslator; + } + + /** + * {@inheritDoc} + */ + public T selectOne(String statement) { + return this.sqlSessionProxy.selectOne(statement); + } + + /** + * {@inheritDoc} + */ + public T selectOne(String statement, Object parameter) { + return this.sqlSessionProxy.selectOne(statement, parameter); + } + + /** + * {@inheritDoc} + */ + public Map selectMap(String statement, String mapKey) { + return this.sqlSessionProxy.selectMap(statement, mapKey); + } + + /** + * {@inheritDoc} + */ + public Map selectMap(String statement, Object parameter, String mapKey) { + return this.sqlSessionProxy.selectMap(statement, parameter, mapKey); + } + + /** + * {@inheritDoc} + */ + public Map selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) { + return this.sqlSessionProxy.selectMap(statement, parameter, mapKey, rowBounds); + } + + /** + * {@inheritDoc} + */ + public List selectList(String statement) { + return this.sqlSessionProxy.selectList(statement); + } + + /** + * {@inheritDoc} + */ + public List selectList(String statement, Object parameter) { + return this.sqlSessionProxy.selectList(statement, parameter); + } + + /** + * {@inheritDoc} + */ + public List selectList(String statement, Object parameter, RowBounds rowBounds) { + return this.sqlSessionProxy.selectList(statement, parameter, rowBounds); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("rawtypes") + public void select(String statement, ResultHandler handler) { + this.sqlSessionProxy.select(statement, handler); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("rawtypes") + public void select(String statement, Object parameter, ResultHandler handler) { + this.sqlSessionProxy.select(statement, parameter, handler); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("rawtypes") + public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { + this.sqlSessionProxy.select(statement, parameter, rowBounds, handler); + } + + /** + * {@inheritDoc} + */ + public int insert(String statement) { + return this.sqlSessionProxy.insert(statement); + } + + /** + * {@inheritDoc} + */ + public int insert(String statement, Object parameter) { + return this.sqlSessionProxy.insert(statement, parameter); + } + + /** + * {@inheritDoc} + */ + public int update(String statement) { + return this.sqlSessionProxy.update(statement); + } + + /** + * {@inheritDoc} + */ + public int update(String statement, Object parameter) { + return this.sqlSessionProxy.update(statement, parameter); + } + + /** + * {@inheritDoc} + */ + public int delete(String statement) { + return this.sqlSessionProxy.delete(statement); + } + + /** + * {@inheritDoc} + */ + public int delete(String statement, Object parameter) { + return this.sqlSessionProxy.delete(statement, parameter); + } + + /** + * {@inheritDoc} + */ + public T getMapper(Class type) { + return getConfiguration().getMapper(type, this); + } + + /** + * {@inheritDoc} + */ + public void commit() { + throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession"); + } + + /** + * {@inheritDoc} + */ + public void commit(boolean force) { + throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession"); + } + + /** + * {@inheritDoc} + */ + public void rollback() { + throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession"); + } + + /** + * {@inheritDoc} + */ + public void rollback(boolean force) { + throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession"); + } + + /** + * {@inheritDoc} + */ + public void close() { + throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession"); + } + + /** + * {@inheritDoc} + */ + public void clearCache() { + this.sqlSessionProxy.clearCache(); + } + + /** + * {@inheritDoc} + */ + public Connection getConnection() { + return this.sqlSessionProxy.getConnection(); + } + + /** + * {@inheritDoc} + * + * @since 1.0.2 + */ + public List flushStatements() { + return this.sqlSessionProxy.flushStatements(); + } + + /** + * Proxy needed to route MyBatis method calls to the proper SqlSession got from + * Spring's Transaction Manager It also + * unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass + * a {@code PersistenceException} to + * the {@code PersistenceExceptionTranslator}. + */ + private class SqlSessionInterceptor implements InvocationHandler { + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + final SqlSession sqlSession = getSqlSession(DynamicSqlSessionTemplate.this.getSqlSessionFactory(), + DynamicSqlSessionTemplate.this.executorType, DynamicSqlSessionTemplate.this.exceptionTranslator); + try { + Object result = method.invoke(sqlSession, args); + if (!isSqlSessionTransactional(sqlSession, DynamicSqlSessionTemplate.this.getSqlSessionFactory())) { + sqlSession.commit(true); + } + return result; + } catch (Throwable t) { + Throwable unwrapped = unwrapThrowable(t); + if (DynamicSqlSessionTemplate.this.exceptionTranslator != null + && unwrapped instanceof PersistenceException) { + Throwable translated = DynamicSqlSessionTemplate.this.exceptionTranslator + .translateExceptionIfPossible((PersistenceException) unwrapped); + if (translated != null) { + unwrapped = translated; + } + } + throw unwrapped; + } finally { + closeSqlSession(sqlSession, DynamicSqlSessionTemplate.this.getSqlSessionFactory()); + } + } + } +} \ No newline at end of file diff --git a/boyue-framework/src/main/java/com/boyue/framework/datasource/DynamicTransactionManager.java b/boyue-framework/src/main/java/com/boyue/framework/datasource/DynamicTransactionManager.java new file mode 100644 index 0000000..37e7278 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/datasource/DynamicTransactionManager.java @@ -0,0 +1,28 @@ +package com.boyue.framework.datasource; + +import java.util.Objects; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.support.JdbcTransactionManager; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +@Component +@EnableTransactionManagement(proxyTargetClass = true) +public class DynamicTransactionManager extends JdbcTransactionManager { + + @Autowired + DataSourceManagement dataSourceManagement; + + @Override + public DataSource getDataSource() { + DataSource dataSource = dataSourceManagement.getDataSource(DynamicDataSourceContextHolder.getDataSourceType()); + if (!Objects.isNull(dataSource)) { + return dataSource; + } else { + return dataSourceManagement.getPrimaryDataSource(); + } + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/interceptor/RepeatSubmitInterceptor.java b/boyue-framework/src/main/java/com/boyue/framework/interceptor/RepeatSubmitInterceptor.java new file mode 100644 index 0000000..bca94ba --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/interceptor/RepeatSubmitInterceptor.java @@ -0,0 +1,55 @@ +package com.boyue.framework.interceptor; + +import java.lang.reflect.Method; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; +import com.alibaba.fastjson2.JSON; +import com.boyue.common.annotation.RepeatSubmit; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.utils.ServletUtils; + +/** + * 防止重复提交拦截器 + * + * @author boyue + */ +@Component +public abstract class RepeatSubmitInterceptor implements HandlerInterceptor +{ + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception + { + if (handler instanceof HandlerMethod) + { + HandlerMethod handlerMethod = (HandlerMethod) handler; + Method method = handlerMethod.getMethod(); + RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class); + if (annotation != null) + { + if (this.isRepeatSubmit(request, annotation)) + { + AjaxResult ajaxResult = AjaxResult.error(annotation.message()); + ServletUtils.renderString(response, JSON.toJSONString(ajaxResult)); + return false; + } + } + return true; + } + else + { + return true; + } + } + + /** + * 验证是否重复提交由子类实现具体的防重复提交的规则 + * + * @param request + * @return + * @throws Exception + */ + public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation); +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/interceptor/impl/SameUrlDataInterceptor.java b/boyue-framework/src/main/java/com/boyue/framework/interceptor/impl/SameUrlDataInterceptor.java new file mode 100644 index 0000000..5460fa4 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/interceptor/impl/SameUrlDataInterceptor.java @@ -0,0 +1,104 @@ +package com.boyue.framework.interceptor.impl; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import com.alibaba.fastjson2.JSON; +import com.boyue.common.annotation.RepeatSubmit; +import com.boyue.common.constant.CacheConstants; +import com.boyue.common.filter.RepeatedlyRequestWrapper; +import com.boyue.common.utils.CacheUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.http.HttpHelper; +import com.boyue.framework.interceptor.RepeatSubmitInterceptor; + +import jakarta.servlet.http.HttpServletRequest; + +/** + * 判断请求url和数据是否和上一次相同, + * 如果和上次相同,则是重复提交表单。 有效时间为10秒内。 + * + * @author boyue + */ +@Component +public class SameUrlDataInterceptor extends RepeatSubmitInterceptor +{ + public final String REPEAT_PARAMS = "repeatParams"; + + public final String REPEAT_TIME = "repeatTime"; + + // 令牌自定义标识 + @Value("${token.header}") + private String header; + + @SuppressWarnings("unchecked") + @Override + public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) + { + String nowParams = ""; + if (request instanceof RepeatedlyRequestWrapper) + { + RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request; + nowParams = HttpHelper.getBodyString(repeatedlyRequest); + } + // body参数为空,获取Parameter的数据 + if (StringUtils.isEmpty(nowParams)) + { + nowParams = JSON.toJSONString(request.getParameterMap()); + } + Map nowDataMap = new HashMap(); + nowDataMap.put(REPEAT_PARAMS, nowParams); + nowDataMap.put(REPEAT_TIME, System.currentTimeMillis()); + // 请求地址(作为存放cache的key值) + String url = request.getRequestURI(); + // 唯一值(没有消息头则使用请求地址) + String submitKey = StringUtils.trimToEmpty(request.getHeader(header)); + // 唯一标识(指定key + url + 消息头) + String cacheRepeatKey = url + submitKey; + Object sessionObj = CacheUtils.get(CacheConstants.REPEAT_SUBMIT_KEY, cacheRepeatKey); + if (sessionObj != null) + { + Map sessionMap = (Map) sessionObj; + if (sessionMap.containsKey(url)) + { + Map preDataMap = (Map) sessionMap.get(url); + if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, annotation.interval())) + { + return true; + } + } + } + Map cacheMap = new HashMap(); + cacheMap.put(url, nowDataMap); + CacheUtils.put(CacheConstants.REPEAT_SUBMIT_KEY, cacheRepeatKey, cacheMap, annotation.interval(), TimeUnit.MILLISECONDS); + return false; + } + + /** + * 判断参数是否相同 + */ + private boolean compareParams(Map nowMap, Map preMap) + { + String nowParams = (String) nowMap.get(REPEAT_PARAMS); + String preParams = (String) preMap.get(REPEAT_PARAMS); + return nowParams.equals(preParams); + } + + /** + * 判断两次间隔时间 + */ + private boolean compareTime(Map nowMap, Map preMap, int interval) + { + long time1 = (Long) nowMap.get(REPEAT_TIME); + long time2 = (Long) preMap.get(REPEAT_TIME); + if ((time1 - time2) < interval) + { + return true; + } + return false; + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/manager/AsyncManager.java b/boyue-framework/src/main/java/com/boyue/framework/manager/AsyncManager.java new file mode 100644 index 0000000..d69760f --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/manager/AsyncManager.java @@ -0,0 +1,55 @@ +package com.boyue.framework.manager; + +import java.util.TimerTask; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import com.boyue.common.utils.Threads; +import com.boyue.common.utils.spring.SpringUtils; + +/** + * 异步任务管理器 + * + * @author boyue + */ +public class AsyncManager +{ + /** + * 操作延迟10毫秒 + */ + private final int OPERATE_DELAY_TIME = 10; + + /** + * 异步操作任务调度线程池 + */ + private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService"); + + /** + * 单例模式 + */ + private AsyncManager(){} + + private static AsyncManager me = new AsyncManager(); + + public static AsyncManager me() + { + return me; + } + + /** + * 执行任务 + * + * @param task 任务 + */ + public void execute(TimerTask task) + { + executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS); + } + + /** + * 停止任务线程池 + */ + public void shutdown() + { + Threads.shutdownAndAwaitTermination(executor); + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/manager/ShutdownManager.java b/boyue-framework/src/main/java/com/boyue/framework/manager/ShutdownManager.java new file mode 100644 index 0000000..8195691 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/manager/ShutdownManager.java @@ -0,0 +1,43 @@ +package com.boyue.framework.manager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import com.boyue.common.utils.http.HttpUtils; + +import jakarta.annotation.PreDestroy; + +/** + * 确保应用退出时能关闭后台线程 + * + * @author boyue + */ +@Component +public class ShutdownManager +{ + private static final Logger logger = LoggerFactory.getLogger("sys-user"); + + @PreDestroy + public void destroy() + { + shutdownAsyncManager(); + HttpUtils.shutdown(); + } + + /** + * 停止异步执行任务 + */ + private void shutdownAsyncManager() + { + try + { + logger.info("====关闭后台任务任务线程池===="); + AsyncManager.me().shutdown(); + } + catch (Exception e) + { + logger.error(e.getMessage(), e); + } + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/manager/factory/AsyncFactory.java b/boyue-framework/src/main/java/com/boyue/framework/manager/factory/AsyncFactory.java new file mode 100644 index 0000000..7156026 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/manager/factory/AsyncFactory.java @@ -0,0 +1,102 @@ +package com.boyue.framework.manager.factory; + +import java.util.TimerTask; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.boyue.common.constant.Constants; +import com.boyue.common.utils.LogUtils; +import com.boyue.common.utils.ServletUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.ip.AddressUtils; +import com.boyue.common.utils.ip.IpUtils; +import com.boyue.common.utils.spring.SpringUtils; +import com.boyue.system.domain.SysLogininfor; +import com.boyue.system.domain.SysOperLog; +import com.boyue.system.service.ISysLogininforService; +import com.boyue.system.service.ISysOperLogService; +import eu.bitwalker.useragentutils.UserAgent; + +/** + * 异步工厂(产生任务用) + * + * @author boyue + */ +public class AsyncFactory +{ + private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user"); + + /** + * 记录登录信息 + * + * @param username 用户名 + * @param status 状态 + * @param message 消息 + * @param args 列表 + * @return 任务task + */ + public static TimerTask recordLogininfor(final String username, final String status, final String message, + final Object... args) + { + final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); + final String ip = IpUtils.getIpAddr(); + return new TimerTask() + { + @Override + public void run() + { + String address = AddressUtils.getRealAddressByIP(ip); + StringBuilder s = new StringBuilder(); + s.append(LogUtils.getBlock(ip)); + s.append(address); + s.append(LogUtils.getBlock(username)); + s.append(LogUtils.getBlock(status)); + s.append(LogUtils.getBlock(message)); + // 打印信息到日志 + sys_user_logger.info(s.toString(), args); + // 获取客户端操作系统 + String os = userAgent.getOperatingSystem().getName(); + // 获取客户端浏览器 + String browser = userAgent.getBrowser().getName(); + // 封装对象 + SysLogininfor logininfor = new SysLogininfor(); + logininfor.setUserName(username); + logininfor.setIpaddr(ip); + logininfor.setLoginLocation(address); + logininfor.setBrowser(browser); + logininfor.setOs(os); + logininfor.setMsg(message); + // 日志状态 + if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) + { + logininfor.setStatus(Constants.SUCCESS); + } + else if (Constants.LOGIN_FAIL.equals(status)) + { + logininfor.setStatus(Constants.FAIL); + } + // 插入数据 + SpringUtils.getBean(ISysLogininforService.class).insertLogininfor(logininfor); + } + }; + } + + /** + * 操作日志记录 + * + * @param operLog 操作日志信息 + * @return 任务task + */ + public static TimerTask recordOper(final SysOperLog operLog) + { + return new TimerTask() + { + @Override + public void run() + { + // 远程查询操作地点 + operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp())); + SpringUtils.getBean(ISysOperLogService.class).insertOperlog(operLog); + } + }; + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/security/context/AuthenticationContextHolder.java b/boyue-framework/src/main/java/com/boyue/framework/security/context/AuthenticationContextHolder.java new file mode 100644 index 0000000..95d7e92 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/security/context/AuthenticationContextHolder.java @@ -0,0 +1,28 @@ +package com.boyue.framework.security.context; + +import org.springframework.security.core.Authentication; + +/** + * 身份验证信息 + * + * @author boyue + */ +public class AuthenticationContextHolder +{ + private static final ThreadLocal contextHolder = new ThreadLocal<>(); + + public static Authentication getContext() + { + return contextHolder.get(); + } + + public static void setContext(Authentication context) + { + contextHolder.set(context); + } + + public static void clearContext() + { + contextHolder.remove(); + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/security/context/PermissionContextHolder.java b/boyue-framework/src/main/java/com/boyue/framework/security/context/PermissionContextHolder.java new file mode 100644 index 0000000..4afadf1 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/security/context/PermissionContextHolder.java @@ -0,0 +1,27 @@ +package com.boyue.framework.security.context; + +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import com.boyue.common.core.text.Convert; + +/** + * 权限信息 + * + * @author boyue + */ +public class PermissionContextHolder +{ + private static final String PERMISSION_CONTEXT_ATTRIBUTES = "PERMISSION_CONTEXT"; + + public static void setContext(String permission) + { + RequestContextHolder.currentRequestAttributes().setAttribute(PERMISSION_CONTEXT_ATTRIBUTES, permission, + RequestAttributes.SCOPE_REQUEST); + } + + public static String getContext() + { + return Convert.toStr(RequestContextHolder.currentRequestAttributes().getAttribute(PERMISSION_CONTEXT_ATTRIBUTES, + RequestAttributes.SCOPE_REQUEST)); + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/security/filter/JwtAuthenticationTokenFilter.java b/boyue-framework/src/main/java/com/boyue/framework/security/filter/JwtAuthenticationTokenFilter.java new file mode 100644 index 0000000..803db64 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/security/filter/JwtAuthenticationTokenFilter.java @@ -0,0 +1,44 @@ +package com.boyue.framework.security.filter; + +import java.io.IOException; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import com.boyue.common.core.domain.model.LoginUser; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.framework.web.service.TokenService; + +/** + * token过滤器 验证token有效性 + * + * @author boyue + */ +@Component +public class JwtAuthenticationTokenFilter extends OncePerRequestFilter +{ + @Autowired + private TokenService tokenService; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException + { + LoginUser loginUser = tokenService.getLoginUser(request); + if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) + { + tokenService.verifyToken(loginUser); + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authenticationToken); + } + chain.doFilter(request, response); + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/security/handle/AuthenticationEntryPointImpl.java b/boyue-framework/src/main/java/com/boyue/framework/security/handle/AuthenticationEntryPointImpl.java new file mode 100644 index 0000000..0b2380d --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/security/handle/AuthenticationEntryPointImpl.java @@ -0,0 +1,34 @@ +package com.boyue.framework.security.handle; + +import java.io.IOException; +import java.io.Serializable; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; +import com.alibaba.fastjson2.JSON; +import com.boyue.common.constant.HttpStatus; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.utils.ServletUtils; +import com.boyue.common.utils.StringUtils; + +/** + * 认证失败处理类 返回未授权 + * + * @author boyue + */ +@Component +public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable +{ + private static final long serialVersionUID = -8970718410437077606L; + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) + throws IOException + { + int code = HttpStatus.UNAUTHORIZED; + String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI()); + ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg))); + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/security/handle/LogoutSuccessHandlerImpl.java b/boyue-framework/src/main/java/com/boyue/framework/security/handle/LogoutSuccessHandlerImpl.java new file mode 100644 index 0000000..915e4de --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/security/handle/LogoutSuccessHandlerImpl.java @@ -0,0 +1,55 @@ +package com.boyue.framework.security.handle; + +import java.io.IOException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; + +import com.alibaba.fastjson2.JSON; +import com.boyue.common.constant.Constants; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.domain.model.LoginUser; +import com.boyue.common.utils.MessageUtils; +import com.boyue.common.utils.ServletUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.framework.manager.AsyncManager; +import com.boyue.framework.manager.factory.AsyncFactory; +import com.boyue.framework.web.service.TokenService; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 自定义退出处理类 返回成功 + * + * @author boyue + */ +@Configuration +public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler { + @Autowired + private TokenService tokenService; + + /** + * 退出处理 + * + * @return + */ + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) + throws IOException, ServletException { + LoginUser loginUser = tokenService.getLoginUser(request); + if (StringUtils.isNotNull(loginUser)) { + String userName = loginUser.getUsername(); + // 删除用户缓存记录 + tokenService.delLoginUser(loginUser.getToken()); + // 记录用户退出日志 + AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, + MessageUtils.message("user.logout.success"))); + } + ServletUtils.renderString(response, + JSON.toJSONString(AjaxResult.success(MessageUtils.message("user.logout.success")))); + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/web/domain/Server.java b/boyue-framework/src/main/java/com/boyue/framework/web/domain/Server.java new file mode 100644 index 0000000..1fe97c8 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/web/domain/Server.java @@ -0,0 +1,240 @@ +package com.boyue.framework.web.domain; + +import java.net.UnknownHostException; +import java.util.LinkedList; +import java.util.List; +import java.util.Properties; +import com.boyue.common.utils.Arith; +import com.boyue.common.utils.ip.IpUtils; +import com.boyue.framework.web.domain.server.Cpu; +import com.boyue.framework.web.domain.server.Jvm; +import com.boyue.framework.web.domain.server.Mem; +import com.boyue.framework.web.domain.server.Sys; +import com.boyue.framework.web.domain.server.SysFile; +import oshi.SystemInfo; +import oshi.hardware.CentralProcessor; +import oshi.hardware.CentralProcessor.TickType; +import oshi.hardware.GlobalMemory; +import oshi.hardware.HardwareAbstractionLayer; +import oshi.software.os.FileSystem; +import oshi.software.os.OSFileStore; +import oshi.software.os.OperatingSystem; +import oshi.util.Util; + +/** + * 服务器相关信息 + * + * @author boyue + */ +public class Server +{ + private static final int OSHI_WAIT_SECOND = 1000; + + /** + * CPU相关信息 + */ + private Cpu cpu = new Cpu(); + + /** + * 內存相关信息 + */ + private Mem mem = new Mem(); + + /** + * JVM相关信息 + */ + private Jvm jvm = new Jvm(); + + /** + * 服务器相关信息 + */ + private Sys sys = new Sys(); + + /** + * 磁盘相关信息 + */ + private List sysFiles = new LinkedList(); + + public Cpu getCpu() + { + return cpu; + } + + public void setCpu(Cpu cpu) + { + this.cpu = cpu; + } + + public Mem getMem() + { + return mem; + } + + public void setMem(Mem mem) + { + this.mem = mem; + } + + public Jvm getJvm() + { + return jvm; + } + + public void setJvm(Jvm jvm) + { + this.jvm = jvm; + } + + public Sys getSys() + { + return sys; + } + + public void setSys(Sys sys) + { + this.sys = sys; + } + + public List getSysFiles() + { + return sysFiles; + } + + public void setSysFiles(List sysFiles) + { + this.sysFiles = sysFiles; + } + + public void copyTo() throws Exception + { + SystemInfo si = new SystemInfo(); + HardwareAbstractionLayer hal = si.getHardware(); + + setCpuInfo(hal.getProcessor()); + + setMemInfo(hal.getMemory()); + + setSysInfo(); + + setJvmInfo(); + + setSysFiles(si.getOperatingSystem()); + } + + /** + * 设置CPU信息 + */ + private void setCpuInfo(CentralProcessor processor) + { + // CPU信息 + long[] prevTicks = processor.getSystemCpuLoadTicks(); + Util.sleep(OSHI_WAIT_SECOND); + long[] ticks = processor.getSystemCpuLoadTicks(); + long nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()]; + long irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()]; + long softirq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()]; + long steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()]; + long cSys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()]; + long user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()]; + long iowait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()]; + long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()]; + long totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal; + cpu.setCpuNum(processor.getLogicalProcessorCount()); + cpu.setTotal(totalCpu); + cpu.setSys(cSys); + cpu.setUsed(user); + cpu.setWait(iowait); + cpu.setFree(idle); + } + + /** + * 设置内存信息 + */ + private void setMemInfo(GlobalMemory memory) + { + mem.setTotal(memory.getTotal()); + mem.setUsed(memory.getTotal() - memory.getAvailable()); + mem.setFree(memory.getAvailable()); + } + + /** + * 设置服务器信息 + */ + private void setSysInfo() + { + Properties props = System.getProperties(); + sys.setComputerName(IpUtils.getHostName()); + sys.setComputerIp(IpUtils.getHostIp()); + sys.setOsName(props.getProperty("os.name")); + sys.setOsArch(props.getProperty("os.arch")); + sys.setUserDir(props.getProperty("user.dir")); + } + + /** + * 设置Java虚拟机 + */ + private void setJvmInfo() throws UnknownHostException + { + Properties props = System.getProperties(); + jvm.setTotal(Runtime.getRuntime().totalMemory()); + jvm.setMax(Runtime.getRuntime().maxMemory()); + jvm.setFree(Runtime.getRuntime().freeMemory()); + jvm.setVersion(props.getProperty("java.version")); + jvm.setHome(props.getProperty("java.home")); + } + + /** + * 设置磁盘信息 + */ + private void setSysFiles(OperatingSystem os) + { + FileSystem fileSystem = os.getFileSystem(); + List fsArray = fileSystem.getFileStores(); + for (OSFileStore fs : fsArray) + { + long free = fs.getUsableSpace(); + long total = fs.getTotalSpace(); + long used = total - free; + SysFile sysFile = new SysFile(); + sysFile.setDirName(fs.getMount()); + sysFile.setSysTypeName(fs.getType()); + sysFile.setTypeName(fs.getName()); + sysFile.setTotal(convertFileSize(total)); + sysFile.setFree(convertFileSize(free)); + sysFile.setUsed(convertFileSize(used)); + sysFile.setUsage(Arith.mul(Arith.div(used, total, 4), 100)); + sysFiles.add(sysFile); + } + } + + /** + * 字节转换 + * + * @param size 字节大小 + * @return 转换后值 + */ + public String convertFileSize(long size) + { + long kb = 1024; + long mb = kb * 1024; + long gb = mb * 1024; + if (size >= gb) + { + return String.format("%.1f GB", (float) size / gb); + } + else if (size >= mb) + { + float f = (float) size / mb; + return String.format(f > 100 ? "%.0f MB" : "%.1f MB", f); + } + else if (size >= kb) + { + float f = (float) size / kb; + return String.format(f > 100 ? "%.0f KB" : "%.1f KB", f); + } + else + { + return String.format("%d B", size); + } + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/web/domain/server/Cpu.java b/boyue-framework/src/main/java/com/boyue/framework/web/domain/server/Cpu.java new file mode 100644 index 0000000..6e95b37 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/web/domain/server/Cpu.java @@ -0,0 +1,101 @@ +package com.boyue.framework.web.domain.server; + +import com.boyue.common.utils.Arith; + +/** + * CPU相关信息 + * + * @author boyue + */ +public class Cpu +{ + /** + * 核心数 + */ + private int cpuNum; + + /** + * CPU总的使用率 + */ + private double total; + + /** + * CPU系统使用率 + */ + private double sys; + + /** + * CPU用户使用率 + */ + private double used; + + /** + * CPU当前等待率 + */ + private double wait; + + /** + * CPU当前空闲率 + */ + private double free; + + public int getCpuNum() + { + return cpuNum; + } + + public void setCpuNum(int cpuNum) + { + this.cpuNum = cpuNum; + } + + public double getTotal() + { + return Arith.round(Arith.mul(total, 100), 2); + } + + public void setTotal(double total) + { + this.total = total; + } + + public double getSys() + { + return Arith.round(Arith.mul(sys / total, 100), 2); + } + + public void setSys(double sys) + { + this.sys = sys; + } + + public double getUsed() + { + return Arith.round(Arith.mul(used / total, 100), 2); + } + + public void setUsed(double used) + { + this.used = used; + } + + public double getWait() + { + return Arith.round(Arith.mul(wait / total, 100), 2); + } + + public void setWait(double wait) + { + this.wait = wait; + } + + public double getFree() + { + return Arith.round(Arith.mul(free / total, 100), 2); + } + + public void setFree(double free) + { + this.free = free; + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/web/domain/server/Jvm.java b/boyue-framework/src/main/java/com/boyue/framework/web/domain/server/Jvm.java new file mode 100644 index 0000000..ae27795 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/web/domain/server/Jvm.java @@ -0,0 +1,130 @@ +package com.boyue.framework.web.domain.server; + +import java.lang.management.ManagementFactory; +import com.boyue.common.utils.Arith; +import com.boyue.common.utils.DateUtils; + +/** + * JVM相关信息 + * + * @author boyue + */ +public class Jvm +{ + /** + * 当前JVM占用的内存总数(M) + */ + private double total; + + /** + * JVM最大可用内存总数(M) + */ + private double max; + + /** + * JVM空闲内存(M) + */ + private double free; + + /** + * JDK版本 + */ + private String version; + + /** + * JDK路径 + */ + private String home; + + public double getTotal() + { + return Arith.div(total, (1024 * 1024), 2); + } + + public void setTotal(double total) + { + this.total = total; + } + + public double getMax() + { + return Arith.div(max, (1024 * 1024), 2); + } + + public void setMax(double max) + { + this.max = max; + } + + public double getFree() + { + return Arith.div(free, (1024 * 1024), 2); + } + + public void setFree(double free) + { + this.free = free; + } + + public double getUsed() + { + return Arith.div(total - free, (1024 * 1024), 2); + } + + public double getUsage() + { + return Arith.mul(Arith.div(total - free, total, 4), 100); + } + + /** + * 获取JDK名称 + */ + public String getName() + { + return ManagementFactory.getRuntimeMXBean().getVmName(); + } + + public String getVersion() + { + return version; + } + + public void setVersion(String version) + { + this.version = version; + } + + public String getHome() + { + return home; + } + + public void setHome(String home) + { + this.home = home; + } + + /** + * JDK启动时间 + */ + public String getStartTime() + { + return DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, DateUtils.getServerStartDate()); + } + + /** + * JDK运行时间 + */ + public String getRunTime() + { + return DateUtils.timeDistance(DateUtils.getNowDate(), DateUtils.getServerStartDate()); + } + + /** + * 运行参数 + */ + public String getInputArgs() + { + return ManagementFactory.getRuntimeMXBean().getInputArguments().toString(); + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/web/domain/server/Mem.java b/boyue-framework/src/main/java/com/boyue/framework/web/domain/server/Mem.java new file mode 100644 index 0000000..0c7fc87 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/web/domain/server/Mem.java @@ -0,0 +1,61 @@ +package com.boyue.framework.web.domain.server; + +import com.boyue.common.utils.Arith; + +/** + * 內存相关信息 + * + * @author boyue + */ +public class Mem +{ + /** + * 内存总量 + */ + private double total; + + /** + * 已用内存 + */ + private double used; + + /** + * 剩余内存 + */ + private double free; + + public double getTotal() + { + return Arith.div(total, (1024 * 1024 * 1024), 2); + } + + public void setTotal(long total) + { + this.total = total; + } + + public double getUsed() + { + return Arith.div(used, (1024 * 1024 * 1024), 2); + } + + public void setUsed(long used) + { + this.used = used; + } + + public double getFree() + { + return Arith.div(free, (1024 * 1024 * 1024), 2); + } + + public void setFree(long free) + { + this.free = free; + } + + public double getUsage() + { + return Arith.mul(Arith.div(used, total, 4), 100); + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/web/domain/server/Sys.java b/boyue-framework/src/main/java/com/boyue/framework/web/domain/server/Sys.java new file mode 100644 index 0000000..a39a8ca --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/web/domain/server/Sys.java @@ -0,0 +1,84 @@ +package com.boyue.framework.web.domain.server; + +/** + * 系统相关信息 + * + * @author boyue + */ +public class Sys +{ + /** + * 服务器名称 + */ + private String computerName; + + /** + * 服务器Ip + */ + private String computerIp; + + /** + * 项目路径 + */ + private String userDir; + + /** + * 操作系统 + */ + private String osName; + + /** + * 系统架构 + */ + private String osArch; + + public String getComputerName() + { + return computerName; + } + + public void setComputerName(String computerName) + { + this.computerName = computerName; + } + + public String getComputerIp() + { + return computerIp; + } + + public void setComputerIp(String computerIp) + { + this.computerIp = computerIp; + } + + public String getUserDir() + { + return userDir; + } + + public void setUserDir(String userDir) + { + this.userDir = userDir; + } + + public String getOsName() + { + return osName; + } + + public void setOsName(String osName) + { + this.osName = osName; + } + + public String getOsArch() + { + return osArch; + } + + public void setOsArch(String osArch) + { + this.osArch = osArch; + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/web/domain/server/SysFile.java b/boyue-framework/src/main/java/com/boyue/framework/web/domain/server/SysFile.java new file mode 100644 index 0000000..f96bb8c --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/web/domain/server/SysFile.java @@ -0,0 +1,114 @@ +package com.boyue.framework.web.domain.server; + +/** + * 系统文件相关信息 + * + * @author boyue + */ +public class SysFile +{ + /** + * 盘符路径 + */ + private String dirName; + + /** + * 盘符类型 + */ + private String sysTypeName; + + /** + * 文件类型 + */ + private String typeName; + + /** + * 总大小 + */ + private String total; + + /** + * 剩余大小 + */ + private String free; + + /** + * 已经使用量 + */ + private String used; + + /** + * 资源的使用率 + */ + private double usage; + + public String getDirName() + { + return dirName; + } + + public void setDirName(String dirName) + { + this.dirName = dirName; + } + + public String getSysTypeName() + { + return sysTypeName; + } + + public void setSysTypeName(String sysTypeName) + { + this.sysTypeName = sysTypeName; + } + + public String getTypeName() + { + return typeName; + } + + public void setTypeName(String typeName) + { + this.typeName = typeName; + } + + public String getTotal() + { + return total; + } + + public void setTotal(String total) + { + this.total = total; + } + + public String getFree() + { + return free; + } + + public void setFree(String free) + { + this.free = free; + } + + public String getUsed() + { + return used; + } + + public void setUsed(String used) + { + this.used = used; + } + + public double getUsage() + { + return usage; + } + + public void setUsage(double usage) + { + this.usage = usage; + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/web/exception/GlobalExceptionHandler.java b/boyue-framework/src/main/java/com/boyue/framework/web/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..bf0169b --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/web/exception/GlobalExceptionHandler.java @@ -0,0 +1,137 @@ +package com.boyue.framework.web.exception; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.validation.BindException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingPathVariableException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; + +import com.boyue.common.constant.HttpStatus; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.text.Convert; +import com.boyue.common.exception.DemoModeException; +import com.boyue.common.exception.ServiceException; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.html.EscapeUtil; + +import jakarta.servlet.http.HttpServletRequest; + +/** + * 全局异常处理器 + * + * @author boyue + */ +@RestControllerAdvice +public class GlobalExceptionHandler { + private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + /** + * 权限校验异常 + */ + @ExceptionHandler(AccessDeniedException.class) + public AjaxResult handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage()); + return AjaxResult.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权"); + } + + /** + * 请求方式不支持 + */ + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, + HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod()); + return AjaxResult.error(e.getMessage()); + } + + /** + * 业务异常 + */ + @ExceptionHandler(ServiceException.class) + public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request) { + log.error(e.getMessage(), e); + Integer code = e.getCode(); + return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage()); + } + + /** + * 请求路径中缺少必需的路径变量 + */ + @ExceptionHandler(MissingPathVariableException.class) + public AjaxResult handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI, e); + return AjaxResult.error(String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName())); + } + + /** + * 请求参数类型不匹配 + */ + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public AjaxResult handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, + HttpServletRequest request) { + String requestURI = request.getRequestURI(); + String value = Convert.toStr(e.getValue()); + if (StringUtils.isNotEmpty(value)) { + value = EscapeUtil.clean(value); + } + log.error("请求参数类型不匹配'{}',发生系统异常.", requestURI, e); + return AjaxResult.error(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'", e.getName(), + e.getRequiredType().getName(), value)); + } + + /** + * 拦截未知的运行时异常 + */ + @ExceptionHandler(RuntimeException.class) + public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生未知异常.", requestURI, e); + return AjaxResult.error(e.getMessage()); + } + + /** + * 系统异常 + */ + @ExceptionHandler(Exception.class) + public AjaxResult handleException(Exception e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生系统异常.", requestURI, e); + return AjaxResult.error(e.getMessage()); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(BindException.class) + public AjaxResult handleBindException(BindException e) { + log.error(e.getMessage(), e); + String message = e.getAllErrors().get(0).getDefaultMessage(); + return AjaxResult.error(message); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + log.error(e.getMessage(), e); + String message = e.getBindingResult().getFieldError().getDefaultMessage(); + return AjaxResult.error(message); + } + + /** + * 演示模式异常 + */ + @ExceptionHandler(DemoModeException.class) + public AjaxResult handleDemoModeException(DemoModeException e) { + return AjaxResult.error("演示模式,不允许操作"); + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/web/service/PermissionService.java b/boyue-framework/src/main/java/com/boyue/framework/web/service/PermissionService.java new file mode 100644 index 0000000..718c6c5 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/web/service/PermissionService.java @@ -0,0 +1,130 @@ +package com.boyue.framework.web.service; + +import java.util.Set; + +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import com.boyue.common.core.domain.entity.SysRole; +import com.boyue.common.core.domain.model.LoginUser; +import com.boyue.common.core.security.service.IPermissionService; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.framework.security.context.PermissionContextHolder; + +/** + * boyue首创 自定义权限实现,ss取自SpringSecurity首字母 + * Geek改进 将自定义权限实现形成规范,提高扩展性 + * + * @author boyue&&Dftre + */ +@Service("ss") +public class PermissionService implements IPermissionService { + /** 所有权限标识 */ + private static final String ALL_PERMISSION = "*:*:*"; + + /** 管理员角色权限标识 */ + private static final String SUPER_ADMIN = "admin"; + + private static final String ROLE_DELIMETER = ","; + + private static final String PERMISSION_DELIMETER = ","; + + /** + * 验证用户是否具备某权限 + * + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public boolean hasPermi(String permission) { + if (StringUtils.isEmpty(permission)) { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) { + return false; + } + PermissionContextHolder.setContext(permission); + return hasPermissions(loginUser.getPermissions(), permission); + } + + /** + * 验证用户是否具有以下任意一个权限 + * + * @param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表 + * @return 用户是否具有以下任意一个权限 + */ + public boolean hasAnyPermi(String permissions) { + if (StringUtils.isEmpty(permissions)) { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) { + return false; + } + PermissionContextHolder.setContext(permissions); + Set authorities = loginUser.getPermissions(); + for (String permission : permissions.split(PERMISSION_DELIMETER)) { + if (permission != null && hasPermissions(authorities, permission)) { + return true; + } + } + return false; + } + + /** + * 判断用户是否拥有某个角色 + * + * @param role 角色字符串 + * @return 用户是否具备某角色 + */ + public boolean hasRole(String role) { + if (StringUtils.isEmpty(role)) { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) { + return false; + } + for (SysRole sysRole : loginUser.getUser().getRoles()) { + String roleKey = sysRole.getRoleKey(); + if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role))) { + return true; + } + } + return false; + } + + /** + * 验证用户是否具有以下任意一个角色 + * + * @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表 + * @return 用户是否具有以下任意一个角色 + */ + public boolean hasAnyRoles(String roles) { + if (StringUtils.isEmpty(roles)) { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) { + return false; + } + for (String role : roles.split(ROLE_DELIMETER)) { + if (hasRole(role)) { + return true; + } + } + return false; + } + + /** + * 判断是否包含权限 + * + * @param permissions 权限列表 + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + private boolean hasPermissions(Set permissions, String permission) { + return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission)); + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/web/service/SysLoginService.java b/boyue-framework/src/main/java/com/boyue/framework/web/service/SysLoginService.java new file mode 100644 index 0000000..b7bd9cd --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/web/service/SysLoginService.java @@ -0,0 +1,186 @@ +package com.boyue.framework.web.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +import com.boyue.common.constant.CacheConstants; +import com.boyue.common.constant.Constants; +import com.boyue.common.constant.UserConstants; +import com.boyue.common.core.domain.entity.SysUser; +import com.boyue.common.core.domain.model.LoginUser; +import com.boyue.common.exception.ServiceException; +import com.boyue.common.exception.user.BlackListException; +import com.boyue.common.exception.user.CaptchaException; +import com.boyue.common.exception.user.CaptchaExpireException; +import com.boyue.common.exception.user.UserNotExistsException; +import com.boyue.common.exception.user.UserPasswordNotMatchException; +import com.boyue.common.utils.CacheUtils; +import com.boyue.common.utils.DateUtils; +import com.boyue.common.utils.MessageUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.ip.IpUtils; +import com.boyue.framework.manager.AsyncManager; +import com.boyue.framework.manager.factory.AsyncFactory; +import com.boyue.framework.security.context.AuthenticationContextHolder; +import com.boyue.system.service.ISysConfigService; +import com.boyue.system.service.ISysUserService; + +import jakarta.annotation.Resource; + +/** + * 登录校验方法 + * + * @author boyue + */ +@Component +public class SysLoginService +{ + @Autowired + private TokenService tokenService; + + @Resource + private AuthenticationManager authenticationManager; + + @Autowired + private ISysUserService userService; + + @Autowired + private ISysConfigService configService; + + @Autowired + private SysPasswordService passwordService; + + /** + * 登录验证 + * + * @param username 用户名 + * @param password 密码 + * @param code 验证码 + * @param uuid 唯一标识 + * @return 结果 + */ + public String login(String username, String password, String code, String uuid) + { + // 验证码校验 + validateCaptcha(username, code, uuid); + // 登录前置校验 + loginPreCheck(username, password); + String ip = IpUtils.getIpAddr(); + // 验证 IP 是否被封锁 + passwordService.validateIp(ip); + // 用户验证 + Authentication authentication = null; + try + { + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); + AuthenticationContextHolder.setContext(authenticationToken); + // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername + authentication = authenticationManager.authenticate(authenticationToken); + } + catch (Exception e) + { + if (e instanceof BadCredentialsException) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); + throw new UserPasswordNotMatchException(); + } + else + { + passwordService.incrementIpFailCount(ip); + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage())); + throw new ServiceException(e.getMessage()); + } + } + finally + { + AuthenticationContextHolder.clearContext(); + } + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); + LoginUser loginUser = (LoginUser) authentication.getPrincipal(); + recordLoginInfo(loginUser.getUserId()); + // 生成token + return tokenService.createToken(loginUser); + } + + /** + * 校验验证码 + * + * @param username 用户名 + * @param code 验证码 + * @param uuid 唯一标识 + * @return 结果 + */ + public void validateCaptcha(String username, String code, String uuid) + { + boolean captchaEnabled = configService.selectCaptchaEnabled(); + if (captchaEnabled) + { + String captcha = CacheUtils.get(CacheConstants.CAPTCHA_CODE_KEY, StringUtils.nvl(uuid, ""), String.class); + CacheUtils.removeIfPresent(CacheConstants.CAPTCHA_CODE_KEY, StringUtils.nvl(uuid, "")); + if (captcha == null) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"))); + throw new CaptchaExpireException(); + } + if (!code.equalsIgnoreCase(captcha)) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"))); + throw new CaptchaException(); + } + } + } + + /** + * 登录前置校验 + * @param username 用户名 + * @param password 用户密码 + */ + public void loginPreCheck(String username, String password) + { + // 用户名或密码为空 错误 + if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null"))); + throw new UserNotExistsException(); + } + // 密码如果不在指定范围内 错误 + if (password.length() < UserConstants.PASSWORD_MIN_LENGTH + || password.length() > UserConstants.PASSWORD_MAX_LENGTH) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL,MessageUtils.message("user.password.not.match"))); + throw new UserPasswordNotMatchException(); + } + // 用户名不在指定范围内 错误 + if (username.length() < UserConstants.USERNAME_MIN_LENGTH + || username.length() > UserConstants.USERNAME_MAX_LENGTH) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); + throw new UserPasswordNotMatchException(); + } + // IP黑名单校验 + String blackStr = configService.selectConfigByKey("sys.login.blackIPList"); + if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked"))); + throw new BlackListException(); + } + } + + /** + * 记录登录信息 + * + * @param userId 用户ID + */ + public void recordLoginInfo(Long userId) + { + SysUser sysUser = new SysUser(); + sysUser.setUserId(userId); + sysUser.setLoginIp(IpUtils.getIpAddr()); + sysUser.setLoginDate(DateUtils.getNowDate()); + userService.updateUserProfile(sysUser); + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/web/service/SysPasswordService.java b/boyue-framework/src/main/java/com/boyue/framework/web/service/SysPasswordService.java new file mode 100644 index 0000000..cfb79b6 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/web/service/SysPasswordService.java @@ -0,0 +1,125 @@ +package com.boyue.framework.web.service; + +import com.boyue.common.exception.user.IpRetryLimitExceedException; +import com.boyue.common.utils.ip.IpUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cache.Cache; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +import com.boyue.common.constant.CacheConstants; +import com.boyue.common.constant.Constants; +import com.boyue.common.core.domain.entity.SysUser; +import com.boyue.common.exception.user.UserPasswordNotMatchException; +import com.boyue.common.exception.user.UserPasswordRetryLimitExceedException; +import com.boyue.common.utils.CacheUtils; +import com.boyue.common.utils.MessageUtils; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.framework.manager.AsyncManager; +import com.boyue.framework.manager.factory.AsyncFactory; +import com.boyue.framework.security.context.AuthenticationContextHolder; + +import java.util.concurrent.TimeUnit; + +/** + * 登录密码方法 + * + * @author boyue + */ +@Component +public class SysPasswordService +{ + + @Value(value = "${user.password.maxRetryCount}") + private int maxRetryCount; + + @Value(value = "${user.password.lockTime}") + private int lockTime; + + @Value(value = "${user.ip.maxRetryCount:15}") + public int maxIpRetryCount; + + @Value(value = "${user.ip.lockTime:15}") + public int ipLockTime; + /** + * 登录账户密码错误次数缓存键名 + * + * @return 缓存Cache + */ + private Cache getCache() + { + return CacheUtils.getCache(CacheConstants.PWD_ERR_CNT_KEY); + } + + private Cache getIpCache() { + return CacheUtils.getCache(CacheConstants.IP_ERR_CNT_KEY); + } + + public void validate(SysUser user) + { + Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext(); + String username = usernamePasswordAuthenticationToken.getName(); + String password = usernamePasswordAuthenticationToken.getCredentials().toString(); + + String ip = IpUtils.getIpAddr(); + validateIp(ip); + Integer retryCount = getCache().get(username, Integer.class); + if (retryCount == null) + { + retryCount = 0; + } + if (retryCount >= Integer.valueOf(maxRetryCount).intValue()) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, + MessageUtils.message("user.password.retry.limit.exceed", maxRetryCount, lockTime))); + throw new UserPasswordRetryLimitExceedException(maxRetryCount, lockTime); + } + if (!matches(user, password)) + { + retryCount = retryCount + 1; + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, + MessageUtils.message("user.password.retry.limit.count", retryCount))); + CacheUtils.put(CacheConstants.PWD_ERR_CNT_KEY,username,retryCount,lockTime,TimeUnit.MINUTES); + throw new UserPasswordNotMatchException(); + } + else + { + clearLoginRecordCache(username); + } + } + + public void validateIp(String ip) + { + Integer ipRetryCount = getIpCache().get(ip, Integer.class); + if (ipRetryCount == null) + { + ipRetryCount = 0; + } + + if (ipRetryCount >= maxIpRetryCount) + { + throw new IpRetryLimitExceedException(maxIpRetryCount, ipLockTime); + } + } + + public void incrementIpFailCount(String ip) + { + Integer ipRetryCount = getIpCache().get(ip, Integer.class); + if (ipRetryCount == null) + { + ipRetryCount = 0; + } + ipRetryCount += 1; + CacheUtils.put(CacheConstants.IP_ERR_CNT_KEY,ip,ipRetryCount,ipLockTime,TimeUnit.MINUTES); + } + + public boolean matches(SysUser user, String rawPassword) + { + return SecurityUtils.matchesPassword(rawPassword, user.getPassword()); + } + + public void clearLoginRecordCache(String loginName) + { + getCache().evictIfPresent(loginName); + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/web/service/SysPermissionService.java b/boyue-framework/src/main/java/com/boyue/framework/web/service/SysPermissionService.java new file mode 100644 index 0000000..5544841 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/web/service/SysPermissionService.java @@ -0,0 +1,76 @@ +package com.boyue.framework.web.service; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; + +import com.boyue.common.constant.UserConstants; +import com.boyue.common.core.domain.entity.SysRole; +import com.boyue.common.core.domain.entity.SysUser; +import com.boyue.common.utils.StringUtils; +import com.boyue.system.service.ISysMenuService; +import com.boyue.system.service.ISysRoleService; + +/** + * 用户权限处理 + * + * @author boyue + */ +@Component +public class SysPermissionService { + @Autowired + private ISysRoleService roleService; + + @Autowired + private ISysMenuService menuService; + + /** + * 获取角色数据权限 + * + * @param user 用户信息 + * @return 角色权限信息 + */ + public Set getRolePermission(SysUser user) { + Set roles = new HashSet(); + // 管理员拥有所有权限 + if (user.isAdmin()) { + roles.add("admin"); + } else { + roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId())); + } + return roles; + } + + /** + * 获取菜单数据权限 + * + * @param user 用户信息 + * @return 菜单权限信息 + */ + public Set getMenuPermission(SysUser user) { + Set perms = new HashSet(); + // 管理员拥有所有权限 + if (user.isAdmin()) { + perms.add("*:*:*"); + } else { + List roles = user.getRoles(); + if (!CollectionUtils.isEmpty(roles)) { + // 多角色设置permissions属性,以便数据权限匹配权限 + for (SysRole role : roles) { + if (StringUtils.equals(role.getStatus(), UserConstants.ROLE_NORMAL)) { + Set rolePerms = menuService.selectMenuPermsByRoleId(role.getRoleId()); + role.setPermissions(rolePerms); + perms.addAll(rolePerms); + } + } + } else { + perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId())); + } + } + return perms; + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/web/service/SysRegisterService.java b/boyue-framework/src/main/java/com/boyue/framework/web/service/SysRegisterService.java new file mode 100644 index 0000000..6719aa8 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/web/service/SysRegisterService.java @@ -0,0 +1,110 @@ +package com.boyue.framework.web.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.boyue.common.constant.CacheConstants; +import com.boyue.common.constant.Constants; +import com.boyue.common.constant.UserConstants; +import com.boyue.common.core.domain.entity.SysUser; +import com.boyue.common.core.domain.model.RegisterBody; +import com.boyue.common.exception.user.CaptchaException; +import com.boyue.common.exception.user.CaptchaExpireException; +import com.boyue.common.utils.CacheUtils; +import com.boyue.common.utils.MessageUtils; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.framework.manager.AsyncManager; +import com.boyue.framework.manager.factory.AsyncFactory; +import com.boyue.system.service.ISysConfigService; +import com.boyue.system.service.ISysUserService; + +/** + * 注册校验方法 + * + * @author boyue + */ +@Component +public class SysRegisterService +{ + @Autowired + private ISysUserService userService; + + @Autowired + private ISysConfigService configService; + + /** + * 注册 + */ + public String register(RegisterBody registerBody) + { + String msg = "", username = registerBody.getUsername(), password = registerBody.getPassword(); + SysUser sysUser = new SysUser(); + sysUser.setUserName(username); + // 验证码开关 + boolean captchaEnabled = configService.selectCaptchaEnabled(); + if (captchaEnabled) + { + validateCaptcha(username, registerBody.getCode(), registerBody.getUuid()); + } + if (StringUtils.isEmpty(username)) + { + msg = "用户名不能为空"; + } + else if (StringUtils.isEmpty(password)) + { + msg = "用户密码不能为空"; + } + else if (username.length() < UserConstants.USERNAME_MIN_LENGTH + || username.length() > UserConstants.USERNAME_MAX_LENGTH) + { + msg = "账户长度必须在2到20个字符之间"; + } + else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH + || password.length() > UserConstants.PASSWORD_MAX_LENGTH) + { + msg = "密码长度必须在5到20个字符之间"; + } + else if (!userService.checkUserNameUnique(sysUser)) + { + msg = "保存用户'" + username + "'失败,注册账号已存在"; + } + else + { + sysUser.setNickName(username); + sysUser.setPassword(SecurityUtils.encryptPassword(password)); + boolean regFlag = userService.registerUser(sysUser); + if (!regFlag) + { + msg = "注册失败,请联系系统管理人员"; + } + else + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.register.success"))); + } + } + return msg; + } + + /** + * 校验验证码 + * + * @param username 用户名 + * @param code 验证码 + * @param uuid 唯一标识 + * @return 结果 + */ + public void validateCaptcha(String username, String code, String uuid) + { + String captcha = CacheUtils.get(CacheConstants.CAPTCHA_CODE_KEY, StringUtils.nvl(uuid, ""), String.class); + CacheUtils.removeIfPresent(CacheConstants.CAPTCHA_CODE_KEY, StringUtils.nvl(uuid, "")); + if (captcha == null) + { + throw new CaptchaExpireException(); + } + if (!code.equalsIgnoreCase(captcha)) + { + throw new CaptchaException(); + } + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/web/service/TokenService.java b/boyue-framework/src/main/java/com/boyue/framework/web/service/TokenService.java new file mode 100644 index 0000000..b9982eb --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/web/service/TokenService.java @@ -0,0 +1,203 @@ +package com.boyue.framework.web.service; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import javax.crypto.SecretKey; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import com.boyue.common.constant.CacheConstants; +import com.boyue.common.constant.Constants; +import com.boyue.common.core.domain.model.LoginUser; +import com.boyue.common.utils.CacheUtils; +import com.boyue.common.utils.ServletUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.ip.AddressUtils; +import com.boyue.common.utils.ip.IpUtils; +import com.boyue.common.utils.uuid.IdUtils; + +import eu.bitwalker.useragentutils.UserAgent; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import jakarta.servlet.http.HttpServletRequest; + +/** + * token验证处理 + * + * @author boyue + */ +@Component +public class TokenService { + // 令牌自定义标识 + @Value("${token.header}") + private String header; + + // 令牌秘钥 + @Value("${token.secret}") + private String secret; + + // 令牌有效期(默认30分钟) + @Value("${token.expireTime}") + private int expireTime; + + protected static final long MILLIS_SECOND = 1000; + + protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND; + + private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L; + + /** + * 获取用户身份信息 + * + * @return 用户信息 + */ + public LoginUser getLoginUser(HttpServletRequest request) { + // 获取请求携带的令牌 + String token = getToken(request); + if (StringUtils.isNotEmpty(token)) { + try { + Claims claims = parseToken(token); + // 解析对应的权限以及用户信息 + String uuid = (String) claims.get(Constants.LOGIN_USER_KEY); + LoginUser user = CacheUtils.get(CacheConstants.LOGIN_TOKEN_KEY, uuid, LoginUser.class); + return user; + } catch (Exception e) { + } + } + return null; + } + + /** + * 设置用户身份信息 + */ + public void setLoginUser(LoginUser loginUser) { + if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) { + refreshToken(loginUser); + } + } + + /** + * 删除用户身份信息 + */ + public void delLoginUser(String token) { + if (StringUtils.isNotEmpty(token)) { + CacheUtils.removeIfPresent(CacheConstants.LOGIN_TOKEN_KEY, token); + } + } + + /** + * 创建令牌 + * + * @param loginUser 用户信息 + * @return 令牌 + */ + public String createToken(LoginUser loginUser) { + String token = IdUtils.fastUUID(); + loginUser.setToken(token); + setUserAgent(loginUser); + refreshToken(loginUser); + Map claims = new HashMap<>(); + claims.put(Constants.LOGIN_USER_KEY, token); + return createToken(claims); + } + + /** + * 验证令牌有效期,相差不足20分钟,自动刷新缓存 + * + * @param loginUser + * @return 令牌 + */ + public void verifyToken(LoginUser loginUser) { + long expireTime = loginUser.getExpireTime(); + long currentTime = System.currentTimeMillis(); + if (expireTime - currentTime <= MILLIS_MINUTE_TEN) { + refreshToken(loginUser); + } + } + + /** + * 刷新令牌有效期 + * + * @param loginUser 登录信息 + */ + public void refreshToken(LoginUser loginUser) { + loginUser.setLoginTime(System.currentTimeMillis()); + loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE); + // 根据uuid将loginUser缓存 + CacheUtils.put(CacheConstants.LOGIN_TOKEN_KEY, loginUser.getToken(), loginUser, expireTime, TimeUnit.MINUTES); + } + + /** + * 设置用户代理信息 + * + * @param loginUser 登录信息 + */ + public void setUserAgent(LoginUser loginUser) { + UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent")); + String ip = IpUtils.getIpAddr(); + loginUser.setIpaddr(ip); + loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip)); + loginUser.setBrowser(userAgent.getBrowser().getName()); + loginUser.setOs(userAgent.getOperatingSystem().getName()); + } + + /** + * 从数据声明生成令牌 + * + * @param claims 数据声明 + * @return 令牌 + */ + private String createToken(Map claims) { + SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret)); + String token = Jwts.builder() + .claims(claims) + .signWith(key) + .compact(); + return token; + } + + /** + * 从令牌中获取数据声明 + * + * @param token 令牌 + * @return 数据声明 + */ + private Claims parseToken(String token) { + SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret)); + return Jwts.parser() + .verifyWith(key) + .build() + .parseSignedClaims(token) + .getPayload(); + } + + /** + * 从令牌中获取用户名 + * + * @param token 令牌 + * @return 用户名 + */ + public String getUsernameFromToken(String token) { + Claims claims = parseToken(token); + return claims.getSubject(); + } + + /** + * 获取请求token + * + * @param request + * @return token + */ + private String getToken(HttpServletRequest request) { + String token = request.getHeader(header); + if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) { + token = token.replace(Constants.TOKEN_PREFIX, ""); + } + return token; + } +} diff --git a/boyue-framework/src/main/java/com/boyue/framework/web/service/UserDetailsServiceImpl.java b/boyue-framework/src/main/java/com/boyue/framework/web/service/UserDetailsServiceImpl.java new file mode 100644 index 0000000..4734047 --- /dev/null +++ b/boyue-framework/src/main/java/com/boyue/framework/web/service/UserDetailsServiceImpl.java @@ -0,0 +1,72 @@ +package com.boyue.framework.web.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import com.boyue.common.core.domain.entity.SysUser; +import com.boyue.common.core.domain.model.LoginUser; +import com.boyue.common.enums.UserStatus; +import com.boyue.common.exception.ServiceException; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.system.service.ISysUserService; + +/** + * 用户验证处理 + * + * @author boyue + */ +@Service +public class UserDetailsServiceImpl implements UserDetailsService { + private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class); + + @Autowired + private ISysUserService userService; + + @Autowired + private SysPasswordService passwordService; + + @Autowired + private SysPermissionService permissionService; + + @Autowired + private TokenService tokenService; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + SysUser user = userService.selectUserByUserName(username); + if (StringUtils.isNull(user)) { + log.info("登录用户:{} 不存在.", username); + throw new ServiceException("登录用户:" + username + " 不存在"); + } else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) { + log.info("登录用户:{} 已被删除.", username); + throw new ServiceException("对不起,您的账号:" + username + " 已被删除"); + } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) { + log.info("登录用户:{} 已被停用.", username); + throw new ServiceException("对不起,您的账号:" + username + " 已停用"); + } + + passwordService.validate(user); + + return createLoginUser(user); + } + + public UserDetails createLoginUser(SysUser user) { + return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user)); + } + + public void refreshLoginUser(Long userId) { + SysUser user = userService.selectUserById(userId); + LoginUser loginUser = SecurityUtils.getLoginUser(); + loginUser.setUserId(user.getUserId()); + loginUser.setDeptId(user.getDeptId()); + loginUser.setUser(user); + loginUser.setPermissions(permissionService.getMenuPermission(user)); + tokenService.refreshToken(loginUser); + } +} diff --git a/boyue-framework/src/main/resources/mybatis/mybatis-config.xml b/boyue-framework/src/main/resources/mybatis/mybatis-config.xml new file mode 100644 index 0000000..fa186dd --- /dev/null +++ b/boyue-framework/src/main/resources/mybatis/mybatis-config.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/boyue-middleware/boyue-middleware-rabbitmq/pom.xml b/boyue-middleware/boyue-middleware-rabbitmq/pom.xml new file mode 100644 index 0000000..4174ba9 --- /dev/null +++ b/boyue-middleware/boyue-middleware-rabbitmq/pom.xml @@ -0,0 +1,33 @@ + + + + boyue-middleware + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-middleware-rabbitmq + + + 中间件 + + + + + + + com.boyue + boyue-common + + + + + org.springframework.boot + spring-boot-starter-amqp + + + + + diff --git a/boyue-middleware/boyue-middleware-rabbitmq/src/main/java/com/boyue/middleware/rabbitmq/DirectRabbitConfig.java b/boyue-middleware/boyue-middleware-rabbitmq/src/main/java/com/boyue/middleware/rabbitmq/DirectRabbitConfig.java new file mode 100644 index 0000000..5aa8476 --- /dev/null +++ b/boyue-middleware/boyue-middleware-rabbitmq/src/main/java/com/boyue/middleware/rabbitmq/DirectRabbitConfig.java @@ -0,0 +1,80 @@ +package com.boyue.middleware.rabbitmq; + +import org.springframework.amqp.core.Binding; +import org.springframework.amqp.core.BindingBuilder; +import org.springframework.amqp.core.DirectExchange; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.rabbit.annotation.RabbitListenerConfigurer; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistrar; +import org.springframework.amqp.support.converter.DefaultClassMapper; +import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.converter.MappingJackson2MessageConverter; +import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory; + +/** + * @Author : JCccc + * @CreateTime : 2019/9/3 + * @Description : + **/ +@Configuration +public class DirectRabbitConfig implements RabbitListenerConfigurer{ + + // 队列 起名:TestDirectQueue + @Bean + public Queue TestDirectQueue() { + // durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效 + // exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable + // autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。 + // return new Queue("TestDirectQueue",true,true,false); + + // 一般设置一下队列的持久化就好,其余两个就是默认false + return new Queue("TestDirectQueue", true); + } + + // Direct交换机 起名:TestDirectExchange + @Bean + DirectExchange TestDirectExchange() { + // return new DirectExchange("TestDirectExchange",true,true); + return new DirectExchange("TestDirectExchange", true, false); + } + + // 绑定 将队列和交换机绑定, 并设置用于匹配键:TestDirectRouting + @Bean + Binding bindingDirect() { + return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("TestDirectRouting"); + } + + @Bean + public MessageConverter jsonToMapMessageConverter() { + DefaultClassMapper defaultClassMapper = new DefaultClassMapper(); + // 指定反序列化期间要信任的一组包,星号 ( * ) 表示全部信任 + defaultClassMapper.setTrustedPackages("*"); + Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter(); + jackson2JsonMessageConverter.setClassMapper(defaultClassMapper); + return jackson2JsonMessageConverter; + } + + + + //以下配置RabbitMQ消息服务 + @Autowired + public ConnectionFactory connectionFactory; + + @Bean + public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() { + DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory(); + // 设置转换器 + factory.setMessageConverter(new MappingJackson2MessageConverter()); + return factory; + } + + @Override + public void configureRabbitListeners(RabbitListenerEndpointRegistrar rabbitListenerEndpointRegistrar) { + rabbitListenerEndpointRegistrar.setMessageHandlerMethodFactory(myHandlerMethodFactory()); + } +} \ No newline at end of file diff --git a/boyue-middleware/boyue-middleware-rabbitmq/src/main/java/com/boyue/middleware/rabbitmq/DirectReceiver.java b/boyue-middleware/boyue-middleware-rabbitmq/src/main/java/com/boyue/middleware/rabbitmq/DirectReceiver.java new file mode 100644 index 0000000..f50378d --- /dev/null +++ b/boyue-middleware/boyue-middleware-rabbitmq/src/main/java/com/boyue/middleware/rabbitmq/DirectReceiver.java @@ -0,0 +1,19 @@ +package com.boyue.middleware.rabbitmq; + +import org.springframework.amqp.rabbit.annotation.RabbitHandler; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +import com.boyue.common.core.domain.Message; + +@Component +@RabbitListener(queues = "TestDirectQueue") // 监听的队列名称 TestDirectQueue +@ConditionalOnProperty(prefix = "spring.rabbitmq", name = { "enable" }, havingValue = "true", matchIfMissing = false) +public class DirectReceiver { + + @RabbitHandler + public void process(Message map) { + System.out.println("DirectReceiver m消费者收到消息 : " + map.toString()); + } +} \ No newline at end of file diff --git a/boyue-middleware/boyue-middleware-rabbitmq/src/main/java/com/boyue/middleware/rabbitmq/SendMessageController.java b/boyue-middleware/boyue-middleware-rabbitmq/src/main/java/com/boyue/middleware/rabbitmq/SendMessageController.java new file mode 100644 index 0000000..95c6ac8 --- /dev/null +++ b/boyue-middleware/boyue-middleware-rabbitmq/src/main/java/com/boyue/middleware/rabbitmq/SendMessageController.java @@ -0,0 +1,41 @@ +package com.boyue.middleware.rabbitmq; + +import java.util.Map; + +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.annotation.Anonymous; +import com.boyue.common.core.domain.Message; + +/** + * @Author : JCccc + * @CreateTime : 2019/9/3 + * @Description : + **/ +@RestController +@RequestMapping("/rabbitmq") +public class SendMessageController { + + @Autowired + RabbitTemplate rabbitTemplate; // 使用RabbitTemplate,这提供了接收/发送等等方法 + + @GetMapping("/sendDirectMessage") + @Anonymous + public String sendDirectMessage() { + // 将消息携带绑定键值:TestDirectRouting 发送到交换机TestDirectExchange + rabbitTemplate.convertAndSend( + "TestDirectExchange", + "TestDirectRouting", + Message.create() + .setPayload(Map.of("message", "你好")) + .setReceiver("接收者") + .setSender("发送者") + ); + return "ok"; + } + +} \ No newline at end of file diff --git a/boyue-middleware/boyue-middleware-redis/pom.xml b/boyue-middleware/boyue-middleware-redis/pom.xml new file mode 100644 index 0000000..eb2b63c --- /dev/null +++ b/boyue-middleware/boyue-middleware-redis/pom.xml @@ -0,0 +1,33 @@ + + + + boyue-middleware + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-middleware-redis + + + 中间件 + + + + + + + com.boyue + boyue-framework + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + diff --git a/boyue-middleware/boyue-middleware-redis/src/main/java/com/boyue/middleware/redis/aspectj/RateLimiterAspect.java b/boyue-middleware/boyue-middleware-redis/src/main/java/com/boyue/middleware/redis/aspectj/RateLimiterAspect.java new file mode 100644 index 0000000..b540a4c --- /dev/null +++ b/boyue-middleware/boyue-middleware-redis/src/main/java/com/boyue/middleware/redis/aspectj/RateLimiterAspect.java @@ -0,0 +1,92 @@ +package com.boyue.middleware.redis.aspectj; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.RedisScript; +import org.springframework.stereotype.Component; + +import com.boyue.common.annotation.RateLimiter; +import com.boyue.common.enums.LimitType; +import com.boyue.common.exception.ServiceException; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.ip.IpUtils; + +/** + * 限流处理 + * + * @author boyue + */ +@Aspect +@Component +@ConditionalOnProperty(prefix = "spring.cache", name = { "type" }, havingValue = "redis", matchIfMissing = false) +public class RateLimiterAspect +{ + private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class); + + private RedisTemplate redisTemplate; + + private RedisScript limitScript; + + @Autowired + public void setRedisTemplate1(RedisTemplate redisTemplate) + { + this.redisTemplate = redisTemplate; + } + + @Autowired + public void setLimitScript(RedisScript limitScript) + { + this.limitScript = limitScript; + } + + @Before("@annotation(rateLimiter)") + public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable + { + int time = rateLimiter.time(); + int count = rateLimiter.count(); + String combineKey = getCombineKey(rateLimiter, point); + List keys = Collections.singletonList(combineKey); + try + { + Long number = redisTemplate.execute(limitScript, keys, count, time); + if (StringUtils.isNull(number) || number.intValue() > count) + { + throw new ServiceException("访问过于频繁,请稍候再试"); + } + log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), combineKey); + } + catch (ServiceException e) + { + throw e; + } + catch (Exception e) + { + throw new RuntimeException("服务器限流异常,请稍候再试"); + } + } + + public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) + { + StringBuffer stringBuffer = new StringBuffer(rateLimiter.key()); + if (rateLimiter.limitType() == LimitType.IP) + { + stringBuffer.append(IpUtils.getIpAddr()).append("-"); + } + MethodSignature signature = (MethodSignature) point.getSignature(); + Method method = signature.getMethod(); + Class targetClass = method.getDeclaringClass(); + stringBuffer.append(targetClass.getName()).append("-").append(method.getName()); + return stringBuffer.toString(); + } +} diff --git a/boyue-middleware/boyue-middleware-redis/src/main/java/com/boyue/middleware/redis/config/FastJson2JsonRedisSerializer.java b/boyue-middleware/boyue-middleware-redis/src/main/java/com/boyue/middleware/redis/config/FastJson2JsonRedisSerializer.java new file mode 100644 index 0000000..eb7871f --- /dev/null +++ b/boyue-middleware/boyue-middleware-redis/src/main/java/com/boyue/middleware/redis/config/FastJson2JsonRedisSerializer.java @@ -0,0 +1,54 @@ +package com.boyue.middleware.redis.config; + +import java.nio.charset.Charset; + +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.SerializationException; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONReader; +import com.alibaba.fastjson2.JSONWriter; +// import com.alibaba.fastjson2.filter.Filter; +import com.alibaba.fastjson2.filter.Filter; +import com.boyue.common.constant.Constants; + +/** + * Redis使用FastJson序列化 + * + * @author boyue + */ +public class FastJson2JsonRedisSerializer implements RedisSerializer +{ + public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); + + static final Filter AUTO_TYPE_FILTER = JSONReader.autoTypeFilter(Constants.JSON_WHITELIST_STR); + + private Class clazz; + + public FastJson2JsonRedisSerializer(Class clazz) + { + super(); + this.clazz = clazz; + } + + @Override + public byte[] serialize(T t) throws SerializationException + { + if (t == null) + { + return new byte[0]; + } + return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET); + } + + @Override + public T deserialize(byte[] bytes) throws SerializationException + { + if (bytes == null || bytes.length <= 0) + { + return null; + } + String str = new String(bytes, DEFAULT_CHARSET); + return JSON.parseObject(str, clazz, AUTO_TYPE_FILTER); + } +} diff --git a/boyue-middleware/boyue-middleware-redis/src/main/java/com/boyue/middleware/redis/config/RedisConfig.java b/boyue-middleware/boyue-middleware-redis/src/main/java/com/boyue/middleware/redis/config/RedisConfig.java new file mode 100644 index 0000000..204b965 --- /dev/null +++ b/boyue-middleware/boyue-middleware-redis/src/main/java/com/boyue/middleware/redis/config/RedisConfig.java @@ -0,0 +1,97 @@ +package com.boyue.middleware.redis.config; + +import java.time.Duration; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.CachingConfigurer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/** + * redis配置 + * + * @author boyue + */ +@Configuration +@ConditionalOnProperty(prefix = "spring.cache", name = { "type" }, havingValue = "redis", matchIfMissing = false) +public class RedisConfig implements CachingConfigurer { + + @Bean + @Primary + public CacheManager cacheManager(RedisConnectionFactory connectionFactory) { + RedisCacheConfiguration config = instanceConfig(3600 * 24 * 15L); + return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).transactionAware().build(); + } + + @Bean + public CacheManager cacheManager30m(RedisConnectionFactory connectionFactory) { + RedisCacheConfiguration config = instanceConfig(1800L); + return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).transactionAware().build(); + } + + @SuppressWarnings(value = { "unchecked", "rawtypes" }) + private RedisCacheConfiguration instanceConfig(Long ttl) { + FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class); + return RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(ttl)).disableCachingNullValues() + .computePrefixWith(name -> name + ":") + .serializeKeysWith( + RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) + .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer)); + } + + @Bean + @SuppressWarnings(value = { "unchecked", "rawtypes" }) + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + + FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class); + + // 使用StringRedisSerializer来序列化和反序列化redis的key值 + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(serializer); + + // Hash的key也采用StringRedisSerializer的序列化方式 + template.setHashKeySerializer(new StringRedisSerializer()); + template.setHashValueSerializer(serializer); + + template.afterPropertiesSet(); + return template; + } + + @Bean + public DefaultRedisScript limitScript() { + DefaultRedisScript redisScript = new DefaultRedisScript<>(); + redisScript.setScriptText(limitScriptText()); + redisScript.setResultType(Long.class); + return redisScript; + } + + /** + * 限流脚本 + */ + private String limitScriptText() { + return "local key = KEYS[1]\n" + + "local count = tonumber(ARGV[1])\n" + + "local time = tonumber(ARGV[2])\n" + + "local current = redis.call('get', key);\n" + + "if current and tonumber(current) > count then\n" + + " return tonumber(current);\n" + + "end\n" + + "current = redis.call('incr', key)\n" + + "if tonumber(current) == 1 then\n" + + " redis.call('expire', key, time)\n" + + "end\n" + + "return tonumber(current);"; + } +} diff --git a/boyue-middleware/boyue-middleware-redis/src/main/java/com/boyue/middleware/redis/controller/RedisCacheController.java b/boyue-middleware/boyue-middleware-redis/src/main/java/com/boyue/middleware/redis/controller/RedisCacheController.java new file mode 100644 index 0000000..e0bdad7 --- /dev/null +++ b/boyue-middleware/boyue-middleware-redis/src/main/java/com/boyue/middleware/redis/controller/RedisCacheController.java @@ -0,0 +1,64 @@ +package com.boyue.middleware.redis.controller; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.utils.StringUtils; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 缓存监控 + * + * @author boyue + */ +@Tag(name = "缓存监控") +@RestController +@SuppressWarnings(value = { "unchecked", "rawtypes" }) +@ConditionalOnProperty(prefix = "spring.cache", name = { "type" }, havingValue = "redis", matchIfMissing = false) +@RequestMapping("/monitor/cache") +public class RedisCacheController { + @Autowired + private RedisTemplate redisTemplate; + + @Operation(summary = "获取缓存信息") + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping() + public AjaxResult getInfo() throws Exception { + Map result = new HashMap<>(3); + + Properties info = (Properties) redisTemplate + .execute((RedisCallback) connection -> connection.commands().info()); + Properties commandStats = (Properties) redisTemplate + .execute((RedisCallback) connection -> connection.commands().info("commandstats")); + Object dbSize = redisTemplate.execute((RedisCallback) connection -> connection.commands().dbSize()); + result.put("info", info); + result.put("dbSize", dbSize); + + List> pieList = new ArrayList<>(); + commandStats.stringPropertyNames().forEach(key -> { + Map data = new HashMap<>(2); + String property = commandStats.getProperty(key); + data.put("name", StringUtils.removeStart(key, "cmdstat_")); + data.put("value", StringUtils.substringBetween(property, "calls=", ",usec")); + pieList.add(data); + }); + result.put("commandStats", pieList); + return AjaxResult.success(result); + } + +} diff --git a/boyue-middleware/boyue-middleware-redis/src/main/java/com/boyue/middleware/redis/utils/RedisCache.java b/boyue-middleware/boyue-middleware-redis/src/main/java/com/boyue/middleware/redis/utils/RedisCache.java new file mode 100644 index 0000000..24e556f --- /dev/null +++ b/boyue-middleware/boyue-middleware-redis/src/main/java/com/boyue/middleware/redis/utils/RedisCache.java @@ -0,0 +1,288 @@ +package com.boyue.middleware.redis.utils; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cache.Cache; +import org.springframework.data.redis.core.BoundSetOperations; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Component; + +import com.boyue.common.service.cache.CacheKeys; +import com.boyue.common.service.cache.CacheTimeOut; +import com.boyue.common.utils.StringUtils; + +/** + * spring redis 工具类 + * + * @author boyue + **/ +@SuppressWarnings(value = { "unchecked", "rawtypes" }) +@Component +@ConditionalOnProperty(prefix = "spring.cache", name = { "type" }, havingValue = "redis", matchIfMissing = false) +public class RedisCache implements CacheKeys, CacheTimeOut { + @Autowired + public RedisTemplate redisTemplate; + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + */ + public void setCacheObject(final String cacheName, final String key, final T value) { + redisTemplate.opsForValue().set(cacheName + ":" + key, value); + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + */ + public void setCacheObject(final String key, final T value) { + redisTemplate.opsForValue().set(key, value); + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param timeout 时间 + * @param timeUnit 时间颗粒度 + */ + public void setCacheObject(final String cacheName, final String key, final T value, final long timeout, + final TimeUnit timeUnit) { + redisTemplate.opsForValue().set(cacheName + ":" + key, value, timeout, timeUnit); + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param timeout 时间 + * @param timeUnit 时间颗粒度 + */ + public void setCacheObject(final String key, final T value, final long timeout, final TimeUnit timeUnit) { + redisTemplate.opsForValue().set(key, value, timeout, timeUnit); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout) { + return expire(key, timeout, TimeUnit.SECONDS); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @param unit 时间单位 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout, final TimeUnit unit) { + return redisTemplate.expire(key, timeout, unit); + } + + /** + * 获取有效时间 + * + * @param key Redis键 + * @return 有效时间 + */ + public long getExpire(final String key) { + return redisTemplate.getExpire(key); + } + + /** + * 判断 key是否存在 + * + * @param key 键 + * @return true 存在 false不存在 + */ + public Boolean hasKey(String key) { + return redisTemplate.hasKey(key); + } + + /** + * 获得缓存的基本对象。 + * + * @param key 缓存键值 + * @return 缓存键值对应的数据 + */ + public T getCacheObject(final String key) { + ValueOperations operation = redisTemplate.opsForValue(); + return operation.get(key); + } + + /** + * 删除单个对象 + * + * @param key + */ + public boolean deleteObject(final String key) { + return redisTemplate.delete(key); + } + + /** + * 删除集合对象 + * + * @param collection 多个对象 + * @return + */ + public boolean deleteObject(final Collection collection) { + return redisTemplate.delete(collection) > 0; + } + + /** + * 缓存List数据 + * + * @param key 缓存的键值 + * @param dataList 待缓存的List数据 + * @return 缓存的对象 + */ + public long setCacheList(final String key, final List dataList) { + Long count = redisTemplate.opsForList().rightPushAll(key, dataList); + return count == null ? 0 : count; + } + + /** + * 获得缓存的list对象 + * + * @param key 缓存的键值 + * @return 缓存键值对应的数据 + */ + public List getCacheList(final String key) { + return redisTemplate.opsForList().range(key, 0, -1); + } + + /** + * 缓存Set + * + * @param key 缓存键值 + * @param dataSet 缓存的数据 + * @return 缓存数据的对象 + */ + public BoundSetOperations setCacheSet(final String key, final Set dataSet) { + BoundSetOperations setOperation = redisTemplate.boundSetOps(key); + Iterator it = dataSet.iterator(); + while (it.hasNext()) { + setOperation.add(it.next()); + } + return setOperation; + } + + /** + * 获得缓存的set + * + * @param key + * @return + */ + public Set getCacheSet(final String key) { + return redisTemplate.opsForSet().members(key); + } + + /** + * 缓存Map + * + * @param key + * @param dataMap + */ + public void setCacheMap(final String key, final Map dataMap) { + if (dataMap != null) { + redisTemplate.opsForHash().putAll(key, dataMap); + } + } + + /** + * 获得缓存的Map + * + * @param key + * @return + */ + public Map getCacheMap(final String key) { + return redisTemplate.opsForHash().entries(key); + } + + /** + * 往Hash中存入数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @param value 值 + */ + public void setCacheMapValue(final String key, final String hKey, final T value) { + redisTemplate.opsForHash().put(key, hKey, value); + } + + /** + * 获取Hash中的数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return Hash中的对象 + */ + public T getCacheMapValue(final String key, final String hKey) { + HashOperations opsForHash = redisTemplate.opsForHash(); + return opsForHash.get(key, hKey); + } + + /** + * 获取多个Hash中的数据 + * + * @param key Redis键 + * @param hKeys Hash键集合 + * @return Hash对象集合 + */ + public List getMultiCacheMapValue(final String key, final Collection hKeys) { + return redisTemplate.opsForHash().multiGet(key, hKeys); + } + + /** + * 删除Hash中的某条数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return 是否成功 + */ + public boolean deleteCacheMapValue(final String key, final String hKey) { + return redisTemplate.opsForHash().delete(key, hKey) > 0; + } + + /** + * 获得缓存的基本对象列表 + * + * @param pattern 字符串前缀 + * @return 对象列表 + */ + public Collection keys(final String pattern) { + return redisTemplate.keys(pattern); + } + + @Override + public Set getCachekeys(Cache cache) { + Set keyset = new HashSet<>(); + Set keysets = redisTemplate.keys(cache.getName() + "*"); + for (Object s : keysets) { + keyset.add(StringUtils.replace(s.toString(), cache.getName() + ":", "")); + } + return keyset; + } +} diff --git a/boyue-middleware/boyue-middleware-starter/pom.xml b/boyue-middleware/boyue-middleware-starter/pom.xml new file mode 100644 index 0000000..bf001d1 --- /dev/null +++ b/boyue-middleware/boyue-middleware-starter/pom.xml @@ -0,0 +1,38 @@ + + + + boyue-middleware + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-middleware-starter + + + 中间件 + + + + + + + com.boyue + boyue-common + + + + + com.boyue + boyue-middleware-redis + + + + com.boyue + boyue-middleware-rabbitmq + + + + + diff --git a/boyue-middleware/pom.xml b/boyue-middleware/pom.xml new file mode 100644 index 0000000..2c1614e --- /dev/null +++ b/boyue-middleware/pom.xml @@ -0,0 +1,44 @@ + + + + boyuehasfj-java + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-middleware + + + + + + 中间件 + + + + + + com.boyue + boyue-middleware-redis + ${boyue.version} + + + + com.boyue + boyue-middleware-rabbitmq + ${boyue.version} + + + + + + + + boyue-middleware-redis + boyue-middleware-rabbitmq + boyue-middleware-starter + + pom + diff --git a/boyue-models/boyue-flowable/pom.xml b/boyue-models/boyue-flowable/pom.xml new file mode 100644 index 0000000..9e859c5 --- /dev/null +++ b/boyue-models/boyue-flowable/pom.xml @@ -0,0 +1,64 @@ + + + + boyue-models + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-flowable + + + form表单 + + + + + + + com.boyue + boyue-system + + + + + org.flowable + flowable-spring-boot-starter + 7.1.0 + + + org.flowable + flowable-spring-security + + + + + + com.googlecode.aviator + aviator + 5.3.3 + + + + org.graalvm.js + js + 24.2.1 + pom + + + org.graalvm.js + js-scriptengine + 24.2.1 + runtime + + + + com.boyue + boyue-form + + + + + \ No newline at end of file diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/common/constant/ProcessConstants.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/common/constant/ProcessConstants.java new file mode 100644 index 0000000..5196aee --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/common/constant/ProcessConstants.java @@ -0,0 +1,80 @@ +package com.boyue.flowable.common.constant; + +/** + * 流程常量信息 + * + * @author Tony + * @date 2021/4/17 22:46 + */ +public class ProcessConstants { + + /** + * 动态数据 + */ + public static final String DYNAMIC = "dynamic"; + + /** + * 固定任务接收 + */ + public static final String FIXED = "fixed"; + + /** + * 单个审批人 + */ + public static final String ASSIGNEE = "assignee"; + + + /** + * 候选人 + */ + public static final String CANDIDATE_USERS = "candidateUsers"; + + + /** + * 审批组 + */ + public static final String CANDIDATE_GROUPS = "candidateGroups"; + + /** + * 单个审批人 + */ + public static final String PROCESS_APPROVAL = "approval"; + + /** + * 会签人员 + */ + public static final String PROCESS_MULTI_INSTANCE_USER = "userList"; + + /** + * nameapace + */ + public static final String NAMASPASE = "http://flowable.org/bpmn"; + + /** + * 会签节点 + */ + public static final String PROCESS_MULTI_INSTANCE = "multiInstance"; + + /** + * 自定义属性 dataType + */ + public static final String PROCESS_CUSTOM_DATA_TYPE = "dataType"; + + /** + * 自定义属性 userType + */ + public static final String PROCESS_CUSTOM_USER_TYPE = "userType"; + + /** + * 初始化人员 + */ + public static final String PROCESS_INITIATOR = "INITIATOR"; + + + /** + * 流程跳过 + */ + public static final String FLOWABLE_SKIP_EXPRESSION_ENABLED = "_FLOWABLE_SKIP_EXPRESSION_ENABLED"; + + +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/common/enums/FlowComment.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/common/enums/FlowComment.java new file mode 100644 index 0000000..f615e2d --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/common/enums/FlowComment.java @@ -0,0 +1,43 @@ +package com.boyue.flowable.common.enums; + +/** + * 流程意见类型 + * + * @author Tony + * @date 2021/4/19 + */ +public enum FlowComment { + + /** + * 说明 + */ + NORMAL("1", "正常意见"), + REBACK("2", "退回意见"), + REJECT("3", "驳回意见"), + DELEGATE("4", "委派意见"), + ASSIGN("5", "转办意见"), + STOP("6", "终止流程"); + + /** + * 类型 + */ + private final String type; + + /** + * 说明 + */ + private final String remark; + + FlowComment(String type, String remark) { + this.type = type; + this.remark = remark; + } + + public String getType() { + return type; + } + + public String getRemark() { + return remark; + } +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/common/expand/el/BaseEl.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/common/expand/el/BaseEl.java new file mode 100644 index 0000000..1199923 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/common/expand/el/BaseEl.java @@ -0,0 +1,12 @@ +package com.boyue.flowable.common.expand.el; + +/** + * 扩展表达式 + * + * @author Tony + * @date 2023-03-04 09:10 + */ +public interface BaseEl { + +} + diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/common/expand/el/FlowEl.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/common/expand/el/FlowEl.java new file mode 100644 index 0000000..1a5e480 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/common/expand/el/FlowEl.java @@ -0,0 +1,34 @@ +package com.boyue.flowable.common.expand.el; + +import org.springframework.stereotype.Component; + +import com.boyue.system.service.ISysDeptService; + +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; + + +/** + * 扩展表达式 + * + * @author Tony + * @date 2023-03-04 12:10 + */ +@Component +@Slf4j +public class FlowEl implements BaseEl { + + @Resource + private ISysDeptService sysDeptService; + + public String findDeptLeader(String name){ + log.info("开始查询表达式变量值,getName"); + return name; + } + + public String getName(String name){ + log.info("开始查询表达式变量值,getName"); + return name; + } +} + diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/config/FlowableConfig.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/config/FlowableConfig.java new file mode 100644 index 0000000..d8c2681 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/config/FlowableConfig.java @@ -0,0 +1,42 @@ +package com.boyue.flowable.config; + +import java.util.concurrent.ThreadPoolExecutor; + +import org.flowable.engine.impl.db.DbIdGenerator; +import org.flowable.spring.SpringProcessEngineConfiguration; +import org.flowable.spring.boot.EngineConfigurationConfigurer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +@Configuration +public class FlowableConfig implements EngineConfigurationConfigurer { + @Override + public void configure(SpringProcessEngineConfiguration engineConfiguration) { + engineConfiguration.setActivityFontName("宋体"); + engineConfiguration.setLabelFontName("宋体"); + engineConfiguration.setAnnotationFontName("宋体"); + engineConfiguration.setIdGenerator(new DbIdGenerator()); + } + + @Bean("applicationTaskExecutor") + public ThreadPoolTaskExecutor applicationTaskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + // 此方法返回可用处理器的虚拟机的最大数量; 不小于1 + int core = Runtime.getRuntime().availableProcessors(); + executor.setCorePoolSize(core);// 设置核心线程数 + executor.setMaxPoolSize(core * 2 + 1);// 设置最大线程数 + executor.setKeepAliveSeconds(120);// 除核心线程外的线程存活时间 + executor.setQueueCapacity(120);// 如果传入值大于0,底层队列使用的是LinkedBlockingQueue,否则默认使用SynchronousQueue + executor.setThreadNamePrefix("thread-default-execute");// 线程名称前缀 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());// 设置拒绝策略,抛出 + // RejectedExecutionException来拒绝新任务的处理。 + // executor.setRejectedExecutionHandler(new + // ThreadPoolExecutor.CallerRunsPolicy());//设置拒绝策略,使用主线程 + // executor.setRejectedExecutionHandler(new + // ThreadPoolExecutor.DiscardPolicy());//设置拒绝策略,直接丢弃掉 + // executor.setRejectedExecutionHandler(new + // ThreadPoolExecutor.DiscardOldestPolicy());//设置拒绝策略,丢弃最早的未处理的任务请求。 + return executor; + } +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/controller/FlowDefinitionController.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/controller/FlowDefinitionController.java new file mode 100644 index 0000000..4d5e220 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/controller/FlowDefinitionController.java @@ -0,0 +1,228 @@ +package com.boyue.flowable.controller; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; + +import javax.imageio.ImageIO; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.domain.entity.SysRole; +import com.boyue.common.core.domain.entity.SysUser; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.enums.BusinessType; +import com.boyue.flowable.domain.SysDeployForm; +import com.boyue.flowable.domain.SysExpression; +import com.boyue.flowable.domain.dto.FlowSaveXmlVo; +import com.boyue.flowable.service.IFlowDefinitionService; +import com.boyue.flowable.service.ISysDeployFormService; +import com.boyue.flowable.service.ISysExpressionService; +import com.boyue.system.service.ISysRoleService; +import com.boyue.system.service.ISysUserService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; + +/** + *

+ * 工作流程定义 + *

+ * + * @author Tony + * @date 2021-04-03 + */ +@Slf4j +@Tag(name = "流程定义") +@RestController +@RequestMapping("/flowable/definition") +public class FlowDefinitionController extends BaseController { + + @Autowired + private IFlowDefinitionService flowDefinitionService; + + @Autowired + private ISysUserService userService; + + @Resource + private ISysRoleService sysRoleService; + @Resource + private ISysExpressionService sysExpressionService; + + @Resource + private ISysDeployFormService sysDeployFormService; + + @GetMapping(value = "/list") + @Operation(summary = "流程定义列表") + public TableDataInfo list( + @RequestParam Integer pageNum, + @RequestParam Integer pageSize, + @RequestParam(required = false) String name) { + return getDataTable(flowDefinitionService.list(name, pageNum, pageSize)); + } + + @Operation(summary = "导入流程文件") + @PostMapping("/import") + public AjaxResult importFile( + @RequestParam(required = false) String name, + @RequestParam(required = false) String category, + MultipartFile file) { + InputStream in = null; + try { + in = file.getInputStream(); + flowDefinitionService.importFile(name, category, in); + } catch (Exception e) { + log.error("导入失败:", e); + return AjaxResult.success(e.getMessage()); + } finally { + try { + if (in != null) { + in.close(); + } + } catch (IOException e) { + log.error("关闭输入流出错", e); + } + } + + return AjaxResult.success("导入成功"); + } + + @Operation(summary = "读取xml文件") + @GetMapping("/readXml/{deployId}") + public AjaxResult readXml(@PathVariable String deployId) { + try { + return flowDefinitionService.readXml(deployId); + } catch (Exception e) { + return AjaxResult.error("加载xml文件异常"); + } + + } + + @Operation(summary = "读取图片文件") + @GetMapping("/readImage/{deployId}") + public void readImage(@PathVariable String deployId, HttpServletResponse response) { + OutputStream os = null; + BufferedImage image = null; + try { + image = ImageIO.read(flowDefinitionService.readImage(deployId)); + response.setContentType("image/png"); + os = response.getOutputStream(); + if (image != null) { + ImageIO.write(image, "png", os); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (os != null) { + os.flush(); + os.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + } + + @Operation(summary = "保存流程设计器内的xml文件") + @Log(title = "流程定义", businessType = BusinessType.INSERT) + @PostMapping("/save") + public AjaxResult save(@RequestBody FlowSaveXmlVo vo) { + InputStream in = null; + try { + in = new ByteArrayInputStream(vo.getXml().getBytes(StandardCharsets.UTF_8)); + flowDefinitionService.importFile(vo.getName(), vo.getCategory(), in); + } catch (Exception e) { + log.error("导入失败:", e); + return AjaxResult.error(e.getMessage()); + } finally { + try { + if (in != null) { + in.close(); + } + } catch (IOException e) { + log.error("关闭输入流出错", e); + } + } + + return AjaxResult.success("导入成功"); + } + + @Operation(summary = "发起流程") + @Log(title = "发起流程", businessType = BusinessType.INSERT) + @PostMapping("/start/{procDefId}") + public AjaxResult start(@PathVariable String procDefId, @RequestBody Map variables) { + return flowDefinitionService.startProcessInstanceById(procDefId, variables); + } + + @Operation(summary = "激活或挂起流程定义") + @Log(title = "激活/挂起流程", businessType = BusinessType.UPDATE) + @PutMapping(value = "/updateState") + public AjaxResult updateState(@RequestParam Integer state, @RequestParam String deployId) { + flowDefinitionService.updateState(state, deployId); + return AjaxResult.success(); + } + + @Operation(summary = "删除流程") + @Log(title = "删除流程", businessType = BusinessType.DELETE) + @DeleteMapping(value = "/{deployIds}") + public AjaxResult delete(@PathVariable String[] deployIds) { + for (String deployId : deployIds) { + flowDefinitionService.delete(deployId); + } + return AjaxResult.success(); + } + + @Operation(summary = "指定流程办理人员列表") + @GetMapping("/userList") + public AjaxResult userList(SysUser user) { + List list = userService.selectUserList(user); + return AjaxResult.success(list); + } + + @Operation(summary = "指定流程办理组列表") + @GetMapping("/roleList") + public AjaxResult roleList(SysRole role) { + List list = sysRoleService.selectRoleList(role); + return AjaxResult.success(list); + } + + @Operation(summary = "指定流程达式列表") + @GetMapping("/expList") + public AjaxResult expList(SysExpression sysExpression) { + List list = sysExpressionService.selectSysExpressionList(sysExpression); + return AjaxResult.success(list); + } + + /** + * 挂载流程表单 + */ + @Log(title = "流程表单", businessType = BusinessType.INSERT) + @PostMapping("/addDeployForm") + public AjaxResult addDeployForm(@RequestBody SysDeployForm sysDeployForm) { + return toAjax(sysDeployFormService.insertSysDeployForm(sysDeployForm)); + } + +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/controller/FlowInstanceController.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/controller/FlowInstanceController.java new file mode 100644 index 0000000..c1e81bc --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/controller/FlowInstanceController.java @@ -0,0 +1,72 @@ +package com.boyue.flowable.controller; + +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.enums.BusinessType; +import com.boyue.flowable.domain.vo.FlowTaskVo; +import com.boyue.flowable.service.IFlowInstanceService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; + +/** + *

+ * 工作流流程实例管理 + *

+ * + * @author Tony + * @date 2021-04-03 + */ +@Slf4j +@Tag(name = "工作流流程实例管理") +@RestController +@RequestMapping("/flowable/instance") +public class FlowInstanceController extends BaseController { + + @Autowired + private IFlowInstanceService flowInstanceService; + + @Operation(summary = "根据流程定义id启动流程实例") + @PostMapping("/startBy/{procDefId}") + public AjaxResult startById(@PathVariable String procDefId, @RequestBody Map variables) { + return flowInstanceService.startProcessInstanceById(procDefId, variables); + + } + + @Operation(summary = "激活或挂起流程实例") + @PostMapping(value = "/updateState") + public AjaxResult updateState(@RequestParam Integer state, @RequestParam String instanceId) { + flowInstanceService.updateState(state, instanceId); + return AjaxResult.success(); + } + + @Operation(summary = "结束流程实例") + @PostMapping(value = "/stopProcessInstance") + public AjaxResult stopProcessInstance(@RequestBody FlowTaskVo flowTaskVo) { + flowInstanceService.stopProcessInstance(flowTaskVo); + return AjaxResult.success(); + } + + @Operation(summary = "删除流程实例") + @Log(title = "删除任务", businessType = BusinessType.DELETE) + @DeleteMapping(value = "/delete/{instanceIds}") + public AjaxResult delete(@PathVariable String[] instanceIds, @RequestParam(required = false) String deleteReason) { + for (String instanceId : instanceIds) { + flowInstanceService.delete(instanceId, deleteReason); + } + return AjaxResult.success(); + } +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/controller/FlowTaskController.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/controller/FlowTaskController.java new file mode 100644 index 0000000..1fa94d4 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/controller/FlowTaskController.java @@ -0,0 +1,289 @@ +package com.boyue.flowable.controller; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; + +import javax.imageio.ImageIO; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.enums.BusinessType; +import com.boyue.flowable.domain.dto.FlowTaskDto; +import com.boyue.flowable.domain.vo.FlowQueryVo; +import com.boyue.flowable.domain.vo.FlowTaskVo; +import com.boyue.flowable.service.IFlowTaskService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; + +/** + *

+ * 工作流任务管理 + *

+ * + * @author Tony + * @date 2021-04-03 + */ +@Slf4j +@Tag(name = "工作流流程任务管理") +@RestController +@RequestMapping("/flowable/task") +public class FlowTaskController extends BaseController { + + @Autowired + private IFlowTaskService flowTaskService; + + @Operation(summary = "我发起的流程") + @GetMapping(value = "/myProcess") + public TableDataInfo myProcess(FlowQueryVo queryVo) { + List myProcess = flowTaskService.myProcess(queryVo); + return getDataTable(myProcess); + } + + @Operation(summary = "取消申请") + @Log(title = "取消申请", businessType = BusinessType.UPDATE) + @PostMapping(value = "/stopProcess") + public AjaxResult stopProcess(@RequestBody FlowTaskVo flowTaskVo) { + return flowTaskService.stopProcess(flowTaskVo); + } + + @Operation(summary = "撤回流程") + @Log(title = "撤回流程", businessType = BusinessType.UPDATE) + @PostMapping(value = "/revokeProcess") + public AjaxResult revokeProcess(@RequestBody FlowTaskVo flowTaskVo) { + return flowTaskService.revokeProcess(flowTaskVo); + } + + @Operation(summary = "获取待办列表") + @GetMapping(value = "/todoList") + public TableDataInfo todoList(FlowQueryVo queryVo) { + return getDataTable(flowTaskService.todoList(queryVo)); + } + + @Operation(summary = "获取已办任务") + @GetMapping(value = "/finishedList") + public TableDataInfo finishedList(FlowQueryVo queryVo) { + return getDataTable(flowTaskService.finishedList(queryVo)); + } + + @Operation(summary = "流程历史流转记录") + @GetMapping(value = "/flowRecord") + public AjaxResult flowRecord(String procInsId, String deployId) { + return flowTaskService.flowRecord(procInsId, deployId); + } + + @Operation(summary = "根据任务ID查询挂载的表单信息") + @GetMapping(value = "/getTaskForm") + public AjaxResult getTaskForm(String taskId) { + return flowTaskService.getTaskForm(taskId); + } + + @Operation(summary = "流程初始化表单") + @GetMapping(value = "/flowFormData") + public AjaxResult flowFormData(String deployId) { + return flowTaskService.flowFormData(deployId); + } + + @Operation(summary = "获取流程变量") + @GetMapping(value = "/processVariables/{taskId}") + public AjaxResult processVariables(@PathVariable String taskId) { + return flowTaskService.processVariables(taskId); + } + + @Operation(summary = "审批任务") + @Log(title = "审批任务", businessType = BusinessType.UPDATE) + @PostMapping(value = "/complete") + public AjaxResult complete(@RequestBody FlowTaskVo flowTaskVo) { + return flowTaskService.complete(flowTaskVo); + } + + @Operation(summary = "驳回任务") + @Log(title = "驳回任务", businessType = BusinessType.UPDATE) + @PostMapping(value = "/reject") + public AjaxResult taskReject(@RequestBody FlowTaskVo flowTaskVo) { + flowTaskService.taskReject(flowTaskVo); + return AjaxResult.success(); + } + + @Operation(summary = "退回任务") + @Log(title = "退回任务", businessType = BusinessType.UPDATE) + @PostMapping(value = "/return") + public AjaxResult taskReturn(@RequestBody FlowTaskVo flowTaskVo) { + flowTaskService.taskReturn(flowTaskVo); + return AjaxResult.success(); + } + + @Operation(summary = "获取所有可回退的节点") + @PostMapping(value = "/returnList") + public AjaxResult findReturnTaskList(@RequestBody FlowTaskVo flowTaskVo) { + return flowTaskService.findReturnTaskList(flowTaskVo); + } + + @Operation(summary = "删除任务") + @Log(title = "删除任务", businessType = BusinessType.DELETE) + @DeleteMapping(value = "/delete") + public AjaxResult delete(@RequestBody FlowTaskVo flowTaskVo) { + flowTaskService.deleteTask(flowTaskVo); + return AjaxResult.success(); + } + + @Operation(summary = "认领/签收任务") + @PostMapping(value = "/claim") + public AjaxResult claim(@RequestBody FlowTaskVo flowTaskVo) { + flowTaskService.claim(flowTaskVo); + return AjaxResult.success(); + } + + @Operation(summary = "取消认领/签收任务") + @PostMapping(value = "/unClaim") + public AjaxResult unClaim(@RequestBody FlowTaskVo flowTaskVo) { + flowTaskService.unClaim(flowTaskVo); + return AjaxResult.success(); + } + + @Operation(summary = "委派任务") + @PostMapping(value = "/delegateTask") + public AjaxResult delegate(@RequestBody FlowTaskVo flowTaskVo) { + flowTaskService.delegateTask(flowTaskVo); + return AjaxResult.success(); + } + + @Operation(summary = "任务归还") + @PostMapping(value = "/resolveTask") + public AjaxResult resolveTask(@RequestBody FlowTaskVo flowTaskVo) { + flowTaskService.resolveTask(flowTaskVo); + return AjaxResult.success(); + } + + @Operation(summary = "转办任务") + @PostMapping(value = "/assignTask") + public AjaxResult assign(@RequestBody FlowTaskVo flowTaskVo) { + flowTaskService.assignTask(flowTaskVo); + return AjaxResult.success(); + } + + @PostMapping(value = "/addMultiInstanceExecution") + @Operation(summary = "多实例加签") + public AjaxResult addMultiInstanceExecution(@RequestBody FlowTaskVo flowTaskVo) { + flowTaskService.addMultiInstanceExecution(flowTaskVo); + return AjaxResult.success("加签成功"); + } + + @PostMapping(value = "/deleteMultiInstanceExecution") + @Operation(summary = "多实例减签") + public AjaxResult deleteMultiInstanceExecution(@RequestBody FlowTaskVo flowTaskVo) { + flowTaskService.deleteMultiInstanceExecution(flowTaskVo); + return AjaxResult.success("减签成功"); + } + + @Operation(summary = "获取下一节点") + @PostMapping(value = "/nextFlowNode") + public AjaxResult getNextFlowNode(@RequestBody FlowTaskVo flowTaskVo) { + return flowTaskService.getNextFlowNode(flowTaskVo); + } + + @Operation(summary = "流程发起时获取下一节点") + @PostMapping(value = "/nextFlowNodeByStart") + public AjaxResult getNextFlowNodeByStart(@RequestBody FlowTaskVo flowTaskVo) { + return flowTaskService.getNextFlowNodeByStart(flowTaskVo); + } + + /** + * 生成流程图 + * + * @param processId 任务ID + */ + @GetMapping("/diagram/{processId}") + public void genProcessDiagram(HttpServletResponse response, @PathVariable("processId") String processId) { + InputStream inputStream = flowTaskService.diagram(processId); + OutputStream os = null; + BufferedImage image = null; + try { + image = ImageIO.read(inputStream); + response.setContentType("image/png"); + os = response.getOutputStream(); + if (image != null) { + ImageIO.write(image, "png", os); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + if (os != null) { + os.flush(); + os.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * 获取流程执行节点 + * + * @param procInsId 流程实例编号 + * @param procInsId 任务执行编号 + */ + @GetMapping("/flowViewer/{procInsId}/{executionId}") + public AjaxResult getFlowViewer( + @PathVariable("procInsId") String procInsId, + @PathVariable("executionId") String executionId) { + return flowTaskService.getFlowViewer(procInsId, executionId); + } + + /** + * 流程节点信息 + * + * @param procInsId 流程实例id + * @return + */ + @GetMapping("/flowXmlAndNode") + public AjaxResult flowXmlAndNode(@RequestParam(value = "procInsId", required = false) String procInsId, + @RequestParam(value = "deployId", required = false) String deployId) { + return flowTaskService.flowXmlAndNode(procInsId, deployId); + } + + /** + * 流程节点表单 + * + * @param taskId 流程任务编号 + * @return + */ + @GetMapping("/flowTaskForm") + public AjaxResult flowTaskForm(@RequestParam(value = "taskId", required = false) String taskId) throws Exception { + return flowTaskService.flowTaskForm(taskId); + } + + /** + * 流程节点信息 + * + * @param procInsId 流程实例编号 + * @param elementId 流程节点编号 + * @return + */ + @GetMapping("/flowTaskInfo") + public AjaxResult flowTaskInfo( + @RequestParam(value = "procInsId") String procInsId, + @RequestParam(value = "elementId") String elementId) { + return flowTaskService.flowTaskInfo(procInsId, elementId); + } + +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/controller/SysExpressionController.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/controller/SysExpressionController.java new file mode 100644 index 0000000..3c4a5cd --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/controller/SysExpressionController.java @@ -0,0 +1,100 @@ +package com.boyue.flowable.controller; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.enums.BusinessType; +import com.boyue.common.utils.poi.ExcelUtil; +import com.boyue.flowable.domain.SysExpression; +import com.boyue.flowable.service.ISysExpressionService; + +import jakarta.servlet.http.HttpServletResponse; + +/** + * 流程达式Controller + * + * @author boyue + * @date 2022-12-12 + */ +@RestController +@RequestMapping("/system/expression") +public class SysExpressionController extends BaseController { + @Autowired + private ISysExpressionService sysExpressionService; + + /** + * 查询流程达式列表 + */ + @PreAuthorize("@ss.hasPermi('system:expression:list')") + @GetMapping("/list") + public TableDataInfo list(SysExpression sysExpression) { + startPage(); + List list = sysExpressionService.selectSysExpressionList(sysExpression); + return getDataTable(list); + } + + /** + * 导出流程达式列表 + */ + @PreAuthorize("@ss.hasPermi('system:expression:export')") + @Log(title = "流程达式", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, SysExpression sysExpression) { + List list = sysExpressionService.selectSysExpressionList(sysExpression); + ExcelUtil util = new ExcelUtil(SysExpression.class); + util.exportExcel(response, list, "流程达式数据"); + } + + /** + * 获取流程达式详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:expression:query')") + @GetMapping(value = "/{id}") + public AjaxResult getInfo(@PathVariable("id") Long id) { + return success(sysExpressionService.selectSysExpressionById(id)); + } + + /** + * 新增流程达式 + */ + @PreAuthorize("@ss.hasPermi('system:expression:add')") + @Log(title = "流程达式", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody SysExpression sysExpression) { + return toAjax(sysExpressionService.insertSysExpression(sysExpression)); + } + + /** + * 修改流程达式 + */ + @PreAuthorize("@ss.hasPermi('system:expression:edit')") + @Log(title = "流程达式", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody SysExpression sysExpression) { + return toAjax(sysExpressionService.updateSysExpression(sysExpression)); + } + + /** + * 删除流程达式 + */ + @PreAuthorize("@ss.hasPermi('system:expression:remove')") + @Log(title = "流程达式", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable Long[] ids) { + return toAjax(sysExpressionService.deleteSysExpressionByIds(ids)); + } +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/controller/SysListenerController.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/controller/SysListenerController.java new file mode 100644 index 0000000..d5611f0 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/controller/SysListenerController.java @@ -0,0 +1,100 @@ +package com.boyue.flowable.controller; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.enums.BusinessType; +import com.boyue.common.utils.poi.ExcelUtil; +import com.boyue.flowable.domain.SysListener; +import com.boyue.flowable.service.ISysListenerService; + +import jakarta.servlet.http.HttpServletResponse; + +/** + * 流程监听Controller + * + * @author Tony + * @date 2022-12-25 + */ +@RestController +@RequestMapping("/system/listener") +public class SysListenerController extends BaseController { + @Autowired + private ISysListenerService sysListenerService; + + /** + * 查询流程监听列表 + */ + @PreAuthorize("@ss.hasPermi('system:listener:list')") + @GetMapping("/list") + public TableDataInfo list(SysListener sysListener) { + startPage(); + List list = sysListenerService.selectSysListenerList(sysListener); + return getDataTable(list); + } + + /** + * 导出流程监听列表 + */ + @PreAuthorize("@ss.hasPermi('system:listener:export')") + @Log(title = "流程监听", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, SysListener sysListener) { + List list = sysListenerService.selectSysListenerList(sysListener); + ExcelUtil util = new ExcelUtil(SysListener.class); + util.exportExcel(response, list, "流程监听数据"); + } + + /** + * 获取流程监听详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:listener:query')") + @GetMapping(value = "/{id}") + public AjaxResult getInfo(@PathVariable("id") Long id) { + return success(sysListenerService.selectSysListenerById(id)); + } + + /** + * 新增流程监听 + */ + @PreAuthorize("@ss.hasPermi('system:listener:add')") + @Log(title = "流程监听", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody SysListener sysListener) { + return toAjax(sysListenerService.insertSysListener(sysListener)); + } + + /** + * 修改流程监听 + */ + @PreAuthorize("@ss.hasPermi('system:listener:edit')") + @Log(title = "流程监听", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody SysListener sysListener) { + return toAjax(sysListenerService.updateSysListener(sysListener)); + } + + /** + * 删除流程监听 + */ + @PreAuthorize("@ss.hasPermi('system:listener:remove')") + @Log(title = "流程监听", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable Long[] ids) { + return toAjax(sysListenerService.deleteSysListenerByIds(ids)); + } +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/SysDeployForm.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/SysDeployForm.java new file mode 100644 index 0000000..7aeab39 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/SysDeployForm.java @@ -0,0 +1,65 @@ +package com.boyue.flowable.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.boyue.common.annotation.Excel; +import com.boyue.common.core.domain.BaseEntity; + +/** + * 流程实例关联表单对象 sys_instance_form + * + * @author Tony + * @date 2021-03-30 + */ +public class SysDeployForm extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 主键 */ + private Long id; + + /** 表单主键 */ + @Excel(name = "表单主键") + private Long formId; + + /** 流程定义主键 */ + @Excel(name = "流程定义主键") + private String deployId; + + public void setId(Long id) + { + this.id = id; + } + + public Long getId() + { + return id; + } + public void setFormId(Long formId) + { + this.formId = formId; + } + + public Long getFormId() + { + return formId; + } + + public String getDeployId() { + return deployId; + } + + public void setDeployId(String deployId) { + this.deployId = deployId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("formId", getFormId()) + .append("deployId", getDeployId()) + .toString(); + } +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/SysExpression.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/SysExpression.java new file mode 100644 index 0000000..5a499a9 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/SysExpression.java @@ -0,0 +1,96 @@ +package com.boyue.flowable.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.boyue.common.annotation.Excel; +import com.boyue.common.core.domain.BaseEntity; + +/** + * 流程达式对象 sys_expression + * + * @author boyue + * @date 2022-12-12 + */ +public class SysExpression extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 表单主键 */ + private Long id; + + /** 表达式名称 */ + @Excel(name = "表达式名称") + private String name; + + /** 表达式内容 */ + @Excel(name = "表达式内容") + private String expression; + /** 表达式类型 */ + @Excel(name = "表达式类型") + private String dataType; + + /** 状态 */ + private Integer status; + + public void setId(Long id) + { + this.id = id; + } + + public Long getId() + { + return id; + } + public void setName(String name) + { + this.name = name; + } + + public String getName() + { + return name; + } + public void setExpression(String expression) + { + this.expression = expression; + } + + public String getExpression() + { + return expression; + } + public void setStatus(Integer status) + { + this.status = status; + } + + public Integer getStatus() + { + return status; + } + + public void setDataType(String dataType) { + this.dataType = dataType; + } + public String getDataType() { + return dataType; + } + + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("name", getName()) + .append("expression", getExpression()) + .append("dataType", getDataType()) + .append("createTime", getCreateTime()) + .append("updateTime", getUpdateTime()) + .append("createBy", getCreateBy()) + .append("updateBy", getUpdateBy()) + .append("status", getStatus()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/SysListener.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/SysListener.java new file mode 100644 index 0000000..81cb2c0 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/SysListener.java @@ -0,0 +1,127 @@ +package com.boyue.flowable.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.boyue.common.annotation.Excel; +import com.boyue.common.core.domain.BaseEntity; + +/** + * 流程监听对象 sys_listener + * + * @author Tony + * @date 2022-12-25 + */ +public class SysListener extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 表单主键 */ + private Long id; + + /** 名称 */ + @Excel(name = "名称") + private String name; + + /** 监听类型 */ + @Excel(name = "监听类型") + private String type; + + /** 事件类型 */ + @Excel(name = "事件类型") + private String eventType; + + /** 值类型 */ + @Excel(name = "值类型") + private String valueType; + + /** 执行内容 */ + @Excel(name = "执行内容") + private String value; + + /** 状态 */ + @Excel(name = "状态") + private Integer status; + + public void setId(Long id) + { + this.id = id; + } + + public Long getId() + { + return id; + } + public void setName(String name) + { + this.name = name; + } + + public String getName() + { + return name; + } + public void setType(String type) + { + this.type = type; + } + + public String getType() + { + return type; + } + public void setEventType(String eventType) + { + this.eventType = eventType; + } + + public String getEventType() + { + return eventType; + } + public void setValueType(String valueType) + { + this.valueType = valueType; + } + + public String getValueType() + { + return valueType; + } + public void setValue(String value) + { + this.value = value; + } + + public String getValue() + { + return value; + } + public void setStatus(Integer status) + { + this.status = status; + } + + public Integer getStatus() + { + return status; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("name", getName()) + .append("type", getType()) + .append("eventType", getEventType()) + .append("valueType", getValueType()) + .append("value", getValue()) + .append("createTime", getCreateTime()) + .append("updateTime", getUpdateTime()) + .append("createBy", getCreateBy()) + .append("updateBy", getUpdateBy()) + .append("status", getStatus()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/SysTaskForm.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/SysTaskForm.java new file mode 100644 index 0000000..9da3e2b --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/SysTaskForm.java @@ -0,0 +1,66 @@ +package com.boyue.flowable.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.boyue.common.annotation.Excel; +import com.boyue.common.core.domain.BaseEntity; + +/** + * 流程任务关联单对象 sys_task_form + * + * @author Tony + * @date 2021-04-03 + */ +public class SysTaskForm extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 主键 */ + private Long id; + + /** 表单主键 */ + @Excel(name = "表单主键") + private Long formId; + + /** 所属任务 */ + @Excel(name = "所属任务") + private String taskId; + + public void setId(Long id) + { + this.id = id; + } + + public Long getId() + { + return id; + } + public void setFormId(Long formId) + { + this.formId = formId; + } + + public Long getFormId() + { + return formId; + } + public void setTaskId(String taskId) + { + this.taskId = taskId; + } + + public String getTaskId() + { + return taskId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("formId", getFormId()) + .append("taskId", getTaskId()) + .toString(); + } +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/dto/FlowCommentDto.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/dto/FlowCommentDto.java new file mode 100644 index 0000000..1195e39 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/dto/FlowCommentDto.java @@ -0,0 +1,25 @@ +package com.boyue.flowable.domain.dto; + +import lombok.Builder; +import lombok.Data; + +import java.io.Serializable; + +/** + * @author Tony + * @date 2021/3/28 15:50 + */ +@Data +@Builder +public class FlowCommentDto implements Serializable { + + /** + * 意见类别 0 正常意见 1 退回意见 2 驳回意见 + */ + private String type; + + /** + * 意见内容 + */ + private String comment; +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/dto/FlowFromFieldDTO.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/dto/FlowFromFieldDTO.java new file mode 100644 index 0000000..d8c2fb0 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/dto/FlowFromFieldDTO.java @@ -0,0 +1,15 @@ +package com.boyue.flowable.domain.dto; + +import lombok.Data; + +import java.io.Serializable; + +/** + * @author Tony + * @date 2021/3/31 23:20 + */ +@Data +public class FlowFromFieldDTO implements Serializable { + + private Object fields; +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/dto/FlowNextDto.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/dto/FlowNextDto.java new file mode 100644 index 0000000..cddf873 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/dto/FlowNextDto.java @@ -0,0 +1,30 @@ +package com.boyue.flowable.domain.dto; + +import java.io.Serializable; + +import lombok.Data; + +/** + * 动态人员、组 + * @author Tony + * @date 2021/4/17 22:59 + */ +@Data +public class FlowNextDto implements Serializable { + + /** + * 审批人类型 + */ + private String type; + + /** + * 是否需要动态指定任务审批人 + */ + private String dataType; + + /** + * 流程变量 + */ + private String vars; + +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/dto/FlowProcDefDto.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/dto/FlowProcDefDto.java new file mode 100644 index 0000000..ff34aec --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/dto/FlowProcDefDto.java @@ -0,0 +1,56 @@ +package com.boyue.flowable.domain.dto; +import java.io.Serializable; +import java.util.Date; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + *

流程定义

+ * + * @author Tony + * @date 2021-04-03 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Schema(description = "流程定义") +public class FlowProcDefDto implements Serializable { + + @Schema(title = "流程id") + private String id; + + @Schema(title = "流程名称") + private String name; + + @Schema(title = "流程key") + private String flowKey; + + @Schema(title = "流程分类") + private String category; + + @Schema(title = "配置表单名称") + private String formName; + + @Schema(title = "配置表单id") + private Long formId; + + @Schema(title = "版本") + private int version; + + @Schema(title = "部署ID") + private String deploymentId; + + @Schema(title = "流程定义状态: 1:激活 , 2:中止") + private int suspensionState; + + @Schema(title = "部署时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date deploymentTime; + + +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/dto/FlowSaveXmlVo.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/dto/FlowSaveXmlVo.java new file mode 100644 index 0000000..cfa1817 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/dto/FlowSaveXmlVo.java @@ -0,0 +1,28 @@ +package com.boyue.flowable.domain.dto; + +import lombok.Data; + +import java.io.Serializable; + +/** + * @author Tony + * @date 2021/3/28 19:48 + */ +@Data +public class FlowSaveXmlVo implements Serializable { + + /** + * 流程名称 + */ + private String name; + + /** + * 流程分类 + */ + private String category; + + /** + * xml 文件 + */ + private String xml; +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/dto/FlowTaskDto.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/dto/FlowTaskDto.java new file mode 100644 index 0000000..e44dbd5 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/dto/FlowTaskDto.java @@ -0,0 +1,104 @@ +package com.boyue.flowable.domain.dto; + +import java.io.Serializable; +import java.util.Date; + +import com.fasterxml.jackson.annotation.JsonFormat; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 工作流任务 + *

+ * + * @author Tony + * @date 2021-04-03 + */ +@Getter +@Setter +@Schema(description = "工作流任务相关-返回参数") +public class FlowTaskDto implements Serializable { + + @Schema(title = "任务编号") + private String taskId; + + @Schema(title = "任务执行编号") + private String executionId; + + @Schema(title = "任务名称") + private String taskName; + + @Schema(title = "任务Key") + private String taskDefKey; + + @Schema(title = "任务执行人Id") + private Long assigneeId; + + @Schema(title = "部门名称") + private String deptName; + + @Schema(title = "流程发起人部门名称") + private String startDeptName; + + @Schema(title = "任务执行人名称") + private String assigneeName; + @Schema(title = "任务执行人部门") + private String assigneeDeptName;; + + @Schema(title = "流程发起人Id") + private String startUserId; + + @Schema(title = "流程发起人名称") + private String startUserName; + + @Schema(title = "流程类型") + private String category; + + @Schema(title = "流程变量信息") + private Object variables; + + @Schema(title = "局部变量信息") + private Object taskLocalVars; + + @Schema(title = "流程部署编号") + private String deployId; + + @Schema(title = "流程ID") + private String procDefId; + + @Schema(title = "流程key") + private String procDefKey; + + @Schema(title = "流程定义名称") + private String procDefName; + + @Schema(title = "流程定义内置使用版本") + private int procDefVersion; + + @Schema(title = "流程实例ID") + private String procInsId; + + @Schema(title = "历史流程实例ID") + private String hisProcInsId; + + @Schema(title = "任务耗时") + private String duration; + + @Schema(title = "任务意见") + private FlowCommentDto comment; + + @Schema(title = "候选执行人") + private String candidate; + + @Schema(title = "任务创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + @Schema(title = "任务完成时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date finishTime; + +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/dto/FlowViewerDto.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/dto/FlowViewerDto.java new file mode 100644 index 0000000..b44b43d --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/dto/FlowViewerDto.java @@ -0,0 +1,23 @@ +package com.boyue.flowable.domain.dto; + +import lombok.Data; + +import java.io.Serializable; + +/** + * @author Tony + * @date 2021/4/21 20:55 + */ +@Data +public class FlowViewerDto implements Serializable { + + /** + * 流程key + */ + private String key; + + /** + * 是否完成(已经审批) + */ + private boolean completed; +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/vo/FlowQueryVo.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/vo/FlowQueryVo.java new file mode 100644 index 0000000..34c97db --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/vo/FlowQueryVo.java @@ -0,0 +1,32 @@ +package com.boyue.flowable.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + *

流程任务

+ * + * @author Tony + * @date 2021-04-03 + */ +@Data +@Schema(description = "工作流任务相关--请求参数") +public class FlowQueryVo { + + @Schema(title = "流程名称") + private String name; + + @Schema(title = "开始时间") + private String startTime; + + @Schema(title = "结束时间") + private String endTime; + + @Schema(title = "当前页码") + private Integer pageNum; + + @Schema(title = "每页条数") + private Integer pageSize; + + +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/vo/FlowTaskVo.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/vo/FlowTaskVo.java new file mode 100644 index 0000000..98aa263 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/vo/FlowTaskVo.java @@ -0,0 +1,55 @@ +package com.boyue.flowable.domain.vo; + +import java.util.List; +import java.util.Map; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + *

流程任务

+ * + * @author Tony + * @date 2021-04-03 + */ +@Data +@Schema(description = "工作流任务相关--请求参数") +public class FlowTaskVo { + + @Schema(title = "任务Id") + private String taskId; + + @Schema(title = "用户Id") + private String userId; + + @Schema(title = "任务意见") + private String comment; + + @Schema(title = "流程实例Id") + private String instanceId; + + @Schema(title = "节点") + private String targetKey; + + private String deploymentId; + @Schema(title = "流程环节定义ID") + private String defId; + + @Schema(title = "子执行流ID") + private String currentChildExecutionId; + + @Schema(title = "子执行流是否已执行") + private Boolean flag; + + @Schema(title = "流程变量信息") + private Map variables; + + @Schema(title = "审批人") + private String assignee; + + @Schema(title = "候选人") + private List candidateUsers; + + @Schema(title = "审批组") + private List candidateGroups; +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/vo/ReturnTaskNodeVo.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/vo/ReturnTaskNodeVo.java new file mode 100644 index 0000000..66a76ad --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/domain/vo/ReturnTaskNodeVo.java @@ -0,0 +1,22 @@ +package com.boyue.flowable.domain.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + *

可退回节点

+ * + * @author tony + * @date 2022-04-23 11:01:52 + */ +@Data +@Schema(description = "可退回节点") +public class ReturnTaskNodeVo { + + @Schema(title = "任务Id") + private String id; + + @Schema(title = "用户Id") + private String name; + +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/expression/FlowDelegationExpression.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/expression/FlowDelegationExpression.java new file mode 100644 index 0000000..a8f2e98 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/expression/FlowDelegationExpression.java @@ -0,0 +1,16 @@ +package com.boyue.flowable.expression; + +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.JavaDelegate; +import org.springframework.stereotype.Component; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component("flowDelegationExpression") +public class FlowDelegationExpression implements JavaDelegate { + @Override + public void execute(DelegateExecution execution) { + log.info("代理表达式执行器:{}", execution); + } +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/factory/FlowServiceFactory.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/factory/FlowServiceFactory.java new file mode 100644 index 0000000..2a7519a --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/factory/FlowServiceFactory.java @@ -0,0 +1,48 @@ +package com.boyue.flowable.factory; + +import org.flowable.engine.HistoryService; +import org.flowable.engine.IdentityService; +import org.flowable.engine.ManagementService; +import org.flowable.engine.ProcessEngine; +import org.flowable.engine.RepositoryService; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.TaskService; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import jakarta.annotation.Resource; +import lombok.Getter; + + +/** + * flowable 引擎注入封装 + * @author Tony + * @date 2021-04-03 + */ +@Component +@Getter +public class FlowServiceFactory { + + @Resource + protected RepositoryService repositoryService; + + @Resource + protected RuntimeService runtimeService; + + @Resource + protected IdentityService identityService; + + @Resource + protected TaskService taskService; + + @Resource + protected HistoryService historyService; + + @Resource + protected ManagementService managementService; + + @Qualifier("processEngine") + @Resource + protected ProcessEngine processEngine; + +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/flow/CustomProcessDiagramCanvas.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/flow/CustomProcessDiagramCanvas.java new file mode 100644 index 0000000..5cca47a --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/flow/CustomProcessDiagramCanvas.java @@ -0,0 +1,457 @@ +package com.boyue.flowable.flow; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Font; +import java.awt.Paint; +import java.awt.RenderingHints; +import java.awt.Stroke; +import java.awt.font.FontRenderContext; +import java.awt.font.LineBreakMeasurer; +import java.awt.font.TextAttribute; +import java.awt.font.TextLayout; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Rectangle2D; +import java.awt.geom.RoundRectangle2D; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.text.AttributedCharacterIterator; +import java.text.AttributedString; + +import javax.imageio.ImageIO; + +import org.flowable.bpmn.model.AssociationDirection; +import org.flowable.bpmn.model.GraphicInfo; +import org.flowable.image.impl.DefaultProcessDiagramCanvas; +import org.flowable.image.util.ReflectUtil; + +/** + * @author Tony + * @date 2021/4/4 23:58 + */ +public class CustomProcessDiagramCanvas extends DefaultProcessDiagramCanvas { + // 定义走过流程连线颜色为绿色 + protected static Color HIGHLIGHT_SequenceFlow_COLOR = Color.GREEN; + // 设置未走过流程的连接线颜色 + protected static Color CONNECTION_COLOR = Color.BLACK; + // 设置flows连接线字体颜色red + protected static Color LABEL_COLOR = new Color(0, 0, 0); + // 高亮显示task框颜色 + protected static Color HIGHLIGHT_COLOR = Color.GREEN; + protected static Color HIGHLIGHT_COLOR1 = Color.RED; + + public CustomProcessDiagramCanvas(int width, int height, int minX, int minY, String imageType, + String activityFontName, String labelFontName, String annotationFontName, + ClassLoader customClassLoader) { + super(width, height, minX, minY, imageType, activityFontName, labelFontName, annotationFontName, + customClassLoader); + this.initialize(imageType); + } + + /** + * 重写绘制连线的方式,设置绘制颜色 + * + * @param xPoints + * @param yPoints + * @param conditional + * @param isDefault + * @param connectionType + * @param associationDirection + * @param highLighted + * @param scaleFactor + */ + @Override + public void drawConnection(int[] xPoints, int[] yPoints, boolean conditional, boolean isDefault, + String connectionType, AssociationDirection associationDirection, boolean highLighted, + double scaleFactor) { + Paint originalPaint = this.g.getPaint(); + Stroke originalStroke = this.g.getStroke(); + this.g.setPaint(CONNECTION_COLOR); + if (connectionType.equals("association")) { + this.g.setStroke(ASSOCIATION_STROKE); + } else if (highLighted) { + this.g.setPaint(HIGHLIGHT_SequenceFlow_COLOR); + this.g.setStroke(HIGHLIGHT_FLOW_STROKE); + } + + for (int i = 1; i < xPoints.length; ++i) { + Integer sourceX = xPoints[i - 1]; + Integer sourceY = yPoints[i - 1]; + Integer targetX = xPoints[i]; + Integer targetY = yPoints[i]; + java.awt.geom.Line2D.Double line = new java.awt.geom.Line2D.Double((double) sourceX, + (double) sourceY, + (double) targetX, (double) targetY); + this.g.draw(line); + } + + java.awt.geom.Line2D.Double line; + if (isDefault) { + line = new java.awt.geom.Line2D.Double((double) xPoints[0], (double) yPoints[0], + (double) xPoints[1], + (double) yPoints[1]); + this.drawDefaultSequenceFlowIndicator(line, scaleFactor); + } + + if (conditional) { + line = new java.awt.geom.Line2D.Double((double) xPoints[0], (double) yPoints[0], + (double) xPoints[1], + (double) yPoints[1]); + this.drawConditionalSequenceFlowIndicator(line, scaleFactor); + } + + if (associationDirection.equals(AssociationDirection.ONE) + || associationDirection.equals(AssociationDirection.BOTH)) { + line = new java.awt.geom.Line2D.Double((double) xPoints[xPoints.length - 2], + (double) yPoints[xPoints.length - 2], (double) xPoints[xPoints.length - 1], + (double) yPoints[xPoints.length - 1]); + this.drawArrowHead(line, scaleFactor); + } + + if (associationDirection.equals(AssociationDirection.BOTH)) { + line = new java.awt.geom.Line2D.Double((double) xPoints[1], (double) yPoints[1], + (double) xPoints[0], + (double) yPoints[0]); + this.drawArrowHead(line, scaleFactor); + } + + this.g.setPaint(originalPaint); + this.g.setStroke(originalStroke); + } + + /** + * 设置字体大小图标颜色 + * + * @param imageType + */ + @Override + public void initialize(String imageType) { + if ("png".equalsIgnoreCase(imageType)) { + this.processDiagram = new BufferedImage(this.canvasWidth, this.canvasHeight, 2); + } else { + this.processDiagram = new BufferedImage(this.canvasWidth, this.canvasHeight, 1); + } + + this.g = this.processDiagram.createGraphics(); + if (!"png".equalsIgnoreCase(imageType)) { + this.g.setBackground(new Color(255, 255, 255, 0)); + this.g.clearRect(0, 0, this.canvasWidth, this.canvasHeight); + } + + this.g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + // 修改图标颜色,修改图标字体大小 + this.g.setPaint(Color.black); + Font font = new Font(this.activityFontName, 10, 14); + this.g.setFont(font); + this.fontMetrics = this.g.getFontMetrics(); + // 修改连接线字体大小 + LABEL_FONT = new Font(this.labelFontName, 10, 15); + ANNOTATION_FONT = new Font(this.annotationFontName, 0, 11); + + try { + USERTASK_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/userTask.png", + this.customClassLoader)); + SCRIPTTASK_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/scriptTask.png", + this.customClassLoader)); + SERVICETASK_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/serviceTask.png", + this.customClassLoader)); + RECEIVETASK_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/receiveTask.png", + this.customClassLoader)); + SENDTASK_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/sendTask.png", + this.customClassLoader)); + CASETASK_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/caseTask.png", + this.customClassLoader)); + MANUALTASK_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/manualTask.png", + this.customClassLoader)); + BUSINESS_RULE_TASK_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/businessRuleTask.png", + this.customClassLoader)); + SHELL_TASK_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/shellTask.png", + this.customClassLoader)); + DMN_TASK_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/dmnTask.png", + this.customClassLoader)); + CAMEL_TASK_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/camelTask.png", + this.customClassLoader)); + HTTP_TASK_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/httpTask.png", + this.customClassLoader)); + TIMER_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/timer.png", + this.customClassLoader)); + COMPENSATE_THROW_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/compensate-throw.png", + this.customClassLoader)); + COMPENSATE_CATCH_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/compensate.png", + this.customClassLoader)); + CONDITIONAL_CATCH_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/conditional.png", + this.customClassLoader)); + ERROR_THROW_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/error-throw.png", + this.customClassLoader)); + ERROR_CATCH_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/error.png", + this.customClassLoader)); + ESCALATION_THROW_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/escalation-throw.png", + this.customClassLoader)); + ESCALATION_CATCH_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/escalation.png", + this.customClassLoader)); + MESSAGE_THROW_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/message-throw.png", + this.customClassLoader)); + MESSAGE_CATCH_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/message.png", + this.customClassLoader)); + SIGNAL_THROW_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/signal-throw.png", + this.customClassLoader)); + SIGNAL_CATCH_IMAGE = ImageIO + .read(ReflectUtil.getResource("org/flowable/icons/signal.png", + this.customClassLoader)); + + } catch (IOException var4) { + LOGGER.warn("Could not load image for process diagram creation: {}", var4.getMessage()); + } + + } + + /** + * 设置连接线字体 + * + * @param text + * @param graphicInfo + * @param centered + */ + @Override + public void drawLabel(String text, GraphicInfo graphicInfo, boolean centered) { + float interline = 1.0f; + + // text + if (text != null && text.length() > 0) { + Paint originalPaint = g.getPaint(); + Font originalFont = g.getFont(); + + g.setPaint(LABEL_COLOR); + g.setFont(LABEL_FONT); + + int wrapWidth = 100; + int textY = (int) graphicInfo.getY(); + + AttributedString as = new AttributedString(text); + as.addAttribute(TextAttribute.FOREGROUND, g.getPaint()); + as.addAttribute(TextAttribute.FONT, g.getFont()); + AttributedCharacterIterator aci = as.getIterator(); + FontRenderContext frc = new FontRenderContext(null, true, false); + LineBreakMeasurer lbm = new LineBreakMeasurer(aci, frc); + + while (lbm.getPosition() < text.length()) { + TextLayout tl = lbm.nextLayout(wrapWidth); + textY += tl.getAscent(); + + Rectangle2D bb = tl.getBounds(); + double tX = graphicInfo.getX(); + + if (centered) { + tX += (int) (graphicInfo.getWidth() / 2 - bb.getWidth() / 2); + } + tl.draw(g, (float) tX, textY); + textY += tl.getDescent() + tl.getLeading() + (interline - 1.0f) * tl.getAscent(); + } + + // restore originals + g.setFont(originalFont); + g.setPaint(originalPaint); + } + } + + /** + * 高亮显示task框完成的 + * + * @param x + * @param y + * @param width + * @param height + */ + @Override + public void drawHighLight(int x, int y, int width, int height) { + Paint originalPaint = g.getPaint(); + Stroke originalStroke = g.getStroke(); + + g.setPaint(HIGHLIGHT_COLOR); + g.setStroke(THICK_TASK_BORDER_STROKE); + + RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20); + g.draw(rect); + + g.setPaint(originalPaint); + g.setStroke(originalStroke); + } + + /** + * 自定义task框当前的位置 + * + * @param x + * @param y + * @param width + * @param height + */ + public void drawHighLightNow(int x, int y, int width, int height) { + Paint originalPaint = g.getPaint(); + Stroke originalStroke = g.getStroke(); + + g.setPaint(HIGHLIGHT_COLOR1); + g.setStroke(THICK_TASK_BORDER_STROKE); + + RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20); + g.draw(rect); + + g.setPaint(originalPaint); + g.setStroke(originalStroke); + } + + /** + * 自定义结束节点 + * + * @param x + * @param y + * @param width + * @param height + */ + public void drawHighLightEnd(int x, int y, int width, int height) { + Paint originalPaint = g.getPaint(); + Stroke originalStroke = g.getStroke(); + + g.setPaint(HIGHLIGHT_COLOR); + g.setStroke(THICK_TASK_BORDER_STROKE); + + RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, 20, 20); + g.draw(rect); + + g.setPaint(originalPaint); + g.setStroke(originalStroke); + } + + /** + * task框自定义文字 + * + * @param name + * @param graphicInfo + * @param thickBorder + * @param scaleFactor + */ + @Override + protected void drawTask(String name, GraphicInfo graphicInfo, boolean thickBorder, double scaleFactor) { + + Paint originalPaint = g.getPaint(); + int x = (int) graphicInfo.getX(); + int y = (int) graphicInfo.getY(); + int width = (int) graphicInfo.getWidth(); + int height = (int) graphicInfo.getHeight(); + + // Create a new gradient paint for every task box, gradient depends on x and y + // and is not relative + g.setPaint(TASK_BOX_COLOR); + + int arcR = 6; + if (thickBorder) { + arcR = 3; + } + + // shape + RoundRectangle2D rect = new RoundRectangle2D.Double(x, y, width, height, arcR, arcR); + g.fill(rect); + g.setPaint(TASK_BORDER_COLOR); + + if (thickBorder) { + Stroke originalStroke = g.getStroke(); + g.setStroke(THICK_TASK_BORDER_STROKE); + g.draw(rect); + g.setStroke(originalStroke); + } else { + g.draw(rect); + } + + g.setPaint(originalPaint); + // text + if (scaleFactor == 1.0 && name != null && name.length() > 0) { + int boxWidth = width - (2 * TEXT_PADDING); + int boxHeight = height - 16 - ICON_PADDING - ICON_PADDING - MARKER_WIDTH - 2 - 2; + int boxX = x + width / 2 - boxWidth / 2; + int boxY = y + height / 2 - boxHeight / 2 + ICON_PADDING + ICON_PADDING - 2 - 2; + + drawMultilineCentredText(name, boxX, boxY, boxWidth, boxHeight); + } + } + + protected static Color EVENT_COLOR = new Color(255, 255, 255); + + /** + * 重写开始事件 + * + * @param graphicInfo + * @param image + * @param scaleFactor + */ + @Override + public void drawStartEvent(GraphicInfo graphicInfo, BufferedImage image, double scaleFactor) { + Paint originalPaint = g.getPaint(); + g.setPaint(EVENT_COLOR); + Ellipse2D circle = new Ellipse2D.Double(graphicInfo.getX(), graphicInfo.getY(), + graphicInfo.getWidth(), graphicInfo.getHeight()); + g.fill(circle); + g.setPaint(EVENT_BORDER_COLOR); + g.draw(circle); + g.setPaint(originalPaint); + if (image != null) { + // calculate coordinates to center image + int imageX = (int) Math + .round(graphicInfo.getX() + (graphicInfo.getWidth() / 2) + - (image.getWidth() / (2 * scaleFactor))); + int imageY = (int) Math.round( + graphicInfo.getY() + (graphicInfo.getHeight() / 2) + - (image.getHeight() / (2 * scaleFactor))); + g.drawImage(image, imageX, imageY, + (int) (image.getWidth() / scaleFactor), (int) (image.getHeight() / scaleFactor), + null); + } + + } + + /** + * 重写结束事件 + * + * @param graphicInfo + * @param scaleFactor + */ + @Override + public void drawNoneEndEvent(GraphicInfo graphicInfo, double scaleFactor) { + Paint originalPaint = g.getPaint(); + Stroke originalStroke = g.getStroke(); + g.setPaint(EVENT_COLOR); + Ellipse2D circle = new Ellipse2D.Double(graphicInfo.getX(), graphicInfo.getY(), + graphicInfo.getWidth(), graphicInfo.getHeight()); + g.fill(circle); + g.setPaint(EVENT_BORDER_COLOR); + // g.setPaint(HIGHLIGHT_COLOR); + if (scaleFactor == 1.0) { + g.setStroke(END_EVENT_STROKE); + } else { + g.setStroke(new BasicStroke(2.0f)); + } + g.draw(circle); + g.setStroke(originalStroke); + g.setPaint(originalPaint); + } +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/flow/CustomProcessDiagramGenerator.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/flow/CustomProcessDiagramGenerator.java new file mode 100644 index 0000000..9b299fd --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/flow/CustomProcessDiagramGenerator.java @@ -0,0 +1,431 @@ +package com.boyue.flowable.flow; + +import java.util.Iterator; +import java.util.List; + +import org.flowable.bpmn.model.Activity; +import org.flowable.bpmn.model.Artifact; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.CallActivity; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.FlowElementsContainer; +import org.flowable.bpmn.model.FlowNode; +import org.flowable.bpmn.model.Gateway; +import org.flowable.bpmn.model.GraphicInfo; +import org.flowable.bpmn.model.Lane; +import org.flowable.bpmn.model.MultiInstanceLoopCharacteristics; +import org.flowable.bpmn.model.Pool; +import org.flowable.bpmn.model.Process; +import org.flowable.bpmn.model.SequenceFlow; +import org.flowable.bpmn.model.SubProcess; +import org.flowable.image.impl.DefaultProcessDiagramCanvas; +import org.flowable.image.impl.DefaultProcessDiagramGenerator; + +/** + * @author Tony + * @date 2021/4/5 0:31 + */ +public class CustomProcessDiagramGenerator extends DefaultProcessDiagramGenerator { + @Override + protected DefaultProcessDiagramCanvas generateProcessDiagram(BpmnModel bpmnModel, String imageType, + List highLightedActivities, List highLightedFlows, String activityFontName, + String labelFontName, String annotationFontName, ClassLoader customClassLoader, double scaleFactor, + boolean drawSequenceFlowNameWithNoLabelDI) { + this.prepareBpmnModel(bpmnModel); + DefaultProcessDiagramCanvas processDiagramCanvas = initProcessDiagramCanvas(bpmnModel, imageType, + activityFontName, labelFontName, annotationFontName, customClassLoader); + Iterator var13 = bpmnModel.getPools().iterator(); + + while (var13.hasNext()) { + Pool process = (Pool) var13.next(); + GraphicInfo subProcesses = bpmnModel.getGraphicInfo(process.getId()); + processDiagramCanvas.drawPoolOrLane(process.getName(), subProcesses, scaleFactor); + } + + var13 = bpmnModel.getProcesses().iterator(); + + Process process1; + Iterator subProcesses1; + while (var13.hasNext()) { + process1 = (Process) var13.next(); + subProcesses1 = process1.getLanes().iterator(); + + while (subProcesses1.hasNext()) { + Lane artifact = (Lane) subProcesses1.next(); + GraphicInfo subProcess = bpmnModel.getGraphicInfo(artifact.getId()); + processDiagramCanvas.drawPoolOrLane(artifact.getName(), subProcess, scaleFactor); + } + } + + var13 = bpmnModel.getProcesses().iterator(); + + while (var13.hasNext()) { + process1 = (Process) var13.next(); + subProcesses1 = process1.findFlowElementsOfType(FlowNode.class).iterator(); + + while (subProcesses1.hasNext()) { + FlowNode artifact1 = (FlowNode) subProcesses1.next(); + if (!this.isPartOfCollapsedSubProcess(artifact1, bpmnModel)) { + this.drawActivity(processDiagramCanvas, bpmnModel, artifact1, highLightedActivities, + highLightedFlows, scaleFactor, Boolean.valueOf(drawSequenceFlowNameWithNoLabelDI)); + } + } + } + + var13 = bpmnModel.getProcesses().iterator(); + + label75: while (true) { + List subProcesses2; + do { + if (!var13.hasNext()) { + return processDiagramCanvas; + } + + process1 = (Process) var13.next(); + subProcesses1 = process1.getArtifacts().iterator(); + + while (subProcesses1.hasNext()) { + Artifact artifact2 = (Artifact) subProcesses1.next(); + this.drawArtifact(processDiagramCanvas, bpmnModel, artifact2); + } + + subProcesses2 = process1.findFlowElementsOfType(SubProcess.class, true); + } while (subProcesses2 == null); + + Iterator artifact3 = subProcesses2.iterator(); + + while (true) { + GraphicInfo graphicInfo; + SubProcess subProcess1; + do { + do { + if (!artifact3.hasNext()) { + continue label75; + } + + subProcess1 = (SubProcess) artifact3.next(); + graphicInfo = bpmnModel.getGraphicInfo(subProcess1.getId()); + } while (graphicInfo != null && graphicInfo.getExpanded() != null + && !graphicInfo.getExpanded().booleanValue()); + } while (this.isPartOfCollapsedSubProcess(subProcess1, bpmnModel)); + + Iterator var19 = subProcess1.getArtifacts().iterator(); + + while (var19.hasNext()) { + Artifact subProcessArtifact = (Artifact) var19.next(); + this.drawArtifact(processDiagramCanvas, bpmnModel, subProcessArtifact); + } + } + } + } + + protected static DefaultProcessDiagramCanvas initProcessDiagramCanvas(BpmnModel bpmnModel, String imageType, + String activityFontName, String labelFontName, String annotationFontName, ClassLoader customClassLoader) { + double minX = 1.7976931348623157E308D; + double maxX = 0.0D; + double minY = 1.7976931348623157E308D; + double maxY = 0.0D; + + GraphicInfo nrOfLanes; + for (Iterator flowNodes = bpmnModel.getPools().iterator(); flowNodes + .hasNext(); maxY = nrOfLanes.getY() + nrOfLanes.getHeight()) { + Pool artifacts = (Pool) flowNodes.next(); + nrOfLanes = bpmnModel.getGraphicInfo(artifacts.getId()); + minX = nrOfLanes.getX(); + maxX = nrOfLanes.getX() + nrOfLanes.getWidth(); + minY = nrOfLanes.getY(); + } + + List var23 = gatherAllFlowNodes(bpmnModel); + Iterator var24 = var23.iterator(); + + label155: while (var24.hasNext()) { + FlowNode var26 = (FlowNode) var24.next(); + GraphicInfo artifact = bpmnModel.getGraphicInfo(var26.getId()); + if (artifact.getX() + artifact.getWidth() > maxX) { + maxX = artifact.getX() + artifact.getWidth(); + } + + if (artifact.getX() < minX) { + minX = artifact.getX(); + } + + if (artifact.getY() + artifact.getHeight() > maxY) { + maxY = artifact.getY() + artifact.getHeight(); + } + + if (artifact.getY() < minY) { + minY = artifact.getY(); + } + + Iterator process = var26.getOutgoingFlows().iterator(); + + while (true) { + List l; + do { + if (!process.hasNext()) { + continue label155; + } + + SequenceFlow graphicInfoList = (SequenceFlow) process.next(); + l = bpmnModel.getFlowLocationGraphicInfo(graphicInfoList.getId()); + } while (l == null); + + Iterator graphicInfo = l.iterator(); + + while (graphicInfo.hasNext()) { + GraphicInfo graphicInfo1 = (GraphicInfo) graphicInfo.next(); + if (graphicInfo1.getX() > maxX) { + maxX = graphicInfo1.getX(); + } + + if (graphicInfo1.getX() < minX) { + minX = graphicInfo1.getX(); + } + + if (graphicInfo1.getY() > maxY) { + maxY = graphicInfo1.getY(); + } + + if (graphicInfo1.getY() < minY) { + minY = graphicInfo1.getY(); + } + } + } + } + + List var25 = gatherAllArtifacts(bpmnModel); + Iterator var27 = var25.iterator(); + + GraphicInfo var37; + while (var27.hasNext()) { + Artifact var29 = (Artifact) var27.next(); + GraphicInfo var31 = bpmnModel.getGraphicInfo(var29.getId()); + if (var31 != null) { + if (var31.getX() + var31.getWidth() > maxX) { + maxX = var31.getX() + var31.getWidth(); + } + + if (var31.getX() < minX) { + minX = var31.getX(); + } + + if (var31.getY() + var31.getHeight() > maxY) { + maxY = var31.getY() + var31.getHeight(); + } + + if (var31.getY() < minY) { + minY = var31.getY(); + } + } + + List var33 = bpmnModel.getFlowLocationGraphicInfo(var29.getId()); + if (var33 != null) { + Iterator var35 = var33.iterator(); + + while (var35.hasNext()) { + var37 = (GraphicInfo) var35.next(); + if (var37.getX() > maxX) { + maxX = var37.getX(); + } + + if (var37.getX() < minX) { + minX = var37.getX(); + } + + if (var37.getY() > maxY) { + maxY = var37.getY(); + } + + if (var37.getY() < minY) { + minY = var37.getY(); + } + } + } + } + + int var28 = 0; + Iterator var30 = bpmnModel.getProcesses().iterator(); + + while (var30.hasNext()) { + Process var32 = (Process) var30.next(); + Iterator var34 = var32.getLanes().iterator(); + + while (var34.hasNext()) { + Lane var36 = (Lane) var34.next(); + ++var28; + var37 = bpmnModel.getGraphicInfo(var36.getId()); + if (var37.getX() + var37.getWidth() > maxX) { + maxX = var37.getX() + var37.getWidth(); + } + + if (var37.getX() < minX) { + minX = var37.getX(); + } + + if (var37.getY() + var37.getHeight() > maxY) { + maxY = var37.getY() + var37.getHeight(); + } + + if (var37.getY() < minY) { + minY = var37.getY(); + } + } + } + + if (var23.isEmpty() && bpmnModel.getPools().isEmpty() && var28 == 0) { + minX = 0.0D; + minY = 0.0D; + } + + return new CustomProcessDiagramCanvas((int) maxX + 10, (int) maxY + 10, (int) minX, (int) minY, imageType, + activityFontName, labelFontName, annotationFontName, customClassLoader); + } + + private static void drawHighLight(DefaultProcessDiagramCanvas processDiagramCanvas, GraphicInfo graphicInfo) { + processDiagramCanvas.drawHighLight((int) graphicInfo.getX(), (int) graphicInfo.getY(), + (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight()); + + } + + private static void drawHighLightNow(CustomProcessDiagramCanvas processDiagramCanvas, GraphicInfo graphicInfo) { + processDiagramCanvas.drawHighLightNow((int) graphicInfo.getX(), (int) graphicInfo.getY(), + (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight()); + + } + + private static void drawHighLightEnd(CustomProcessDiagramCanvas processDiagramCanvas, GraphicInfo graphicInfo) { + processDiagramCanvas.drawHighLightEnd((int) graphicInfo.getX(), (int) graphicInfo.getY(), + (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight()); + + } + + @Override + protected void drawActivity(DefaultProcessDiagramCanvas processDiagramCanvas, BpmnModel bpmnModel, + FlowNode flowNode, java.util.List highLightedActivities, java.util.List highLightedFlows, + double scaleFactor, Boolean drawSequenceFlowNameWithNoLabelDI) { + + DefaultProcessDiagramGenerator.ActivityDrawInstruction drawInstruction = activityDrawInstructions + .get(flowNode.getClass()); + if (drawInstruction != null) { + + drawInstruction.draw(processDiagramCanvas, bpmnModel, flowNode); + + // Gather info on the multi instance marker + boolean multiInstanceSequential = false; + boolean multiInstanceParallel = false; + boolean collapsed = false; + if (flowNode instanceof Activity) { + Activity activity = (Activity) flowNode; + MultiInstanceLoopCharacteristics multiInstanceLoopCharacteristics = activity.getLoopCharacteristics(); + if (multiInstanceLoopCharacteristics != null) { + multiInstanceSequential = multiInstanceLoopCharacteristics.isSequential(); + multiInstanceParallel = !multiInstanceSequential; + } + } + + // Gather info on the collapsed marker + GraphicInfo graphicInfo = bpmnModel.getGraphicInfo(flowNode.getId()); + if (flowNode instanceof SubProcess) { + collapsed = graphicInfo.getExpanded() != null && !graphicInfo.getExpanded(); + } else if (flowNode instanceof CallActivity) { + collapsed = true; + } + + if (scaleFactor == 1.0) { + // Actually draw the markers + processDiagramCanvas.drawActivityMarkers((int) graphicInfo.getX(), (int) graphicInfo.getY(), + (int) graphicInfo.getWidth(), (int) graphicInfo.getHeight(), + multiInstanceSequential, multiInstanceParallel, collapsed); + } + + // Draw highlighted activities + if (highLightedActivities.contains(flowNode.getId())) { + + if (highLightedActivities.get(highLightedActivities.size() - 1).equals(flowNode.getId()) + && !"endenv".equals(flowNode.getId())) { + if ((flowNode.getId().contains("Event_"))) { + drawHighLightEnd((CustomProcessDiagramCanvas) processDiagramCanvas, + bpmnModel.getGraphicInfo(flowNode.getId())); + } else { + drawHighLightNow((CustomProcessDiagramCanvas) processDiagramCanvas, + bpmnModel.getGraphicInfo(flowNode.getId())); + } + } else { + drawHighLight(processDiagramCanvas, bpmnModel.getGraphicInfo(flowNode.getId())); + } + + } + + } + + // Outgoing transitions of activity + for (SequenceFlow sequenceFlow : flowNode.getOutgoingFlows()) { + boolean highLighted = (highLightedFlows.contains(sequenceFlow.getId())); + String defaultFlow = null; + if (flowNode instanceof Activity) { + defaultFlow = ((Activity) flowNode).getDefaultFlow(); + } else if (flowNode instanceof Gateway) { + defaultFlow = ((Gateway) flowNode).getDefaultFlow(); + } + + boolean isDefault = false; + if (defaultFlow != null && defaultFlow.equalsIgnoreCase(sequenceFlow.getId())) { + isDefault = true; + } + boolean drawConditionalIndicator = sequenceFlow.getConditionExpression() != null + && !(flowNode instanceof Gateway); + + String sourceRef = sequenceFlow.getSourceRef(); + String targetRef = sequenceFlow.getTargetRef(); + FlowElement sourceElement = bpmnModel.getFlowElement(sourceRef); + FlowElement targetElement = bpmnModel.getFlowElement(targetRef); + java.util.List graphicInfoList = bpmnModel.getFlowLocationGraphicInfo(sequenceFlow.getId()); + if (graphicInfoList != null && graphicInfoList.size() > 0) { + graphicInfoList = connectionPerfectionizer(processDiagramCanvas, bpmnModel, sourceElement, + targetElement, graphicInfoList); + int xPoints[] = new int[graphicInfoList.size()]; + int yPoints[] = new int[graphicInfoList.size()]; + + for (int i = 1; i < graphicInfoList.size(); i++) { + GraphicInfo graphicInfo = graphicInfoList.get(i); + GraphicInfo previousGraphicInfo = graphicInfoList.get(i - 1); + + if (i == 1) { + xPoints[0] = (int) previousGraphicInfo.getX(); + yPoints[0] = (int) previousGraphicInfo.getY(); + } + xPoints[i] = (int) graphicInfo.getX(); + yPoints[i] = (int) graphicInfo.getY(); + + } + + processDiagramCanvas.drawSequenceflow(xPoints, yPoints, drawConditionalIndicator, isDefault, + highLighted, scaleFactor); + + // Draw sequenceflow label + GraphicInfo labelGraphicInfo = bpmnModel.getLabelGraphicInfo(sequenceFlow.getId()); + if (labelGraphicInfo != null) { + processDiagramCanvas.drawLabel(sequenceFlow.getName(), labelGraphicInfo, false); + } else { + if (drawSequenceFlowNameWithNoLabelDI) { + GraphicInfo lineCenter = getLineCenter(graphicInfoList); + processDiagramCanvas.drawLabel(sequenceFlow.getName(), lineCenter, false); + } + + } + } + } + + // Nested elements + if (flowNode instanceof FlowElementsContainer) { + for (FlowElement nestedFlowElement : ((FlowElementsContainer) flowNode).getFlowElements()) { + if (nestedFlowElement instanceof FlowNode + && !isPartOfCollapsedSubProcess(nestedFlowElement, bpmnModel)) { + drawActivity(processDiagramCanvas, bpmnModel, (FlowNode) nestedFlowElement, + highLightedActivities, highLightedFlows, scaleFactor, drawSequenceFlowNameWithNoLabelDI); + } + } + } + } +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/flow/FindNextNodeUtil.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/flow/FindNextNodeUtil.java new file mode 100644 index 0000000..4573b71 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/flow/FindNextNodeUtil.java @@ -0,0 +1,286 @@ +package com.boyue.flowable.flow; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.CallActivity; +import org.flowable.bpmn.model.EndEvent; +import org.flowable.bpmn.model.ExclusiveGateway; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.Gateway; +import org.flowable.bpmn.model.ParallelGateway; +//import com.greenpineyu.fel.FelEngine; +//import com.greenpineyu.fel.FelEngineImpl; +//import com.greenpineyu.fel.context.FelContext; +//import org.apache.commons.jexl2.JexlContext; +//import org.apache.commons.jexl2.JexlEngine; +//import org.apache.commons.jexl2.MapContext; +//import org.apache.commons.lang3.StringUtils; +import org.flowable.bpmn.model.Process; +import org.flowable.bpmn.model.ReceiveTask; +import org.flowable.bpmn.model.SequenceFlow; +import org.flowable.bpmn.model.ServiceTask; +import org.flowable.bpmn.model.StartEvent; +import org.flowable.bpmn.model.SubProcess; +import org.flowable.bpmn.model.Task; +import org.flowable.bpmn.model.UserTask; +import org.flowable.engine.RepositoryService; +import org.flowable.engine.repository.ProcessDefinition; + +import com.googlecode.aviator.AviatorEvaluator; +import com.googlecode.aviator.Expression; + +/** + * @author Tony + * @date 2021/4/19 20:51 + */ +public class FindNextNodeUtil { + + /** + * 获取下一步骤的用户任务 + * + * @param repositoryService + * @param map + * @return + */ + public static List getNextUserTasks(RepositoryService repositoryService, org.flowable.task.api.Task task, + Map map) { + List data = new ArrayList<>(); + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() + .processDefinitionId(task.getProcessDefinitionId()).singleResult(); + BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId()); + Process mainProcess = bpmnModel.getMainProcess(); + Collection flowElements = mainProcess.getFlowElements(); + String key = task.getTaskDefinitionKey(); + FlowElement flowElement = bpmnModel.getFlowElement(key); + next(flowElements, flowElement, map, data); + return data; + } + + /** + * 启动流程时获取下一步骤的用户任务 + * + * @param repositoryService + * @param map + * @return + */ + public static List getNextUserTasksByStart(RepositoryService repositoryService, + ProcessDefinition processDefinition, Map map) { + List data = new ArrayList<>(); + BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId()); + Process mainProcess = bpmnModel.getMainProcess(); + Collection flowElements = mainProcess.getFlowElements(); + String key = null; + // 找到开始节点 并获取唯一key + for (FlowElement flowElement : flowElements) { + if (flowElement instanceof StartEvent) { + key = flowElement.getId(); + break; + } + } + FlowElement flowElement = bpmnModel.getFlowElement(key); + List sequenceFlows = ((StartEvent) flowElement).getOutgoingFlows(); + // 获取出口连线, 此时从开始节点往后,只能是一个出口 + if (!sequenceFlows.isEmpty()) { + SequenceFlow sequenceFlow = sequenceFlows.get(0); + FlowElement targetFlowElement = sequenceFlow.getTargetFlowElement(); + next(flowElements, targetFlowElement, map, data); + } + return data; + } + + /** + * 查找下一节点 + * + * @param flowElements + * @param flowElement + * @param map + * @param nextUser + */ + public static void next(Collection flowElements, FlowElement flowElement, Map map, + List nextUser) { + // 如果是结束节点 + if (flowElement instanceof EndEvent) { + // 如果是子任务的结束节点 + if (getSubProcess(flowElements, flowElement) != null) { + flowElement = getSubProcess(flowElements, flowElement); + } + } + // 获取Task的出线信息--可以拥有多个 + List outGoingFlows = null; + if (flowElement instanceof Task) { + outGoingFlows = ((Task) flowElement).getOutgoingFlows(); + } else if (flowElement instanceof Gateway) { + outGoingFlows = ((Gateway) flowElement).getOutgoingFlows(); + } else if (flowElement instanceof StartEvent) { + outGoingFlows = ((StartEvent) flowElement).getOutgoingFlows(); + } else if (flowElement instanceof SubProcess) { + outGoingFlows = ((SubProcess) flowElement).getOutgoingFlows(); + } else if (flowElement instanceof CallActivity) { + outGoingFlows = ((CallActivity) flowElement).getOutgoingFlows(); + } + if (outGoingFlows != null && outGoingFlows.size() > 0) { + // 遍历所有的出线--找到可以正确执行的那一条 + for (SequenceFlow sequenceFlow : outGoingFlows) { + // 1.有表达式,且为true + // 2.无表达式 + String expression = sequenceFlow.getConditionExpression(); + if (expression == null || + expressionResult(map, + expression.substring(expression.lastIndexOf("{") + 1, expression.lastIndexOf("}")))) { + // 出线的下一节点 + String nextFlowElementID = sequenceFlow.getTargetRef(); + if (checkSubProcess(nextFlowElementID, flowElements, nextUser)) { + continue; + } + + // 查询下一节点的信息 + FlowElement nextFlowElement = getFlowElementById(nextFlowElementID, flowElements); + // 调用流程 + if (nextFlowElement instanceof CallActivity) { + CallActivity ca = (CallActivity) nextFlowElement; + if (ca.getLoopCharacteristics() != null) { + UserTask userTask = new UserTask(); + userTask.setId(ca.getId()); + + userTask.setId(ca.getId()); + userTask.setLoopCharacteristics(ca.getLoopCharacteristics()); + userTask.setName(ca.getName()); + nextUser.add(userTask); + } + next(flowElements, nextFlowElement, map, nextUser); + } + // 用户任务 + if (nextFlowElement instanceof UserTask) { + nextUser.add((UserTask) nextFlowElement); + } + // 排他网关 + else if (nextFlowElement instanceof ExclusiveGateway) { + next(flowElements, nextFlowElement, map, nextUser); + } + // 并行网关 + else if (nextFlowElement instanceof ParallelGateway) { + next(flowElements, nextFlowElement, map, nextUser); + } + // 接收任务 + else if (nextFlowElement instanceof ReceiveTask) { + next(flowElements, nextFlowElement, map, nextUser); + } + // 服务任务 + else if (nextFlowElement instanceof ServiceTask) { + next(flowElements, nextFlowElement, map, nextUser); + } + // 子任务的起点 + else if (nextFlowElement instanceof StartEvent) { + next(flowElements, nextFlowElement, map, nextUser); + } + // 结束节点 + else if (nextFlowElement instanceof EndEvent) { + next(flowElements, nextFlowElement, map, nextUser); + } + } + } + } + } + + /** + * 判断是否是多实例子流程并且需要设置集合类型变量 + */ + public static boolean checkSubProcess(String id, Collection flowElements, List nextUser) { + for (FlowElement flowElement1 : flowElements) { + if (flowElement1 instanceof SubProcess && flowElement1.getId().equals(id)) { + + SubProcess sp = (SubProcess) flowElement1; + if (sp.getLoopCharacteristics() != null) { + // String inputDataItem = sp.getLoopCharacteristics().getInputDataItem(); + UserTask userTask = new UserTask(); + userTask.setId(sp.getId()); + userTask.setLoopCharacteristics(sp.getLoopCharacteristics()); + userTask.setName(sp.getName()); + nextUser.add(userTask); + return true; + } + } + } + + return false; + + } + + /** + * 查询一个节点的是否子任务中的节点,如果是,返回子任务 + * + * @param flowElements 全流程的节点集合 + * @param flowElement 当前节点 + * @return + */ + public static FlowElement getSubProcess(Collection flowElements, FlowElement flowElement) { + for (FlowElement flowElement1 : flowElements) { + if (flowElement1 instanceof SubProcess) { + for (FlowElement flowElement2 : ((SubProcess) flowElement1).getFlowElements()) { + if (flowElement.equals(flowElement2)) { + return flowElement1; + } + } + } + } + return null; + } + + /** + * 根据ID查询流程节点对象, 如果是子任务,则返回子任务的开始节点 + * + * @param Id 节点ID + * @param flowElements 流程节点集合 + * @return + */ + public static FlowElement getFlowElementById(String Id, Collection flowElements) { + for (FlowElement flowElement : flowElements) { + if (flowElement.getId().equals(Id)) { + // 如果是子任务,则查询出子任务的开始节点 + if (flowElement instanceof SubProcess) { + return getStartFlowElement(((SubProcess) flowElement).getFlowElements()); + } + return flowElement; + } + if (flowElement instanceof SubProcess) { + FlowElement flowElement1 = getFlowElementById(Id, ((SubProcess) flowElement).getFlowElements()); + if (flowElement1 != null) { + return flowElement1; + } + } + } + return null; + } + + /** + * 返回流程的开始节点 + * + * @param flowElements 节点集合 + * @description: + */ + public static FlowElement getStartFlowElement(Collection flowElements) { + for (FlowElement flowElement : flowElements) { + if (flowElement instanceof StartEvent) { + return flowElement; + } + } + return null; + } + + /** + * 校验el表达式 + * + * @param map + * @param expression + * @return + */ + public static boolean expressionResult(Map map, String expression) { + Expression exp = AviatorEvaluator.compile(expression); + return (Boolean) exp.execute(map); + // return true; + } +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/flow/FlowableUtils.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/flow/FlowableUtils.java new file mode 100644 index 0000000..2dffa43 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/flow/FlowableUtils.java @@ -0,0 +1,772 @@ +package com.boyue.flowable.flow; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.Stack; +import java.util.stream.Collectors; + +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.EndEvent; +import org.flowable.bpmn.model.ExtensionAttribute; +import org.flowable.bpmn.model.ExtensionElement; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.FlowNode; +import org.flowable.bpmn.model.Gateway; +import org.flowable.bpmn.model.SequenceFlow; +import org.flowable.bpmn.model.StartEvent; +import org.flowable.bpmn.model.SubProcess; +import org.flowable.bpmn.model.UserTask; +import org.flowable.engine.RepositoryService; +import org.flowable.engine.impl.bpmn.behavior.ParallelMultiInstanceBehavior; +import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior; +import org.flowable.engine.repository.ProcessDefinition; +import org.flowable.task.api.history.HistoricTaskInstance; + +import lombok.extern.slf4j.Slf4j; + +/** + * @author Tony + * @date 2021-04-03 23:57 + */ +@Slf4j +public class FlowableUtils { + + /** + * 根据节点,获取入口连线 + * + * @param source + * @return + */ + public static List getElementIncomingFlows(FlowElement source) { + List sequenceFlows = null; + if (source instanceof FlowNode) { + sequenceFlows = ((FlowNode) source).getIncomingFlows(); + } else if (source instanceof Gateway) { + sequenceFlows = ((Gateway) source).getIncomingFlows(); + } else if (source instanceof SubProcess) { + sequenceFlows = ((SubProcess) source).getIncomingFlows(); + } else if (source instanceof StartEvent) { + sequenceFlows = ((StartEvent) source).getIncomingFlows(); + } else if (source instanceof EndEvent) { + sequenceFlows = ((EndEvent) source).getIncomingFlows(); + } + return sequenceFlows; + } + + /** + * 根据节点,获取出口连线 + * + * @param source + * @return + */ + public static List getElementOutgoingFlows(FlowElement source) { + List sequenceFlows = null; + if (source instanceof FlowNode) { + sequenceFlows = ((FlowNode) source).getOutgoingFlows(); + } else if (source instanceof Gateway) { + sequenceFlows = ((Gateway) source).getOutgoingFlows(); + } else if (source instanceof SubProcess) { + sequenceFlows = ((SubProcess) source).getOutgoingFlows(); + } else if (source instanceof StartEvent) { + sequenceFlows = ((StartEvent) source).getOutgoingFlows(); + } else if (source instanceof EndEvent) { + sequenceFlows = ((EndEvent) source).getOutgoingFlows(); + } + return sequenceFlows; + } + + /** + * 获取全部节点列表,包含子流程节点 + * + * @param flowElements + * @param allElements + * @return + */ + public static Collection getAllElements(Collection flowElements, + Collection allElements) { + allElements = allElements == null ? new ArrayList<>() : allElements; + + for (FlowElement flowElement : flowElements) { + allElements.add(flowElement); + if (flowElement instanceof SubProcess) { + // 继续深入子流程,进一步获取子流程 + allElements = FlowableUtils.getAllElements(((SubProcess) flowElement).getFlowElements(), allElements); + } + } + return allElements; + } + + /** + * 迭代获取父级任务节点列表,向前找 + * + * @param source 起始节点 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param userTaskList 已找到的用户任务节点 + * @return + */ + public static List iteratorFindParentUserTasks(FlowElement source, Set hasSequenceFlow, + List userTaskList) { + userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 + if (source instanceof StartEvent && source.getSubProcess() != null) { + userTaskList = iteratorFindParentUserTasks(source.getSubProcess(), hasSequenceFlow, userTaskList); + } + + // 根据类型,获取入口连线 + List sequenceFlows = getElementIncomingFlows(source); + + if (sequenceFlows != null) { + // 循环找到目标元素 + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 类型为用户节点,则新增父级节点 + if (sequenceFlow.getSourceFlowElement() instanceof UserTask) { + userTaskList.add((UserTask) sequenceFlow.getSourceFlowElement()); + continue; + } + // 类型为子流程,则添加子流程开始节点出口处相连的节点 + if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) { + // 获取子流程用户任务节点 + List childUserTaskList = findChildProcessUserTasks( + (StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements() + .toArray()[0], + null, null); + // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 + if (childUserTaskList != null && childUserTaskList.size() > 0) { + userTaskList.addAll(childUserTaskList); + continue; + } + } + // 继续迭代 + userTaskList = iteratorFindParentUserTasks(sequenceFlow.getSourceFlowElement(), hasSequenceFlow, + userTaskList); + } + } + return userTaskList; + } + + /** + * 根据正在运行的任务节点,迭代获取子级任务节点列表,向后找 + * + * @param source 起始节点(退回节点) + * @param runTaskKeyList 正在运行的任务 Key,用于校验任务节点是否是正在运行的节点 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param userTaskList 需要撤回的用户任务列表 + * @return + */ + public static List iteratorFindChildUserTasks(FlowElement source, List runTaskKeyList, + Set hasSequenceFlow, List userTaskList) { + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; + + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 + if (source instanceof EndEvent && source.getSubProcess() != null) { + userTaskList = iteratorFindChildUserTasks(source.getSubProcess(), runTaskKeyList, hasSequenceFlow, + userTaskList); + } + + // 根据类型,获取出口连线 + List sequenceFlows = getElementOutgoingFlows(source); + + if (sequenceFlows != null) { + // 循环找到目标元素 + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加 + if (sequenceFlow.getTargetFlowElement() instanceof UserTask + && runTaskKeyList.contains((sequenceFlow.getTargetFlowElement()).getId())) { + userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement()); + continue; + } + // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取 + if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) { + List childUserTaskList = iteratorFindChildUserTasks( + (FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements() + .toArray()[0]), + runTaskKeyList, hasSequenceFlow, null); + // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 + if (childUserTaskList != null && childUserTaskList.size() > 0) { + userTaskList.addAll(childUserTaskList); + continue; + } + } + // 继续迭代 + userTaskList = iteratorFindChildUserTasks(sequenceFlow.getTargetFlowElement(), runTaskKeyList, + hasSequenceFlow, userTaskList); + } + } + return userTaskList; + } + + /** + * 迭代获取子流程用户任务节点 + * + * @param source 起始节点 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param userTaskList 需要撤回的用户任务列表 + * @return + */ + public static List findChildProcessUserTasks(FlowElement source, Set hasSequenceFlow, + List userTaskList) { + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; + + // 根据类型,获取出口连线 + List sequenceFlows = getElementOutgoingFlows(source); + + if (sequenceFlows != null) { + // 循环找到目标元素 + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加 + if (sequenceFlow.getTargetFlowElement() instanceof UserTask) { + userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement()); + continue; + } + // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取 + if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) { + List childUserTaskList = findChildProcessUserTasks( + (FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements() + .toArray()[0]), + hasSequenceFlow, null); + // 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 + if (childUserTaskList != null && childUserTaskList.size() > 0) { + userTaskList.addAll(childUserTaskList); + continue; + } + } + // 继续迭代 + userTaskList = findChildProcessUserTasks(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, + userTaskList); + } + } + return userTaskList; + } + + /** + * 从后向前寻路,获取所有脏线路上的点 + * + * @param source 起始节点 + * @param passRoads 已经经过的点集合 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param targets 目标脏线路终点 + * @param dirtyRoads 确定为脏数据的点,因为不需要重复,因此使用 set 存储 + * @return + */ + public static Set iteratorFindDirtyRoads(FlowElement source, List passRoads, + Set hasSequenceFlow, List targets, Set dirtyRoads) { + passRoads = passRoads == null ? new ArrayList<>() : passRoads; + dirtyRoads = dirtyRoads == null ? new HashSet<>() : dirtyRoads; + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 + if (source instanceof StartEvent && source.getSubProcess() != null) { + dirtyRoads = iteratorFindDirtyRoads(source.getSubProcess(), passRoads, hasSequenceFlow, targets, + dirtyRoads); + } + + // 根据类型,获取入口连线 + List sequenceFlows = getElementIncomingFlows(source); + + if (sequenceFlows != null) { + // 循环找到目标元素 + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 新增经过的路线 + passRoads.add(sequenceFlow.getSourceFlowElement().getId()); + // 如果此点为目标点,确定经过的路线为脏线路,添加点到脏线路中,然后找下个连线 + if (targets.contains(sequenceFlow.getSourceFlowElement().getId())) { + dirtyRoads.addAll(passRoads); + continue; + } + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 + if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) { + dirtyRoads = findChildProcessAllDirtyRoad( + (StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements() + .toArray()[0], + null, dirtyRoads); + // 是否存在子流程上,true 是,false 否 + Boolean isInChildProcess = dirtyTargetInChildProcess( + (StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements() + .toArray()[0], + null, targets, null); + if (isInChildProcess) { + // 已在子流程上找到,该路线结束 + continue; + } + } + // 继续迭代 + dirtyRoads = iteratorFindDirtyRoads(sequenceFlow.getSourceFlowElement(), passRoads, hasSequenceFlow, + targets, dirtyRoads); + } + } + return dirtyRoads; + } + + /** + * 迭代获取子流程脏路线 + * 说明,假如回退的点就是子流程,那么也肯定会回退到子流程最初的用户任务节点,因此子流程中的节点全是脏路线 + * + * @param source 起始节点 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param dirtyRoads 确定为脏数据的点,因为不需要重复,因此使用 set 存储 + * @return + */ + public static Set findChildProcessAllDirtyRoad(FlowElement source, Set hasSequenceFlow, + Set dirtyRoads) { + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + dirtyRoads = dirtyRoads == null ? new HashSet<>() : dirtyRoads; + + // 根据类型,获取出口连线 + List sequenceFlows = getElementOutgoingFlows(source); + + if (sequenceFlows != null) { + // 循环找到目标元素 + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 添加脏路线 + dirtyRoads.add(sequenceFlow.getTargetFlowElement().getId()); + // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取 + if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) { + dirtyRoads = findChildProcessAllDirtyRoad( + (FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements() + .toArray()[0]), + hasSequenceFlow, dirtyRoads); + } + // 继续迭代 + dirtyRoads = findChildProcessAllDirtyRoad(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, + dirtyRoads); + } + } + return dirtyRoads; + } + + /** + * 判断脏路线结束节点是否在子流程上 + * + * @param source 起始节点 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param targets 判断脏路线节点是否存在子流程上,只要存在一个,说明脏路线只到子流程为止 + * @param inChildProcess 是否存在子流程上,true 是,false 否 + * @return + */ + public static Boolean dirtyTargetInChildProcess(FlowElement source, Set hasSequenceFlow, + List targets, Boolean inChildProcess) { + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + inChildProcess = inChildProcess != null && inChildProcess; + + // 根据类型,获取出口连线 + List sequenceFlows = getElementOutgoingFlows(source); + + if (sequenceFlows != null && !inChildProcess) { + // 循环找到目标元素 + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 如果发现目标点在子流程上存在,说明只到子流程为止 + if (targets.contains(sequenceFlow.getTargetFlowElement().getId())) { + inChildProcess = true; + break; + } + // 如果节点为子流程节点情况,则从节点中的第一个节点开始获取 + if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) { + inChildProcess = dirtyTargetInChildProcess( + (FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements() + .toArray()[0]), + hasSequenceFlow, targets, inChildProcess); + } + // 继续迭代 + inChildProcess = dirtyTargetInChildProcess(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, + targets, inChildProcess); + } + } + return inChildProcess; + } + + /** + * 迭代从后向前扫描,判断目标节点相对于当前节点是否是串行 + * 不存在直接回退到子流程中的情况,但存在从子流程出去到父流程情况 + * + * @param source 起始节点 + * @param isSequential 是否串行 + * @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 + * @param targetKsy 目标节点 + * @return + */ + public static Boolean iteratorCheckSequentialReferTarget(FlowElement source, String targetKsy, + Set hasSequenceFlow, Boolean isSequential) { + isSequential = isSequential == null || isSequential; + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 + if (source instanceof StartEvent && source.getSubProcess() != null) { + isSequential = iteratorCheckSequentialReferTarget(source.getSubProcess(), targetKsy, hasSequenceFlow, + isSequential); + } + + // 根据类型,获取入口连线 + List sequenceFlows = getElementIncomingFlows(source); + + if (sequenceFlows != null) { + // 循环找到目标元素 + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 如果目标节点已被判断为并行,后面都不需要执行,直接返回 + if (!isSequential) { + break; + } + // 这条线路存在目标节点,这条线路完成,进入下个线路 + if (targetKsy.equals(sequenceFlow.getSourceFlowElement().getId())) { + continue; + } + if (sequenceFlow.getSourceFlowElement() instanceof StartEvent) { + isSequential = false; + break; + } + // 否则就继续迭代 + isSequential = iteratorCheckSequentialReferTarget(sequenceFlow.getSourceFlowElement(), targetKsy, + hasSequenceFlow, isSequential); + } + } + return isSequential; + } + + /** + * 从后向前寻路,获取到达节点的所有路线 + * 不存在直接回退到子流程,但是存在回退到父级流程的情况 + * + * @param source 起始节点 + * @param passRoads 已经经过的点集合 + * @param roads 路线 + * @return + */ + public static List> findRoad(FlowElement source, List passRoads, + Set hasSequenceFlow, List> roads) { + passRoads = passRoads == null ? new ArrayList<>() : passRoads; + roads = roads == null ? new ArrayList<>() : roads; + hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; + + // 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 + if (source instanceof StartEvent && source.getSubProcess() != null) { + roads = findRoad(source.getSubProcess(), passRoads, hasSequenceFlow, roads); + } + + // 根据类型,获取入口连线 + List sequenceFlows = getElementIncomingFlows(source); + + if (sequenceFlows != null && sequenceFlows.size() != 0) { + for (SequenceFlow sequenceFlow : sequenceFlows) { + // 如果发现连线重复,说明循环了,跳过这个循环 + if (hasSequenceFlow.contains(sequenceFlow.getId())) { + continue; + } + // 添加已经走过的连线 + hasSequenceFlow.add(sequenceFlow.getId()); + // 添加经过路线 + if (sequenceFlow.getSourceFlowElement() instanceof UserTask) { + passRoads.add((UserTask) sequenceFlow.getSourceFlowElement()); + } + // 继续迭代 + roads = findRoad(sequenceFlow.getSourceFlowElement(), passRoads, hasSequenceFlow, roads); + } + } else { + // 添加路线 + roads.add(passRoads); + } + return roads; + } + + /** + * 历史节点数据清洗,清洗掉又回滚导致的脏数据 + * + * @param allElements 全部节点信息 + * @param historicTaskInstanceList 历史任务实例信息,数据采用开始时间升序 + * @return + */ + public static List historicTaskInstanceClean(Collection allElements, + List historicTaskInstanceList) { + // 会签节点收集 + List multiTask = new ArrayList<>(); + allElements.forEach(flowElement -> { + if (flowElement instanceof UserTask) { + // 如果该节点的行为为会签行为,说明该节点为会签节点 + if (((UserTask) flowElement).getBehavior() instanceof ParallelMultiInstanceBehavior + || ((UserTask) flowElement).getBehavior() instanceof SequentialMultiInstanceBehavior) { + multiTask.add(flowElement.getId()); + } + } + }); + // 循环放入栈,栈 LIFO:后进先出 + Stack stack = new Stack<>(); + historicTaskInstanceList.forEach(stack::push); + // 清洗后的历史任务实例 + List lastHistoricTaskInstanceList = new ArrayList<>(); + // 网关存在可能只走了部分分支情况,且还存在跳转废弃数据以及其他分支数据的干扰,因此需要对历史节点数据进行清洗 + // 临时用户任务 key + StringBuilder userTaskKey = null; + // 临时被删掉的任务 key,存在并行情况 + List deleteKeyList = new ArrayList<>(); + // 临时脏数据线路 + List> dirtyDataLineList = new ArrayList<>(); + // 由某个点跳到会签点,此时出现多个会签实例对应 1 个跳转情况,需要把这些连续脏数据都找到 + // 会签特殊处理下标 + int multiIndex = -1; + // 会签特殊处理 key + StringBuilder multiKey = null; + // 会签特殊处理操作标识 + boolean multiOpera = false; + while (!stack.empty()) { + // 从这里开始 userTaskKey 都还是上个栈的 key + // 是否是脏数据线路上的点 + final boolean[] isDirtyData = { false }; + for (Set oldDirtyDataLine : dirtyDataLineList) { + if (oldDirtyDataLine.contains(stack.peek().getTaskDefinitionKey())) { + isDirtyData[0] = true; + } + } + // 删除原因不为空,说明从这条数据开始回跳或者回退的 + // MI_END:会签完成后,其他未签到节点的删除原因,不在处理范围内 + if (stack.peek().getDeleteReason() != null && !"MI_END".equals(stack.peek().getDeleteReason())) { + // 可以理解为脏线路起点 + String dirtyPoint = ""; + if (stack.peek().getDeleteReason().contains("Change activity to ")) { + dirtyPoint = stack.peek().getDeleteReason().replace("Change activity to ", ""); + } + // 会签回退删除原因有点不同 + if (stack.peek().getDeleteReason().contains("Change parent activity to ")) { + dirtyPoint = stack.peek().getDeleteReason().replace("Change parent activity to ", ""); + } + FlowElement dirtyTask = null; + // 获取变更节点的对应的入口处连线 + // 如果是网关并行回退情况,会变成两条脏数据路线,效果一样 + for (FlowElement flowElement : allElements) { + if (flowElement.getId().equals(stack.peek().getTaskDefinitionKey())) { + dirtyTask = flowElement; + } + } + // 获取脏数据线路 + Set dirtyDataLine = FlowableUtils.iteratorFindDirtyRoads(dirtyTask, null, null, + Arrays.asList(dirtyPoint.split(",")), null); + // 自己本身也是脏线路上的点,加进去 + dirtyDataLine.add(stack.peek().getTaskDefinitionKey()); + log.info(stack.peek().getTaskDefinitionKey() + "点脏路线集合:" + dirtyDataLine); + // 是全新的需要添加的脏线路 + boolean isNewDirtyData = true; + for (int i = 0; i < dirtyDataLineList.size(); i++) { + // 如果发现他的上个节点在脏线路内,说明这个点可能是并行的节点,或者连续驳回 + // 这时,都以之前的脏线路节点为标准,只需合并脏线路即可,也就是路线补全 + if (dirtyDataLineList.get(i).contains(userTaskKey.toString())) { + isNewDirtyData = false; + dirtyDataLineList.get(i).addAll(dirtyDataLine); + } + } + // 已确定时全新的脏线路 + if (isNewDirtyData) { + // deleteKey 单一路线驳回到并行,这种同时生成多个新实例记录情况,这时 deleteKey 其实是由多个值组成 + // 按照逻辑,回退后立刻生成的实例记录就是回退的记录 + // 至于驳回所生成的 Key,直接从删除原因中获取,因为存在驳回到并行的情况 + deleteKeyList.add(dirtyPoint + ","); + dirtyDataLineList.add(dirtyDataLine); + } + // 添加后,现在这个点变成脏线路上的点了 + isDirtyData[0] = true; + } + // 如果不是脏线路上的点,说明是有效数据,添加历史实例 Key + if (!isDirtyData[0]) { + lastHistoricTaskInstanceList.add(stack.peek().getTaskDefinitionKey()); + } + // 校验脏线路是否结束 + for (int i = 0; i < deleteKeyList.size(); i++) { + // 如果发现脏数据属于会签,记录下下标与对应 Key,以备后续比对,会签脏数据范畴开始 + if (multiKey == null && multiTask.contains(stack.peek().getTaskDefinitionKey()) + && deleteKeyList.get(i).contains(stack.peek().getTaskDefinitionKey())) { + multiIndex = i; + multiKey = new StringBuilder(stack.peek().getTaskDefinitionKey()); + } + // 会签脏数据处理,节点退回会签清空 + // 如果在会签脏数据范畴中发现 Key改变,说明会签脏数据在上个节点就结束了,可以把会签脏数据删掉 + if (multiKey != null && !multiKey.toString().equals(stack.peek().getTaskDefinitionKey())) { + deleteKeyList.set(multiIndex, + deleteKeyList.get(multiIndex).replace(stack.peek().getTaskDefinitionKey() + ",", "")); + multiKey = null; + // 结束进行下校验删除 + multiOpera = true; + } + // 其他脏数据处理 + // 发现该路线最后一条脏数据,说明这条脏数据线路处理完了,删除脏数据信息 + // 脏数据产生的新实例中是否包含这条数据 + if (multiKey == null && deleteKeyList.get(i).contains(stack.peek().getTaskDefinitionKey())) { + // 删除匹配到的部分 + deleteKeyList.set(i, deleteKeyList.get(i).replace(stack.peek().getTaskDefinitionKey() + ",", "")); + } + // 如果每组中的元素都以匹配过,说明脏数据结束 + if ("".equals(deleteKeyList.get(i))) { + // 同时删除脏数据 + deleteKeyList.remove(i); + dirtyDataLineList.remove(i); + break; + } + } + // 会签数据处理需要在循环外处理,否则可能导致溢出 + // 会签的数据肯定是之前放进去的所以理论上不会溢出,但还是校验下 + if (multiOpera && deleteKeyList.size() > multiIndex && "".equals(deleteKeyList.get(multiIndex))) { + // 同时删除脏数据 + deleteKeyList.remove(multiIndex); + dirtyDataLineList.remove(multiIndex); + multiIndex = -1; + multiOpera = false; + } + // pop() 方法与 peek() 方法不同,在返回值的同时,会把值从栈中移除 + // 保存新的 userTaskKey 在下个循环中使用 + userTaskKey = new StringBuilder(stack.pop().getTaskDefinitionKey()); + } + log.info("清洗后的历史节点数据:" + lastHistoricTaskInstanceList); + return lastHistoricTaskInstanceList; + } + + /** + * 从 flowElement 获取 指定名称的 拓展元素 + * + * @param flowElement 元素 + * @param extensionElementName 拓展元素名称 + */ + public static ExtensionElement getExtensionElementFromFlowElementByName(FlowElement flowElement, + String extensionElementName) { + + if (flowElement == null) { + return null; + } + Map> extensionElements = flowElement.getExtensionElements(); + for (Map.Entry> stringEntry : extensionElements.entrySet()) { + if (stringEntry.getKey().equals(extensionElementName)) { + for (ExtensionElement extensionElement : stringEntry.getValue()) { + if (extensionElement.getName().equals(extensionElementName)) { + return extensionElement; + } + } + } + } + + return null; + } + + /** + * 获取当前任务节点扩展属性信息 + * + * @param repositoryService + * @param task 当前任务 + * @return 自定义属性列表 + */ + public static List getPropertyElement(RepositoryService repositoryService, + org.flowable.task.api.Task task) { + FlowElement flowElement = getCurrentElement(repositoryService, task); + ExtensionElement extensionElement = FlowableUtils.getExtensionElementFromFlowElementByName(flowElement, + "properties"); + if (extensionElement == null) { + return Collections.emptyList(); + } + return getPropertyExtensionElementByName(extensionElement, "property"); + } + + /** + * 获取当前任务节点 + * + * @param repositoryService + * @param task + * @return + */ + public static FlowElement getCurrentElement(RepositoryService repositoryService, org.flowable.task.api.Task task) { + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() + .processDefinitionId(task.getProcessDefinitionId()).singleResult(); + BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId()); + return bpmnModel.getFlowElement(task.getTaskDefinitionKey()); + } + + /** + * 根据属性名获取扩展元素中的扩展属性列表 + * + * @param extensionElement 扩展元素 + * @param attributesName 属性名 + * @return 扩展属性列表 + */ + public static List getPropertyExtensionElementByName(ExtensionElement extensionElement, + String attributesName) { + try { + // 获取名称为attributesName的子元素 + return Optional.ofNullable(extensionElement.getChildElements().get(attributesName)) + .orElse(Collections.emptyList()) // 如果子元素不存在则返回空集合,避免null引用 + .stream() + .map(element -> { + // 获取子元素的属性 + Map> attributes = element.getAttributes(); + Object propertyDto = new Object(); + // 获取FlowPropertyDto的所有属性 + Arrays.stream(propertyDto.getClass().getDeclaredFields()) + .forEach(field -> { + field.setAccessible(true); + // 获取属性名称和值 + attributes.getOrDefault(field.getName(), Collections.emptyList()) + .stream() + .findFirst() + .ifPresent(attribute -> { + try { + // 反射设置属性值 + field.set(propertyDto, attribute.getValue()); + } catch (IllegalAccessException e) { + e.printStackTrace(); + // 如果反射设置失败则忽略该属性 + } + }); + }); + return propertyDto; + }).collect(Collectors.toList()); + } catch (Exception e) { + e.printStackTrace(); + return Collections.emptyList(); // 如果发生异常则返回空列表 + } + } + +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/listener/FlowExecutionListener.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/listener/FlowExecutionListener.java new file mode 100644 index 0000000..c2af02d --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/listener/FlowExecutionListener.java @@ -0,0 +1,37 @@ +package com.boyue.flowable.listener; + +import org.flowable.common.engine.api.delegate.Expression; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.ExecutionListener; +import org.springframework.stereotype.Component; + +import lombok.extern.slf4j.Slf4j; + +/** + * 执行监听器 + * + * 执行监听器允许在执行过程中执行Java代码。 + * 执行监听器可以捕获事件的类型: + * 流程实例启动,结束 + * 输出流捕获 + * 获取启动,结束 + * 路由开始,结束 + * 中间事件开始,结束 + * 触发开始事件,触发结束事件 + * + * @author Tony + * @date 2022/12/16 + */ +@Slf4j +@Component +public class FlowExecutionListener implements ExecutionListener { + /** + * 流程设计器添加的参数 + */ + private Expression param; + + @Override + public void notify(DelegateExecution execution) { + log.info("执行监听器:{},参数:{}", execution, param); + } +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/listener/FlowTaskListener.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/listener/FlowTaskListener.java new file mode 100644 index 0000000..815c9b8 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/listener/FlowTaskListener.java @@ -0,0 +1,38 @@ +package com.boyue.flowable.listener; + +import org.flowable.common.engine.api.delegate.Expression; +import org.flowable.engine.delegate.TaskListener; +import org.flowable.task.service.delegate.DelegateTask; +import org.springframework.stereotype.Component; + +import lombok.extern.slf4j.Slf4j; + +/** + * 任务监听器 + * + * create(创建):在任务被创建且所有的任务属性设置完成后才触发 + * assignment(指派):在任务被分配给某个办理人之后触发 + * complete(完成):在配置了监听器的上一个任务完成时触发 + * delete(删除):在任务即将被删除前触发。请注意任务由completeTask正常完成时也会触发 + * + * @author Tony + * @date 2021/4/20 + */ +@Slf4j +@Component +public class FlowTaskListener implements TaskListener { + + /** + * 流程设计器添加的参数 + */ + private Expression param; + + @Override + public void notify(DelegateTask delegateTask) { + + // 获取事件类型 delegateTask.getEventName(),可以通过监听器给任务执行人发送相应的通知消息 + log.info("任务监听器:{}", delegateTask, param.getValue(delegateTask)); + + } + +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/mapper/FlowDeployMapper.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/mapper/FlowDeployMapper.java new file mode 100644 index 0000000..f383399 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/mapper/FlowDeployMapper.java @@ -0,0 +1,23 @@ +package com.boyue.flowable.mapper; + +import java.util.List; + +import com.boyue.flowable.domain.dto.FlowProcDefDto; + +/** + * 流程定义查询 + * + * @author Tony + * @email + * @date 2022/1/29 5:44 下午 + **/ +public interface FlowDeployMapper { + + /** + * 流程定义列表 + * + * @param name + * @return + */ + List selectDeployList(String name); +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/mapper/SysDeployFormMapper.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/mapper/SysDeployFormMapper.java new file mode 100644 index 0000000..5284636 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/mapper/SysDeployFormMapper.java @@ -0,0 +1,74 @@ +package com.boyue.flowable.mapper; + + + +import java.util.List; + +import com.boyue.flowable.domain.SysDeployForm; +import com.boyue.form.domain.FormTemplate; + +/** + * 流程实例关联表单Mapper接口 + * + * @author Tony + * @date 2021-03-30 + */ +public interface SysDeployFormMapper +{ + /** + * 查询流程实例关联表单 + * + * @param id 流程实例关联表单ID + * @return 流程实例关联表单 + */ + public SysDeployForm selectSysDeployFormById(Long id); + + /** + * 查询流程实例关联表单列表 + * + * @param SysDeployForm 流程实例关联表单 + * @return 流程实例关联表单集合 + */ + public List selectSysDeployFormList(SysDeployForm SysDeployForm); + + /** + * 新增流程实例关联表单 + * + * @param SysDeployForm 流程实例关联表单 + * @return 结果 + */ + public int insertSysDeployForm(SysDeployForm SysDeployForm); + + /** + * 修改流程实例关联表单 + * + * @param SysDeployForm 流程实例关联表单 + * @return 结果 + */ + public int updateSysDeployForm(SysDeployForm SysDeployForm); + + /** + * 删除流程实例关联表单 + * + * @param id 流程实例关联表单ID + * @return 结果 + */ + public int deleteSysDeployFormById(Long id); + + /** + * 批量删除流程实例关联表单 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteSysDeployFormByIds(Long[] ids); + + + + /** + * 查询流程挂着的表单 + * @param deployId + * @return + */ + FormTemplate selectSysDeployFormByDeployId(String deployId); +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/mapper/SysExpressionMapper.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/mapper/SysExpressionMapper.java new file mode 100644 index 0000000..6b62199 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/mapper/SysExpressionMapper.java @@ -0,0 +1,63 @@ +package com.boyue.flowable.mapper; + +import java.util.List; + +import com.boyue.flowable.domain.SysExpression; + + +/** + * 流程达式Mapper接口 + * + * @author boyue + * @date 2022-12-12 + */ +public interface SysExpressionMapper +{ + /** + * 查询流程达式 + * + * @param id 流程达式主键 + * @return 流程达式 + */ + public SysExpression selectSysExpressionById(Long id); + + /** + * 查询流程达式列表 + * + * @param sysExpression 流程达式 + * @return 流程达式集合 + */ + public List selectSysExpressionList(SysExpression sysExpression); + + /** + * 新增流程达式 + * + * @param sysExpression 流程达式 + * @return 结果 + */ + public int insertSysExpression(SysExpression sysExpression); + + /** + * 修改流程达式 + * + * @param sysExpression 流程达式 + * @return 结果 + */ + public int updateSysExpression(SysExpression sysExpression); + + /** + * 删除流程达式 + * + * @param id 流程达式主键 + * @return 结果 + */ + public int deleteSysExpressionById(Long id); + + /** + * 批量删除流程达式 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteSysExpressionByIds(Long[] ids); +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/mapper/SysListenerMapper.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/mapper/SysListenerMapper.java new file mode 100644 index 0000000..19db979 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/mapper/SysListenerMapper.java @@ -0,0 +1,62 @@ +package com.boyue.flowable.mapper; + +import java.util.List; + +import com.boyue.flowable.domain.SysListener; + +/** + * 流程监听Mapper接口 + * + * @author Tony + * @date 2022-12-25 + */ +public interface SysListenerMapper +{ + /** + * 查询流程监听 + * + * @param id 流程监听主键 + * @return 流程监听 + */ + public SysListener selectSysListenerById(Long id); + + /** + * 查询流程监听列表 + * + * @param sysListener 流程监听 + * @return 流程监听集合 + */ + public List selectSysListenerList(SysListener sysListener); + + /** + * 新增流程监听 + * + * @param sysListener 流程监听 + * @return 结果 + */ + public int insertSysListener(SysListener sysListener); + + /** + * 修改流程监听 + * + * @param sysListener 流程监听 + * @return 结果 + */ + public int updateSysListener(SysListener sysListener); + + /** + * 删除流程监听 + * + * @param id 流程监听主键 + * @return 结果 + */ + public int deleteSysListenerById(Long id); + + /** + * 批量删除流程监听 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteSysListenerByIds(Long[] ids); +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/mapper/SysTaskFormMapper.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/mapper/SysTaskFormMapper.java new file mode 100644 index 0000000..ccd153a --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/mapper/SysTaskFormMapper.java @@ -0,0 +1,64 @@ +package com.boyue.flowable.mapper; + + + +import java.util.List; + +import com.boyue.flowable.domain.SysTaskForm; + +/** + * 流程任务关联单Mapper接口 + * + * @author Tony + * @date 2021-04-03 + */ +public interface SysTaskFormMapper +{ + /** + * 查询流程任务关联单 + * + * @param id 流程任务关联单ID + * @return 流程任务关联单 + */ + public SysTaskForm selectSysTaskFormById(Long id); + + /** + * 查询流程任务关联单列表 + * + * @param sysTaskForm 流程任务关联单 + * @return 流程任务关联单集合 + */ + public List selectSysTaskFormList(SysTaskForm sysTaskForm); + + /** + * 新增流程任务关联单 + * + * @param sysTaskForm 流程任务关联单 + * @return 结果 + */ + public int insertSysTaskForm(SysTaskForm sysTaskForm); + + /** + * 修改流程任务关联单 + * + * @param sysTaskForm 流程任务关联单 + * @return 结果 + */ + public int updateSysTaskForm(SysTaskForm sysTaskForm); + + /** + * 删除流程任务关联单 + * + * @param id 流程任务关联单ID + * @return 结果 + */ + public int deleteSysTaskFormById(Long id); + + /** + * 批量删除流程任务关联单 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteSysTaskFormByIds(Long[] ids); +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/IFlowDefinitionService.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/IFlowDefinitionService.java new file mode 100644 index 0000000..c9f40dc --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/IFlowDefinitionService.java @@ -0,0 +1,80 @@ +package com.boyue.flowable.service; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Map; + +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.flowable.domain.dto.FlowProcDefDto; + +/** + * @author Tony + * @date 2021-04-03 14:41 + */ +public interface IFlowDefinitionService { + + boolean exist(String processDefinitionKey); + + + /** + * 流程定义列表 + * + * @param pageNum 当前页码 + * @param pageSize 每页条数 + * @return 流程定义分页列表数据 + */ + List list(String name,Integer pageNum, Integer pageSize); + + /** + * 导入流程文件 + * 当每个key的流程第一次部署时,指定版本为1。对其后所有使用相同key的流程定义, + * 部署时版本会在该key当前已部署的最高版本号基础上加1。key参数用于区分流程定义 + * @param name + * @param category + * @param in + */ + void importFile(String name, String category, InputStream in); + + /** + * 读取xml + * @param deployId + * @return + */ + AjaxResult readXml(String deployId) throws IOException; + + /** + * 根据流程定义ID启动流程实例 + * + * @param procDefId + * @param variables + * @return + */ + + AjaxResult startProcessInstanceById(String procDefId, Map variables); + + + /** + * 激活或挂起流程定义 + * + * @param state 状态 + * @param deployId 流程部署ID + */ + void updateState(Integer state, String deployId); + + + /** + * 删除流程定义 + * + * @param deployId 流程部署ID act_ge_bytearray 表中 deployment_id值 + */ + void delete(String deployId); + + + /** + * 读取图片文件 + * @param deployId + * @return + */ + InputStream readImage(String deployId); +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/IFlowInstanceService.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/IFlowInstanceService.java new file mode 100644 index 0000000..d00390e --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/IFlowInstanceService.java @@ -0,0 +1,53 @@ +package com.boyue.flowable.service; + +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.flowable.domain.vo.FlowTaskVo; +import org.flowable.engine.history.HistoricProcessInstance; +import java.util.Map; + +/** + * @author Tony + * @date 2021-04-03 14:40 + */ +public interface IFlowInstanceService { + + /** + * 结束流程实例 + * + * @param vo + */ + void stopProcessInstance(FlowTaskVo vo); + + /** + * 激活或挂起流程实例 + * + * @param state 状态 + * @param instanceId 流程实例ID + */ + void updateState(Integer state, String instanceId); + + /** + * 删除流程实例ID + * + * @param instanceId 流程实例ID + * @param deleteReason 删除原因 + */ + void delete(String instanceId, String deleteReason); + + /** + * 根据实例ID查询历史实例数据 + * + * @param processInstanceId + * @return + */ + HistoricProcessInstance getHistoricProcessInstanceById(String processInstanceId); + + /** + * 根据流程定义ID启动流程实例 + * + * @param procDefId 流程定义Id + * @param variables 流程变量 + * @return + */ + AjaxResult startProcessInstanceById(String procDefId, Map variables); +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/IFlowTaskService.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/IFlowTaskService.java new file mode 100644 index 0000000..2f79cec --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/IFlowTaskService.java @@ -0,0 +1,218 @@ +package com.boyue.flowable.service; + +import java.io.InputStream; +import java.util.List; + +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.flowable.domain.dto.FlowTaskDto; +import com.boyue.flowable.domain.vo.FlowQueryVo; +import com.boyue.flowable.domain.vo.FlowTaskVo; + +/** + * @author Tony + * @date 2021-04-03 14:42 + */ +public interface IFlowTaskService { + + /** + * 审批任务 + * + * @param task 请求实体参数 + */ + AjaxResult complete(FlowTaskVo task); + + /** + * 驳回任务 + * + * @param flowTaskVo + */ + void taskReject(FlowTaskVo flowTaskVo); + + + /** + * 退回任务 + * + * @param flowTaskVo 请求实体参数 + */ + void taskReturn(FlowTaskVo flowTaskVo); + + /** + * 获取所有可回退的节点 + * + * @param flowTaskVo + * @return + */ + AjaxResult findReturnTaskList(FlowTaskVo flowTaskVo); + + /** + * 删除任务 + * + * @param flowTaskVo 请求实体参数 + */ + void deleteTask(FlowTaskVo flowTaskVo); + + /** + * 认领/签收任务 + * + * @param flowTaskVo 请求实体参数 + */ + void claim(FlowTaskVo flowTaskVo); + + /** + * 取消认领/签收任务 + * + * @param flowTaskVo 请求实体参数 + */ + void unClaim(FlowTaskVo flowTaskVo); + + /** + * 委派任务 + * + * @param flowTaskVo 请求实体参数 + */ + void delegateTask(FlowTaskVo flowTaskVo); + + /** + * 任务归还 + * + * @param flowTaskVo 请求实体参数 + */ + void resolveTask(FlowTaskVo flowTaskVo); + + + /** + * 转办任务 + * + * @param flowTaskVo 请求实体参数 + */ + void assignTask(FlowTaskVo flowTaskVo); + + + /** + * 多实例加签 + * @param flowTaskVo + */ + void addMultiInstanceExecution(FlowTaskVo flowTaskVo); + + /** + * 多实例减签 + * @param flowTaskVo + */ + void deleteMultiInstanceExecution(FlowTaskVo flowTaskVo); + + /** + * 我发起的流程 + * @param queryVo 请求参数 + * @return + */ + List myProcess(FlowQueryVo queryVo); + + /** + * 取消申请 + * 目前实现方式: 直接将当前流程变更为已完成 + * @param flowTaskVo + * @return + */ + AjaxResult stopProcess(FlowTaskVo flowTaskVo); + + /** + * 撤回流程 + * @param flowTaskVo + * @return + */ + AjaxResult revokeProcess(FlowTaskVo flowTaskVo); + + + /** + * 代办任务列表 + * + * @param queryVo 请求参数 + * @return + */ + List todoList(FlowQueryVo queryVo); + + + /** + * 已办任务列表 + * + * @param queryVo 请求参数 + * @return + */ + List finishedList(FlowQueryVo queryVo); + + /** + * 流程历史流转记录 + * + * @param procInsId 流程实例Id + * @return + */ + AjaxResult flowRecord(String procInsId,String deployId); + + /** + * 根据任务ID查询挂载的表单信息 + * + * @param taskId 任务Id + * @return + */ + AjaxResult getTaskForm(String taskId); + + /** + * 获取流程过程图 + * @param processId + * @return + */ + InputStream diagram(String processId); + + /** + * 获取流程执行节点 + * @param procInsId + * @return + */ + AjaxResult getFlowViewer(String procInsId,String executionId); + + /** + * 获取流程变量 + * @param taskId + * @return + */ + AjaxResult processVariables(String taskId); + + /** + * 获取下一节点 + * @param flowTaskVo 任务 + * @return + */ + AjaxResult getNextFlowNode(FlowTaskVo flowTaskVo); + + AjaxResult getNextFlowNodeByStart(FlowTaskVo flowTaskVo); + + /** + * 流程初始化表单 + * @param deployId + * @return + */ + AjaxResult flowFormData(String deployId); + + /** + * 流程节点信息 + * @param procInsId + * @return + */ + AjaxResult flowXmlAndNode(String procInsId,String deployId); + + /** + * 流程节点表单 + * @param taskId 流程任务编号 + * @return + */ + AjaxResult flowTaskForm(String taskId) throws Exception; + + + /** + * 流程节点信息 + * @param procInsId + * @param elementId + * @return + */ + AjaxResult flowTaskInfo(String procInsId, String elementId); +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/ISysDeployFormService.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/ISysDeployFormService.java new file mode 100644 index 0000000..341f15d --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/ISysDeployFormService.java @@ -0,0 +1,69 @@ +package com.boyue.flowable.service; + +import java.util.List; + +import com.boyue.flowable.domain.SysDeployForm; +import com.boyue.form.domain.FormTemplate; +/** + * 流程实例关联表单Service接口 + * + * @author Tony + * @date 2021-04-03 + */ +public interface ISysDeployFormService +{ + /** + * 查询流程实例关联表单 + * + * @param id 流程实例关联表单ID + * @return 流程实例关联表单 + */ + public SysDeployForm selectSysDeployFormById(Long id); + + /** + * 查询流程实例关联表单列表 + * + * @param sysDeployForm 流程实例关联表单 + * @return 流程实例关联表单集合 + */ + public List selectSysDeployFormList(SysDeployForm sysDeployForm); + + /** + * 新增流程实例关联表单 + * + * @param sysDeployForm 流程实例关联表单 + * @return 结果 + */ + public int insertSysDeployForm(SysDeployForm sysDeployForm); + + /** + * 修改流程实例关联表单 + * + * @param sysDeployForm 流程实例关联表单 + * @return 结果 + */ + public int updateSysDeployForm(SysDeployForm sysDeployForm); + + /** + * 批量删除流程实例关联表单 + * + * @param ids 需要删除的流程实例关联表单ID + * @return 结果 + */ + public int deleteSysDeployFormByIds(Long[] ids); + + /** + * 删除流程实例关联表单信息 + * + * @param id 流程实例关联表单ID + * @return 结果 + */ + public int deleteSysDeployFormById(Long id); + + /** + * 查询流程挂着的表单 + * @param deployId + * @return + */ + FormTemplate selectSysDeployFormByDeployId(String deployId); +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/ISysExpressionService.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/ISysExpressionService.java new file mode 100644 index 0000000..09dd3f8 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/ISysExpressionService.java @@ -0,0 +1,62 @@ +package com.boyue.flowable.service; + +import java.util.List; + +import com.boyue.flowable.domain.SysExpression; + +/** + * 流程达式Service接口 + * + * @author boyue + * @date 2022-12-12 + */ +public interface ISysExpressionService +{ + /** + * 查询流程达式 + * + * @param id 流程达式主键 + * @return 流程达式 + */ + public SysExpression selectSysExpressionById(Long id); + + /** + * 查询流程达式列表 + * + * @param sysExpression 流程达式 + * @return 流程达式集合 + */ + public List selectSysExpressionList(SysExpression sysExpression); + + /** + * 新增流程达式 + * + * @param sysExpression 流程达式 + * @return 结果 + */ + public int insertSysExpression(SysExpression sysExpression); + + /** + * 修改流程达式 + * + * @param sysExpression 流程达式 + * @return 结果 + */ + public int updateSysExpression(SysExpression sysExpression); + + /** + * 批量删除流程达式 + * + * @param ids 需要删除的流程达式主键集合 + * @return 结果 + */ + public int deleteSysExpressionByIds(Long[] ids); + + /** + * 删除流程达式信息 + * + * @param id 流程达式主键 + * @return 结果 + */ + public int deleteSysExpressionById(Long id); +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/ISysListenerService.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/ISysListenerService.java new file mode 100644 index 0000000..3b53ac9 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/ISysListenerService.java @@ -0,0 +1,62 @@ +package com.boyue.flowable.service; + +import java.util.List; + +import com.boyue.flowable.domain.SysListener; + +/** + * 流程监听Service接口 + * + * @author Tony + * @date 2022-12-25 + */ +public interface ISysListenerService +{ + /** + * 查询流程监听 + * + * @param id 流程监听主键 + * @return 流程监听 + */ + public SysListener selectSysListenerById(Long id); + + /** + * 查询流程监听列表 + * + * @param sysListener 流程监听 + * @return 流程监听集合 + */ + public List selectSysListenerList(SysListener sysListener); + + /** + * 新增流程监听 + * + * @param sysListener 流程监听 + * @return 结果 + */ + public int insertSysListener(SysListener sysListener); + + /** + * 修改流程监听 + * + * @param sysListener 流程监听 + * @return 结果 + */ + public int updateSysListener(SysListener sysListener); + + /** + * 批量删除流程监听 + * + * @param ids 需要删除的流程监听主键集合 + * @return 结果 + */ + public int deleteSysListenerByIds(Long[] ids); + + /** + * 删除流程监听信息 + * + * @param id 流程监听主键 + * @return 结果 + */ + public int deleteSysListenerById(Long id); +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/impl/FlowDefinitionServiceImpl.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/impl/FlowDefinitionServiceImpl.java new file mode 100644 index 0000000..f3783e1 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/impl/FlowDefinitionServiceImpl.java @@ -0,0 +1,246 @@ +package com.boyue.flowable.service.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.apache.commons.io.IOUtils; +import org.flowable.bpmn.model.BpmnModel; +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.image.impl.DefaultProcessDiagramGenerator; +import org.flowable.task.api.Task; +import org.springframework.stereotype.Service; + +import com.github.pagehelper.PageHelper; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.domain.entity.SysUser; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.flowable.common.constant.ProcessConstants; +import com.boyue.flowable.common.enums.FlowComment; +import com.boyue.flowable.domain.dto.FlowProcDefDto; +import com.boyue.flowable.factory.FlowServiceFactory; +import com.boyue.flowable.mapper.FlowDeployMapper; +import com.boyue.flowable.service.IFlowDefinitionService; +import com.boyue.flowable.service.ISysDeployFormService; +import com.boyue.form.domain.FormTemplate; +import com.boyue.system.service.ISysDeptService; +import com.boyue.system.service.ISysUserService; + +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; + +/** + * 流程定义 + * + * @author Tony + * @date 2021-04-03 + */ +@Service +@Slf4j +public class FlowDefinitionServiceImpl extends FlowServiceFactory implements IFlowDefinitionService { + + @Resource + private ISysDeployFormService sysDeployFormService; + + @Resource + private ISysUserService sysUserService; + + @Resource + private ISysDeptService sysDeptService; + + @Resource + private FlowDeployMapper flowDeployMapper; + + private static final String BPMN_FILE_SUFFIX = ".bpmn"; + + @Override + public boolean exist(String processDefinitionKey) { + ProcessDefinitionQuery processDefinitionQuery + = repositoryService.createProcessDefinitionQuery().processDefinitionKey(processDefinitionKey); + long count = processDefinitionQuery.count(); + return count > 0 ? true : false; + } + + + /** + * 流程定义列表 + * + * @param pageNum 当前页码 + * @param pageSize 每页条数 + * @return 流程定义分页列表数据 + */ + @Override + public List list(String name, Integer pageNum, Integer pageSize) { +// Page page = new Page(); +// // 流程定义列表数据查询 +// final ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery(); +// if (StringUtils.isNotEmpty(name)) { +// processDefinitionQuery.processDefinitionNameLike(name); +// } +//// processDefinitionQuery.orderByProcessDefinitionKey().asc(); +// page.setTotal(processDefinitionQuery.count()); +// List processDefinitionList = processDefinitionQuery.listPage(pageSize * (pageNum - 1), pageSize); +// +// List dataList = new ArrayList<>(); +// for (ProcessDefinition processDefinition : processDefinitionList) { +// String deploymentId = processDefinition.getDeploymentId(); +// Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(deploymentId).singleResult(); +// FlowProcDefDto reProcDef = new FlowProcDefDto(); +// BeanUtils.copyProperties(processDefinition, reProcDef); +// SysForm sysForm = sysDeployFormService.selectSysDeployFormByDeployId(deploymentId); +// if (Objects.nonNull(sysForm)) { +// reProcDef.setFormName(sysForm.getFormName()); +// reProcDef.setFormId(sysForm.getFormId()); +// } +// // 流程定义时间 +// reProcDef.setDeploymentTime(deployment.getDeploymentTime()); +// dataList.add(reProcDef); +// } + PageHelper.startPage(pageNum, pageSize); + final List dataList = flowDeployMapper.selectDeployList(name); + // 加载挂表单 + for (FlowProcDefDto procDef : dataList) { + FormTemplate sysForm = sysDeployFormService.selectSysDeployFormByDeployId(procDef.getDeploymentId()); + if (Objects.nonNull(sysForm)) { + procDef.setFormName(sysForm.getFormName()); + procDef.setFormId(sysForm.getFormId()); + } + } + return dataList; + } + + + /** + * 导入流程文件 + * + * 当每个key的流程第一次部署时,指定版本为1。对其后所有使用相同key的流程定义, + * 部署时版本会在该key当前已部署的最高版本号基础上加1。key参数用于区分流程定义 + * @param name + * @param category + * @param in + */ + @Override + public void importFile(String name, String category, InputStream in) { + Deployment deploy = repositoryService.createDeployment().addInputStream(name + BPMN_FILE_SUFFIX, in).name(name).category(category).deploy(); + ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deploy.getId()).singleResult(); + repositoryService.setProcessDefinitionCategory(definition.getId(), category); + + } + + /** + * 读取xml + * + * @param deployId + * @return + */ + @Override + public AjaxResult readXml(String deployId) throws IOException { + ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult(); + InputStream inputStream = repositoryService.getResourceAsStream(definition.getDeploymentId(), definition.getResourceName()); + String result = IOUtils.toString(inputStream, StandardCharsets.UTF_8.name()); + return AjaxResult.success("", result); + } + + /** + * 读取xml + * + * @param deployId + * @return + */ + @Override + public InputStream readImage(String deployId) { + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult(); + //获得图片流 + DefaultProcessDiagramGenerator diagramGenerator = new DefaultProcessDiagramGenerator(); + BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId()); + //输出为图片 + return diagramGenerator.generateDiagram( + bpmnModel, + "png", + Collections.emptyList(), + Collections.emptyList(), + "宋体", + "宋体", + "宋体", + null, + 1.0, + false); + + } + + /** + * 根据流程定义ID启动流程实例 + * + * @param procDefId 流程模板ID + * @param variables 流程变量 + * @return + */ + @Override + public AjaxResult startProcessInstanceById(String procDefId, Map variables) { + try { + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(procDefId) + .latestVersion().singleResult(); + if (Objects.nonNull(processDefinition) && processDefinition.isSuspended()) { + return AjaxResult.error("流程已被挂起,请先激活流程"); + } + // 设置流程发起人Id到流程中 + SysUser sysUser = SecurityUtils.getLoginUser().getUser(); + identityService.setAuthenticatedUserId(sysUser.getUserId().toString()); + variables.put(ProcessConstants.PROCESS_INITIATOR, sysUser.getUserId()); + + // 流程发起时 跳过发起人节点 + ProcessInstance processInstance = runtimeService.startProcessInstanceById(procDefId, variables); + // 给第一步申请人节点设置任务执行人和意见 + Task task = taskService.createTaskQuery().processInstanceId(processInstance.getProcessInstanceId()).singleResult(); + if (Objects.nonNull(task)) { + taskService.addComment(task.getId(), processInstance.getProcessInstanceId(), FlowComment.NORMAL.getType(), sysUser.getNickName() + "发起流程申请"); + taskService.complete(task.getId(), variables); + } + return AjaxResult.success("流程启动成功"); + } catch (Exception e) { + e.printStackTrace(); + return AjaxResult.error("流程启动错误"); + } + } + + + /** + * 激活或挂起流程定义 + * + * @param state 状态 + * @param deployId 流程部署ID + */ + @Override + public void updateState(Integer state, String deployId) { + ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery().deploymentId(deployId).singleResult(); + // 激活 + if (state == 1) { + repositoryService.activateProcessDefinitionById(procDef.getId(), true, null); + } + // 挂起 + if (state == 2) { + repositoryService.suspendProcessDefinitionById(procDef.getId(), true, null); + } + } + + + /** + * 删除流程定义 + * + * @param deployId 流程部署ID act_ge_bytearray 表中 deployment_id值 + */ + @Override + public void delete(String deployId) { + // true 允许级联删除 ,不设置会导致数据库外键关联异常 + repositoryService.deleteDeployment(deployId, true); + } + + +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/impl/FlowInstanceServiceImpl.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/impl/FlowInstanceServiceImpl.java new file mode 100644 index 0000000..d5822b2 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/impl/FlowInstanceServiceImpl.java @@ -0,0 +1,140 @@ +package com.boyue.flowable.service.impl; + +import java.util.Map; +import java.util.Objects; + +import org.flowable.common.engine.api.FlowableObjectNotFoundException; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.task.api.Task; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.flowable.domain.vo.FlowTaskVo; +import com.boyue.flowable.factory.FlowServiceFactory; +import com.boyue.flowable.service.IFlowInstanceService; + +import lombok.extern.slf4j.Slf4j; + +/** + *

+ * 工作流流程实例管理 + *

+ * + * @author Tony + * @date 2021-04-03 + */ +@Service +@Slf4j +public class FlowInstanceServiceImpl extends FlowServiceFactory implements IFlowInstanceService { + + /** + * 结束流程实例 + * + * @param vo + */ + @Override + public void stopProcessInstance(FlowTaskVo vo) { + String taskId = vo.getTaskId(); + + if (!StringUtils.hasText(taskId)) { + throw new IllegalArgumentException("任务ID不能为空。"); + } + Task task = taskService.createTaskQuery().taskId(taskId).singleResult(); + if (task == null) { + throw new RuntimeException("未找到ID为 " + taskId + " 的任务,无法停止流程实例。可能任务已完成或不存在。"); + } + String processInstanceId = task.getProcessInstanceId(); + if (!StringUtils.hasText(processInstanceId)) { + throw new RuntimeException("任务 " + taskId + " 没有关联的流程实例ID。"); + } + String deleteReason = vo.getComment(); // 假设 FlowTaskVo 有 getComment() 方法获取原因 + if (!StringUtils.hasText(deleteReason)) { + deleteReason = "流程实例由用户通过任务ID " + taskId + " 手动停止。"; // 提供一个默认原因 + } + runtimeService.deleteProcessInstance(processInstanceId, deleteReason); + } + + /** + * 激活或挂起流程实例 + * + * @param state 状态 + * @param instanceId 流程实例ID + */ + @Override + public void updateState(Integer state, String instanceId) { + + // 激活 + if (state == 1) { + runtimeService.activateProcessInstanceById(instanceId); + } + // 挂起 + if (state == 2) { + runtimeService.suspendProcessInstanceById(instanceId); + } + } + + /** + * 删除流程实例ID + * + * @param instanceId 流程实例ID + * @param deleteReason 删除原因 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(String instanceId, String deleteReason) { + + // 查询历史数据 + HistoricProcessInstance historicProcessInstance = getHistoricProcessInstanceById(instanceId); + if (historicProcessInstance.getEndTime() != null) { + historyService.deleteHistoricProcessInstance(historicProcessInstance.getId()); + return; + } + // 删除流程实例 + runtimeService.deleteProcessInstance(instanceId, deleteReason); + // 删除历史流程实例 + historyService.deleteHistoricProcessInstance(instanceId); + } + + /** + * 根据实例ID查询历史实例数据 + * + * @param processInstanceId + * @return + */ + @Override + public HistoricProcessInstance getHistoricProcessInstanceById(String processInstanceId) { + HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processInstanceId).singleResult(); + if (Objects.isNull(historicProcessInstance)) { + throw new FlowableObjectNotFoundException("流程实例不存在: " + processInstanceId); + } + return historicProcessInstance; + } + + /** + * 根据流程定义ID启动流程实例 + * + * @param procDefId 流程定义Id + * @param variables 流程变量 + * @return + */ + @Override + public AjaxResult startProcessInstanceById(String procDefId, Map variables) { + + try { + // 设置流程发起人Id到流程中 + Long userId = SecurityUtils.getLoginUser().getUser().getUserId(); + // identityService.setAuthenticatedUserId(userId.toString()); + variables.put("initiator", userId); + variables.put("_FLOWABLE_SKIP_EXPRESSION_ENABLED", true); + runtimeService.startProcessInstanceById(procDefId, variables); + return AjaxResult.success("流程启动成功"); + } catch (Exception e) { + e.printStackTrace(); + return AjaxResult.error("流程启动错误"); + } + } +} \ No newline at end of file diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/impl/FlowTaskServiceImpl.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/impl/FlowTaskServiceImpl.java new file mode 100644 index 0000000..57ac330 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/impl/FlowTaskServiceImpl.java @@ -0,0 +1,1315 @@ +package com.boyue.flowable.service.impl; + +import java.io.InputStream; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.EndEvent; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.MultiInstanceLoopCharacteristics; +import org.flowable.bpmn.model.Process; +import org.flowable.bpmn.model.UserTask; +import org.flowable.common.engine.api.FlowableException; +import org.flowable.common.engine.api.FlowableObjectNotFoundException; +import org.flowable.common.engine.impl.identity.Authentication; +import org.flowable.engine.ProcessEngineConfiguration; +import org.flowable.engine.history.HistoricActivityInstance; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.history.HistoricProcessInstanceQuery; +import org.flowable.engine.impl.cmd.AddMultiInstanceExecutionCmd; +import org.flowable.engine.impl.cmd.DeleteMultiInstanceExecutionCmd; +import org.flowable.engine.repository.ProcessDefinition; +import org.flowable.engine.runtime.Execution; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.engine.task.Comment; +import org.flowable.identitylink.api.history.HistoricIdentityLink; +import org.flowable.image.ProcessDiagramGenerator; +import org.flowable.task.api.DelegationState; +import org.flowable.task.api.Task; +import org.flowable.task.api.TaskQuery; +import org.flowable.task.api.history.HistoricTaskInstance; +import org.flowable.task.api.history.HistoricTaskInstanceQuery; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.TypeReference; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.domain.entity.SysRole; +import com.boyue.common.core.domain.entity.SysUser; +import com.boyue.common.exception.ServiceException; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.flowable.common.constant.ProcessConstants; +import com.boyue.flowable.common.enums.FlowComment; +import com.boyue.flowable.domain.dto.FlowCommentDto; +import com.boyue.flowable.domain.dto.FlowNextDto; +import com.boyue.flowable.domain.dto.FlowTaskDto; +import com.boyue.flowable.domain.dto.FlowViewerDto; +import com.boyue.flowable.domain.vo.FlowQueryVo; +import com.boyue.flowable.domain.vo.FlowTaskVo; +import com.boyue.flowable.factory.FlowServiceFactory; +import com.boyue.flowable.flow.CustomProcessDiagramGenerator; +import com.boyue.flowable.flow.FindNextNodeUtil; +import com.boyue.flowable.flow.FlowableUtils; +import com.boyue.flowable.service.IFlowTaskService; +import com.boyue.flowable.service.ISysDeployFormService; +import com.boyue.form.domain.FormTemplate; +import com.boyue.form.service.IFormTemplateService; +import com.boyue.system.service.ISysRoleService; +import com.boyue.system.service.ISysUserService; + +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; + +/** + * @author Tony + * @date 2021-04-03 + **/ +@Service +@Slf4j +public class FlowTaskServiceImpl extends FlowServiceFactory implements IFlowTaskService { + + @Resource + private ISysUserService sysUserService; + @Resource + private ISysRoleService sysRoleService; + @Resource + private ISysDeployFormService sysInstanceFormService; + // @Resource + // private ISysFormService sysFormService; + @Resource + private IFormTemplateService formTemplateService; + + /** + * 完成任务 + * + * @param taskVo 请求实体参数 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public AjaxResult complete(FlowTaskVo taskVo) { + Task task = taskService.createTaskQuery().taskId(taskVo.getTaskId()).singleResult(); + if (Objects.isNull(task)) { + return AjaxResult.error("任务不存在"); + } + if (DelegationState.PENDING.equals(task.getDelegationState())) { + taskService.addComment(taskVo.getTaskId(), taskVo.getInstanceId(), FlowComment.DELEGATE.getType(), + taskVo.getComment()); + taskService.resolveTask(taskVo.getTaskId(), taskVo.getVariables()); + } else { + taskService.addComment(taskVo.getTaskId(), taskVo.getInstanceId(), FlowComment.NORMAL.getType(), + taskVo.getComment()); + Long userId = SecurityUtils.getLoginUser().getUser().getUserId(); + taskService.setAssignee(taskVo.getTaskId(), userId.toString()); + taskService.complete(taskVo.getTaskId(), taskVo.getVariables()); + } + return AjaxResult.success(); + } + + /** + * 驳回任务 + * + * @param flowTaskVo + */ + @Override + public void taskReject(FlowTaskVo flowTaskVo) { + if (taskService.createTaskQuery().taskId(flowTaskVo.getTaskId()).singleResult().isSuspended()) { + throw new ServiceException("任务处于挂起状态!"); + } + // 当前任务 task + Task task = taskService.createTaskQuery().taskId(flowTaskVo.getTaskId()).singleResult(); + // 获取流程定义信息 + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() + .processDefinitionId(task.getProcessDefinitionId()).singleResult(); + // 获取所有节点信息 + Process process = repositoryService.getBpmnModel(processDefinition.getId()).getProcesses().get(0); + // 获取全部节点列表,包含子节点 + Collection allElements = FlowableUtils.getAllElements(process.getFlowElements(), null); + // 获取当前任务节点元素 + FlowElement source = null; + if (allElements != null) { + for (FlowElement flowElement : allElements) { + // 类型为用户节点 + if (flowElement.getId().equals(task.getTaskDefinitionKey())) { + // 获取节点信息 + source = flowElement; + } + } + } + + // 目的获取所有跳转到的节点 targetIds + // 获取当前节点的所有父级用户任务节点 + // 深度优先算法思想:延边迭代深入 + List parentUserTaskList = FlowableUtils.iteratorFindParentUserTasks(source, null, null); + if (parentUserTaskList == null || parentUserTaskList.size() == 0) { + throw new ServiceException("当前节点为初始任务节点,不能驳回"); + } + // 获取活动 ID 即节点 Key + List parentUserTaskKeyList = new ArrayList<>(); + parentUserTaskList.forEach(item -> parentUserTaskKeyList.add(item.getId())); + // 获取全部历史节点活动实例,即已经走过的节点历史,数据采用开始时间升序 + List historicTaskInstanceList = historyService.createHistoricTaskInstanceQuery() + .processInstanceId(task.getProcessInstanceId()).orderByHistoricTaskInstanceStartTime().asc().list(); + // 数据清洗,将回滚导致的脏数据清洗掉 + List lastHistoricTaskInstanceList = FlowableUtils.historicTaskInstanceClean(allElements, + historicTaskInstanceList); + // 此时历史任务实例为倒序,获取最后走的节点 + List targetIds = new ArrayList<>(); + // 循环结束标识,遇到当前目标节点的次数 + int number = 0; + StringBuilder parentHistoricTaskKey = new StringBuilder(); + for (String historicTaskInstanceKey : lastHistoricTaskInstanceList) { + // 当会签时候会出现特殊的,连续都是同一个节点历史数据的情况,这种时候跳过 + if (parentHistoricTaskKey.toString().equals(historicTaskInstanceKey)) { + continue; + } + parentHistoricTaskKey = new StringBuilder(historicTaskInstanceKey); + if (historicTaskInstanceKey.equals(task.getTaskDefinitionKey())) { + number++; + } + // 在数据清洗后,历史节点就是唯一一条从起始到当前节点的历史记录,理论上每个点只会出现一次 + // 在流程中如果出现循环,那么每次循环中间的点也只会出现一次,再出现就是下次循环 + // number == 1,第一次遇到当前节点 + // number == 2,第二次遇到,代表最后一次的循环范围 + if (number == 2) { + break; + } + // 如果当前历史节点,属于父级的节点,说明最后一次经过了这个点,需要退回这个点 + if (parentUserTaskKeyList.contains(historicTaskInstanceKey)) { + targetIds.add(historicTaskInstanceKey); + } + } + + // 目的获取所有需要被跳转的节点 currentIds + // 取其中一个父级任务,因为后续要么存在公共网关,要么就是串行公共线路 + UserTask oneUserTask = parentUserTaskList.get(0); + // 获取所有正常进行的任务节点 Key,这些任务不能直接使用,需要找出其中需要撤回的任务 + List runTaskList = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).list(); + List runTaskKeyList = new ArrayList<>(); + runTaskList.forEach(item -> runTaskKeyList.add(item.getTaskDefinitionKey())); + // 需驳回任务列表 + List currentIds = new ArrayList<>(); + // 通过父级网关的出口连线,结合 runTaskList 比对,获取需要撤回的任务 + List currentUserTaskList = FlowableUtils.iteratorFindChildUserTasks(oneUserTask, runTaskKeyList, null, + null); + currentUserTaskList.forEach(item -> currentIds.add(item.getId())); + + // 规定:并行网关之前节点必须需存在唯一用户任务节点,如果出现多个任务节点,则并行网关节点默认为结束节点,原因为不考虑多对多情况 + if (targetIds.size() > 1 && currentIds.size() > 1) { + throw new ServiceException("任务出现多对多情况,无法撤回"); + } + + // 循环获取那些需要被撤回的节点的ID,用来设置驳回原因 + List currentTaskIds = new ArrayList<>(); + currentIds.forEach(currentId -> runTaskList.forEach(runTask -> { + if (currentId.equals(runTask.getTaskDefinitionKey())) { + currentTaskIds.add(runTask.getId()); + } + })); + // 设置驳回意见 + currentTaskIds.forEach(item -> taskService.addComment(item, task.getProcessInstanceId(), + FlowComment.REJECT.getType(), flowTaskVo.getComment())); + + try { + // 如果父级任务多于 1 个,说明当前节点不是并行节点,原因为不考虑多对多情况 + if (targetIds.size() > 1) { + // 1 对 多任务跳转,currentIds 当前节点(1),targetIds 跳转到的节点(多) + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(task.getProcessInstanceId()) + .moveSingleActivityIdToActivityIds(currentIds.get(0), targetIds).changeState(); + } + // 如果父级任务只有一个,因此当前任务可能为网关中的任务 + if (targetIds.size() == 1) { + // 1 对 1 或 多 对 1 情况,currentIds 当前要跳转的节点列表(1或多),targetIds.get(0) 跳转到的节点(1) + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(task.getProcessInstanceId()) + .moveActivityIdsToSingleActivityId(currentIds, targetIds.get(0)).changeState(); + } + } catch (FlowableObjectNotFoundException e) { + throw new ServiceException("未找到流程实例,流程可能已发生变化"); + } catch (FlowableException e) { + throw new ServiceException("无法取消或开始活动"); + } + + } + + /** + * 退回任务 + * + * @param flowTaskVo 请求实体参数 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public void taskReturn(FlowTaskVo flowTaskVo) { + if (taskService.createTaskQuery().taskId(flowTaskVo.getTaskId()).singleResult().isSuspended()) { + throw new ServiceException("任务处于挂起状态"); + } + // 当前任务 task + Task task = taskService.createTaskQuery().taskId(flowTaskVo.getTaskId()).singleResult(); + // 获取流程定义信息 + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() + .processDefinitionId(task.getProcessDefinitionId()).singleResult(); + // 获取所有节点信息 + Process process = repositoryService.getBpmnModel(processDefinition.getId()).getProcesses().get(0); + // 获取全部节点列表,包含子节点 + Collection allElements = FlowableUtils.getAllElements(process.getFlowElements(), null); + // 获取当前任务节点元素 + FlowElement source = null; + // 获取跳转的节点元素 + FlowElement target = null; + if (allElements != null) { + for (FlowElement flowElement : allElements) { + // 当前任务节点元素 + if (flowElement.getId().equals(task.getTaskDefinitionKey())) { + source = flowElement; + } + // 跳转的节点元素 + if (flowElement.getId().equals(flowTaskVo.getTargetKey())) { + target = flowElement; + } + } + } + + // 从当前节点向前扫描 + // 如果存在路线上不存在目标节点,说明目标节点是在网关上或非同一路线上,不可跳转 + // 否则目标节点相对于当前节点,属于串行 + Boolean isSequential = FlowableUtils.iteratorCheckSequentialReferTarget(source, flowTaskVo.getTargetKey(), null, + null); + if (!isSequential) { + throw new ServiceException("当前节点相对于目标节点,不属于串行关系,无法回退"); + } + + // 获取所有正常进行的任务节点 Key,这些任务不能直接使用,需要找出其中需要撤回的任务 + List runTaskList = taskService.createTaskQuery().processInstanceId(task.getProcessInstanceId()).list(); + List runTaskKeyList = new ArrayList<>(); + runTaskList.forEach(item -> runTaskKeyList.add(item.getTaskDefinitionKey())); + // 需退回任务列表 + List currentIds = new ArrayList<>(); + // 通过父级网关的出口连线,结合 runTaskList 比对,获取需要撤回的任务 + List currentUserTaskList = FlowableUtils.iteratorFindChildUserTasks(target, runTaskKeyList, null, + null); + currentUserTaskList.forEach(item -> currentIds.add(item.getId())); + + // 循环获取那些需要被撤回的节点的ID,用来设置驳回原因 + List currentTaskIds = new ArrayList<>(); + currentIds.forEach(currentId -> runTaskList.forEach(runTask -> { + if (currentId.equals(runTask.getTaskDefinitionKey())) { + currentTaskIds.add(runTask.getId()); + } + })); + // 设置回退意见 + currentTaskIds.forEach(currentTaskId -> taskService.addComment(currentTaskId, task.getProcessInstanceId(), + FlowComment.REBACK.getType(), flowTaskVo.getComment())); + + try { + // 1 对 1 或 多 对 1 情况,currentIds 当前要跳转的节点列表(1或多),targetKey 跳转到的节点(1) + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(task.getProcessInstanceId()) + .moveActivityIdsToSingleActivityId(currentIds, flowTaskVo.getTargetKey()).changeState(); + } catch (FlowableObjectNotFoundException e) { + throw new ServiceException("未找到流程实例,流程可能已发生变化"); + } catch (FlowableException e) { + throw new ServiceException("无法取消或开始活动"); + } + } + + /** + * 获取所有可回退的节点 + * + * @param flowTaskVo + * @return + */ + @Override + public AjaxResult findReturnTaskList(FlowTaskVo flowTaskVo) { + // 当前任务 task + Task task = taskService.createTaskQuery().taskId(flowTaskVo.getTaskId()).singleResult(); + // 获取流程定义信息 + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() + .processDefinitionId(task.getProcessDefinitionId()).singleResult(); + // 获取所有节点信息,暂不考虑子流程情况 + Process process = repositoryService.getBpmnModel(processDefinition.getId()).getProcesses().get(0); + Collection flowElements = process.getFlowElements(); + // 获取当前任务节点元素 + UserTask source = null; + if (flowElements != null) { + for (FlowElement flowElement : flowElements) { + // 类型为用户节点 + if (flowElement.getId().equals(task.getTaskDefinitionKey())) { + source = (UserTask) flowElement; + } + } + } + // 获取节点的所有路线 + List> roads = FlowableUtils.findRoad(source, null, null, null); + // 可回退的节点列表 + List userTaskList = new ArrayList<>(); + for (List road : roads) { + if (userTaskList.size() == 0) { + // 还没有可回退节点直接添加 + userTaskList = road; + } else { + // 如果已有回退节点,则比对取交集部分 + userTaskList.retainAll(road); + } + } + return AjaxResult.success(userTaskList); + } + + /** + * 删除任务 + * + * @param flowTaskVo 请求实体参数 + */ + @Override + public void deleteTask(FlowTaskVo flowTaskVo) { + // todo 待确认删除任务是物理删除任务 还是逻辑删除,让这个任务直接通过? + taskService.deleteTask(flowTaskVo.getTaskId(), flowTaskVo.getComment()); + } + + /** + * 认领/签收任务 + * 认领以后,这个用户就会成为任务的执行人,任务会从其他成员的任务列表中消失 + * + * @param flowTaskVo 请求实体参数 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void claim(FlowTaskVo flowTaskVo) { + taskService.claim(flowTaskVo.getTaskId(), flowTaskVo.getUserId()); + } + + /** + * 取消认领/签收任务 + * + * @param flowTaskVo 请求实体参数 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void unClaim(FlowTaskVo flowTaskVo) { + taskService.unclaim(flowTaskVo.getTaskId()); + } + + /** + * 委派任务 + * 任务委派只是委派人将当前的任务交给被委派人进行审批,处理任务后又重新回到委派人身上。 + * + * @param flowTaskVo 请求实体参数 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void delegateTask(FlowTaskVo flowTaskVo) { + taskService.delegateTask(flowTaskVo.getTaskId(), flowTaskVo.getAssignee()); + } + + /** + * 任务归还 + * 被委派人完成任务之后,将任务归还委派人 + * + * @param flowTaskVo 请求实体参数 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void resolveTask(FlowTaskVo flowTaskVo) { + taskService.resolveTask(flowTaskVo.getTaskId()); + } + + /** + * 转办任务 + * 直接将办理人换成别人,这时任务的拥有者不再是转办人 + * + * @param flowTaskVo 请求实体参数 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void assignTask(FlowTaskVo flowTaskVo) { + // 直接转派就可以覆盖掉之前的 + taskService.setAssignee(flowTaskVo.getTaskId(), flowTaskVo.getAssignee()); + // // 删除指派人重新指派 + // taskService.deleteCandidateUser(flowTaskVo.getTaskId(),flowTaskVo.getAssignee()); + // taskService.addCandidateUser(flowTaskVo.getTaskId(),flowTaskVo.getAssignee()); + // // 如果要查询转给他人处理的任务,可以同时将OWNER进行设置: + // taskService.setOwner(flowTaskVo.getTaskId(), flowTaskVo.getAssignee()); + + } + + /** + * 多实例加签 + * act_ru_task、act_ru_identitylink各生成一条记录 + * + * @param flowTaskVo + */ + @Override + public void addMultiInstanceExecution(FlowTaskVo flowTaskVo) { + managementService.executeCommand(new AddMultiInstanceExecutionCmd(flowTaskVo.getDefId(), + flowTaskVo.getInstanceId(), flowTaskVo.getVariables())); + } + + /** + * 多实例减签 + * act_ru_task减1、act_ru_identitylink不变 + * + * @param flowTaskVo + */ + @Override + public void deleteMultiInstanceExecution(FlowTaskVo flowTaskVo) { + managementService.executeCommand( + new DeleteMultiInstanceExecutionCmd(flowTaskVo.getCurrentChildExecutionId(), flowTaskVo.getFlag())); + } + + /** + * 我发起的流程 + * + * @param queryVo 请求参数 + * @return + */ + @Override + public List myProcess(FlowQueryVo queryVo) { + // List page = new Page<>(); + Long userId = SecurityUtils.getLoginUser().getUser().getUserId(); + HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery() + .startedBy(userId.toString()) + .orderByProcessInstanceStartTime() + .desc(); + List historicProcessInstances = historicProcessInstanceQuery + .listPage(queryVo.getPageSize() * (queryVo.getPageNum() - 1), queryVo.getPageSize()); + List flowList = new ArrayList<>(); + for (HistoricProcessInstance hisIns : historicProcessInstances) { + FlowTaskDto flowTask = new FlowTaskDto(); + flowTask.setCreateTime(hisIns.getStartTime()); + flowTask.setFinishTime(hisIns.getEndTime()); + flowTask.setProcInsId(hisIns.getId()); + + // 计算耗时 + if (Objects.nonNull(hisIns.getEndTime())) { + long time = hisIns.getEndTime().getTime() - hisIns.getStartTime().getTime(); + flowTask.setDuration(getDate(time)); + } else { + long time = System.currentTimeMillis() - hisIns.getStartTime().getTime(); + flowTask.setDuration(getDate(time)); + } + // 流程定义信息 + ProcessDefinition pd = repositoryService.createProcessDefinitionQuery() + .processDefinitionId(hisIns.getProcessDefinitionId()) + .singleResult(); + flowTask.setDeployId(pd.getDeploymentId()); + flowTask.setProcDefName(pd.getName()); + flowTask.setProcDefVersion(pd.getVersion()); + flowTask.setCategory(pd.getCategory()); + flowTask.setProcDefVersion(pd.getVersion()); + // 当前所处流程 + List taskList = taskService.createTaskQuery().processInstanceId(hisIns.getId()).list(); + if (CollectionUtils.isNotEmpty(taskList)) { + flowTask.setTaskId(taskList.get(0).getId()); + flowTask.setTaskName(taskList.get(0).getName()); + if (StringUtils.isNotBlank(taskList.get(0).getAssignee())) { + // 当前任务节点办理人信息 + SysUser sysUser = sysUserService.selectUserById(Long.parseLong(taskList.get(0).getAssignee())); + if (Objects.nonNull(sysUser)) { + flowTask.setAssigneeId(sysUser.getUserId()); + flowTask.setAssigneeName(sysUser.getNickName()); + flowTask.setAssigneeDeptName( + Objects.nonNull(sysUser.getDept()) ? sysUser.getDept().getDeptName() : ""); + } + } + } else { + List historicTaskInstance = historyService.createHistoricTaskInstanceQuery() + .processInstanceId(hisIns.getId()).orderByHistoricTaskInstanceEndTime().desc().list(); + flowTask.setTaskId(historicTaskInstance.get(0).getId()); + flowTask.setTaskName(historicTaskInstance.get(0).getName()); + if (StringUtils.isNotBlank(historicTaskInstance.get(0).getAssignee())) { + // 当前任务节点办理人信息 + SysUser sysUser = sysUserService + .selectUserById(Long.parseLong(historicTaskInstance.get(0).getAssignee())); + if (Objects.nonNull(sysUser)) { + flowTask.setAssigneeId(sysUser.getUserId()); + flowTask.setAssigneeName(sysUser.getNickName()); + flowTask.setAssigneeDeptName( + Objects.nonNull(sysUser.getDept()) ? sysUser.getDept().getDeptName() : ""); + } + } + } + flowList.add(flowTask); + } + + return flowList; + } + + /** + * 取消申请 + * 目前实现方式: 直接将当前流程变更为已完成 + * + * @param flowTaskVo + * @return + */ + @Override + public AjaxResult stopProcess(FlowTaskVo flowTaskVo) { + List task = taskService.createTaskQuery().processInstanceId(flowTaskVo.getInstanceId()).list(); + if (CollectionUtils.isEmpty(task)) { + throw new ServiceException("流程未启动或已执行完成,取消申请失败"); + } + // 获取当前流程实例 + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery() + .processInstanceId(flowTaskVo.getInstanceId()) + .singleResult(); + BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId()); + if (Objects.nonNull(bpmnModel)) { + Process process = bpmnModel.getMainProcess(); + List endNodes = process.findFlowElementsOfType(EndEvent.class, false); + if (CollectionUtils.isNotEmpty(endNodes)) { + SysUser loginUser = SecurityUtils.getLoginUser().getUser(); + Authentication.setAuthenticatedUserId(loginUser.getUserId().toString()); + taskService.addComment(null, processInstance.getProcessInstanceId(), + FlowComment.STOP.getType(), + StringUtils.isBlank(flowTaskVo.getComment()) ? "取消申请" : flowTaskVo.getComment()); + // 获取当前流程最后一个节点 + String endId = endNodes.get(0).getId(); + List executions = runtimeService.createExecutionQuery() + .parentId(processInstance.getProcessInstanceId()).list(); + List executionIds = new ArrayList<>(); + executions.forEach(execution -> executionIds.add(execution.getId())); + // 变更流程为已结束状态 + runtimeService.createChangeActivityStateBuilder() + .moveExecutionsToSingleActivityId(executionIds, endId).changeState(); + } + } + + return AjaxResult.success(); + } + + /** + * 撤回流程 目前存在错误 + * + * @param flowTaskVo + * @return + */ + @Override + public AjaxResult revokeProcess(FlowTaskVo flowTaskVo) { + Task task = taskService.createTaskQuery() + .processInstanceId(flowTaskVo.getInstanceId()) + .singleResult(); + if (task == null) { + throw new ServiceException("流程未启动或已执行完成,无法撤回"); + } + + SysUser loginUser = SecurityUtils.getLoginUser().getUser(); + List htiList = historyService.createHistoricTaskInstanceQuery() + .processInstanceId(task.getProcessInstanceId()) + .orderByTaskCreateTime() + .asc() + .list(); + String myTaskId = null; + for (HistoricTaskInstance hti : htiList) { + if (loginUser.getUserId().toString().equals(hti.getAssignee())) { + myTaskId = hti.getId(); + break; + } + } + if (null == myTaskId) { + throw new ServiceException("该任务非当前用户提交,无法撤回"); + } + List historicTaskInstanceList = historyService + .createHistoricTaskInstanceQuery() + .processInstanceId(task.getProcessInstanceId()) + .orderByHistoricTaskInstanceStartTime() + .asc() + .list(); + Iterator it = historicTaskInstanceList.iterator(); + // 循环节点,获取当前节点的上一节点的key + String tarKey = ""; + while (it.hasNext()) { + HistoricTaskInstance his = it.next(); + if (!task.getTaskDefinitionKey().equals(his.getTaskDefinitionKey())) { + tarKey = his.getTaskDefinitionKey(); + } + } + // 跳转节点 + runtimeService.createChangeActivityStateBuilder() + .processInstanceId(flowTaskVo.getInstanceId()) + .moveActivityIdTo(task.getTaskDefinitionKey(), tarKey) + .changeState(); + + return AjaxResult.success(); + } + + /** + * 代办任务列表 + * + * @param queryVo 请求参数 + * @return + */ + @Override + public List todoList(FlowQueryVo queryVo) { + SysUser sysUser = SecurityUtils.getLoginUser().getUser(); + TaskQuery taskQuery = taskService.createTaskQuery() + .active() + .includeProcessVariables() + .taskCandidateGroupIn(sysUser.getRoles().stream().map(role -> role.getRoleId().toString()) + .collect(Collectors.toList())) + .taskCandidateOrAssigned(sysUser.getUserId().toString()) + .orderByTaskCreateTime().desc(); + + if (StringUtils.isNotBlank(queryVo.getName())) { + String likePattern = "%" + queryVo.getName() + "%"; + taskQuery.processDefinitionNameLike(likePattern); + } + List taskList = taskQuery.listPage(queryVo.getPageSize() * (queryVo.getPageNum() - 1), + queryVo.getPageSize()); + List flowList = new ArrayList<>(); + for (Task task : taskList) { + FlowTaskDto flowTask = new FlowTaskDto(); + // 当前流程信息 + flowTask.setTaskId(task.getId()); + flowTask.setTaskDefKey(task.getTaskDefinitionKey()); + flowTask.setCreateTime(task.getCreateTime()); + flowTask.setProcDefId(task.getProcessDefinitionId()); + flowTask.setExecutionId(task.getExecutionId()); + flowTask.setTaskName(task.getName()); + // 流程定义信息 + ProcessDefinition pd = repositoryService.createProcessDefinitionQuery() + .processDefinitionId(task.getProcessDefinitionId()) + .singleResult(); + flowTask.setDeployId(pd.getDeploymentId()); + flowTask.setProcDefName(pd.getName()); + flowTask.setProcDefVersion(pd.getVersion()); + flowTask.setProcInsId(task.getProcessInstanceId()); + + // 流程发起人信息 + HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(task.getProcessInstanceId()) + .singleResult(); + SysUser startUser = sysUserService.selectUserById(Long.parseLong(historicProcessInstance.getStartUserId())); + flowTask.setStartUserId(startUser.getUserId().toString()); + flowTask.setStartUserName(startUser.getNickName()); + flowTask.setStartDeptName(Objects.nonNull(startUser.getDept()) ? startUser.getDept().getDeptName() : ""); + flowList.add(flowTask); + } + return flowList; + } + + /** + * 已办任务列表 + * + * @param queryVo 请求参数 + * @return + */ + @Override + public List finishedList(FlowQueryVo queryVo) { + // List page = new ArrayList<>(); + Long userId = SecurityUtils.getLoginUser().getUser().getUserId(); + HistoricTaskInstanceQuery taskInstanceQuery = historyService.createHistoricTaskInstanceQuery() + .includeProcessVariables() + .finished() + .taskAssignee(userId.toString()) + .orderByHistoricTaskInstanceEndTime() + .desc(); + List historicTaskInstanceList = taskInstanceQuery + .listPage(queryVo.getPageSize() * (queryVo.getPageNum() - 1), queryVo.getPageSize()); + List hisTaskList = new ArrayList<>(); + for (HistoricTaskInstance histTask : historicTaskInstanceList) { + FlowTaskDto flowTask = new FlowTaskDto(); + // 当前流程信息 + flowTask.setTaskId(histTask.getId()); + // 审批人员信息 + flowTask.setCreateTime(histTask.getCreateTime()); + flowTask.setFinishTime(histTask.getEndTime()); + flowTask.setDuration(getDate(histTask.getDurationInMillis())); + flowTask.setProcDefId(histTask.getProcessDefinitionId()); + flowTask.setTaskDefKey(histTask.getTaskDefinitionKey()); + flowTask.setTaskName(histTask.getName()); + + // 流程定义信息 + ProcessDefinition pd = repositoryService.createProcessDefinitionQuery() + .processDefinitionId(histTask.getProcessDefinitionId()) + .singleResult(); + flowTask.setDeployId(pd.getDeploymentId()); + flowTask.setProcDefName(pd.getName()); + flowTask.setProcDefVersion(pd.getVersion()); + flowTask.setProcInsId(histTask.getProcessInstanceId()); + flowTask.setHisProcInsId(histTask.getProcessInstanceId()); + + // 流程发起人信息 + HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(histTask.getProcessInstanceId()) + .singleResult(); + SysUser startUser = sysUserService.selectUserById(Long.parseLong(historicProcessInstance.getStartUserId())); + flowTask.setStartUserId(startUser.getNickName()); + flowTask.setStartUserName(startUser.getNickName()); + flowTask.setStartDeptName(Objects.nonNull(startUser.getDept()) ? startUser.getDept().getDeptName() : ""); + hisTaskList.add(flowTask); + } + return hisTaskList; + } + + // private static Predicate distinctByKey(Function + // keyExtractor) { + // Set seen = ConcurrentHashMap.newKeySet(); + // return t -> seen.add(keyExtractor.apply(t)); + // } + + /** + * 流程历史流转记录 + * + * @param procInsId 流程实例Id + * @return + */ + @Override + public AjaxResult flowRecord(String procInsId, String deployId) { + Map map = new HashMap(); + if (StringUtils.isNotBlank(procInsId)) { + List list = historyService + .createHistoricActivityInstanceQuery() + .processInstanceId(procInsId) + .orderByHistoricActivityInstanceStartTime() + .desc().list(); + List hisFlowList = new ArrayList<>(); + for (HistoricActivityInstance histIns : list) { + // 展示开始节点 + // if ("startEvent".equals(histIns.getActivityType())) { + // FlowTaskDto flowTask = new FlowTaskDto(); + // // 流程发起人信息 + // HistoricProcessInstance historicProcessInstance = + // historyService.createHistoricProcessInstanceQuery() + // .processInstanceId(histIns.getProcessInstanceId()) + // .singleResult(); + // SysUser startUser = + // sysUserService.selectUserById(Long.parseLong(historicProcessInstance.getStartUserId())); + // flowTask.setTaskName(startUser.getNickName() + "(" + + // startUser.getDept().getDeptName() + ")发起申请"); + // flowTask.setFinishTime(histIns.getEndTime()); + // hisFlowList.add(flowTask); + // } else if ("endEvent".equals(histIns.getActivityType())) { + // FlowTaskDto flowTask = new FlowTaskDto(); + // flowTask.setTaskName(StringUtils.isNotBlank(histIns.getActivityName()) ? + // histIns.getActivityName() : "结束"); + // flowTask.setFinishTime(histIns.getEndTime()); + // hisFlowList.add(flowTask); + // } else + if (StringUtils.isNotBlank(histIns.getTaskId())) { + FlowTaskDto flowTask = new FlowTaskDto(); + flowTask.setTaskId(histIns.getTaskId()); + flowTask.setTaskName(histIns.getActivityName()); + flowTask.setCreateTime(histIns.getStartTime()); + flowTask.setFinishTime(histIns.getEndTime()); + if (StringUtils.isNotBlank(histIns.getAssignee())) { + SysUser sysUser = sysUserService.selectUserById(Long.parseLong(histIns.getAssignee())); + flowTask.setAssigneeId(sysUser.getUserId()); + flowTask.setAssigneeName(sysUser.getNickName()); + flowTask.setDeptName(Objects.nonNull(sysUser.getDept()) ? sysUser.getDept().getDeptName() : ""); + } + // 展示审批人员 + List linksForTask = historyService + .getHistoricIdentityLinksForTask(histIns.getTaskId()); + StringBuilder stringBuilder = new StringBuilder(); + for (HistoricIdentityLink identityLink : linksForTask) { + // 获选人,候选组/角色(多个) + if ("candidate".equals(identityLink.getType())) { + if (StringUtils.isNotBlank(identityLink.getUserId())) { + SysUser sysUser = sysUserService + .selectUserById(Long.parseLong(identityLink.getUserId())); + stringBuilder.append(sysUser.getNickName()).append(","); + } + if (StringUtils.isNotBlank(identityLink.getGroupId())) { + SysRole sysRole = sysRoleService + .selectRoleById(Long.parseLong(identityLink.getGroupId())); + stringBuilder.append(sysRole.getRoleName()).append(","); + } + } + } + if (StringUtils.isNotBlank(stringBuilder)) { + flowTask.setCandidate(stringBuilder.substring(0, stringBuilder.length() - 1)); + } + + flowTask.setDuration( + histIns.getDurationInMillis() == null || histIns.getDurationInMillis() == 0 ? null + : getDate(histIns.getDurationInMillis())); + // 获取意见评论内容 + List commentList = taskService.getProcessInstanceComments(histIns.getProcessInstanceId()); + commentList.forEach(comment -> { + if (histIns.getTaskId().equals(comment.getTaskId())) { + flowTask.setComment(FlowCommentDto.builder().type(comment.getType()) + .comment(comment.getFullMessage()).build()); + } + }); + hisFlowList.add(flowTask); + } + } + map.put("flowList", hisFlowList); + } + // 第一次申请获取初始化表单 + if (StringUtils.isNotBlank(deployId)) { + FormTemplate sysForm = sysInstanceFormService.selectSysDeployFormByDeployId(deployId); + if (Objects.isNull(sysForm)) { + return AjaxResult.error("请先配置流程表单"); + } + map.put("formData", JSONObject.parseObject(sysForm.getFormSchema())); + } + return AjaxResult.success(map); + } + + /** + * 根据任务ID查询挂载的表单信息 + * + * @param taskId 任务Id + * @return + */ + @Override + public AjaxResult getTaskForm(String taskId) { + Task task = taskService.createTaskQuery().taskId(taskId).singleResult(); + FormTemplate sysForm = formTemplateService.selectFormTemplateByFormId(Long.parseLong(task.getFormKey())); + return AjaxResult.success(sysForm.getFormSchema()); + } + + /** + * 获取流程过程图 + * + * @param processId + * @return + */ + @Override + public InputStream diagram(String processId) { + String processDefinitionId; + // 获取当前的流程实例 + ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processId) + .singleResult(); + // 如果流程已经结束,则得到结束节点 + if (Objects.isNull(processInstance)) { + HistoricProcessInstance pi = historyService.createHistoricProcessInstanceQuery() + .processInstanceId(processId).singleResult(); + + processDefinitionId = pi.getProcessDefinitionId(); + } else {// 如果流程没有结束,则取当前活动节点 + // 根据流程实例ID获得当前处于活动状态的ActivityId合集 + ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(processId) + .singleResult(); + processDefinitionId = pi.getProcessDefinitionId(); + } + + // 获得活动的节点 + List highLightedFlowList = historyService.createHistoricActivityInstanceQuery() + .processInstanceId(processId).orderByHistoricActivityInstanceStartTime().asc().list(); + + List highLightedFlows = new ArrayList<>(); + List highLightedNodes = new ArrayList<>(); + // 高亮线 + for (HistoricActivityInstance tempActivity : highLightedFlowList) { + if ("sequenceFlow".equals(tempActivity.getActivityType())) { + // 高亮线 + highLightedFlows.add(tempActivity.getActivityId()); + } else { + // 高亮节点 + highLightedNodes.add(tempActivity.getActivityId()); + } + } + + // 获取流程图 + BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId); + ProcessEngineConfiguration configuration = processEngine.getProcessEngineConfiguration(); + // 获取自定义图片生成器 + ProcessDiagramGenerator diagramGenerator = new CustomProcessDiagramGenerator(); + InputStream in = diagramGenerator.generateDiagram(bpmnModel, "png", highLightedNodes, highLightedFlows, + configuration.getActivityFontName(), + configuration.getLabelFontName(), configuration.getAnnotationFontName(), configuration.getClassLoader(), + 1.0, true); + return in; + + } + + /** + * 获取流程执行节点 + * + * @param procInsId 流程实例id + * @return + */ + @Override + public AjaxResult getFlowViewer(String procInsId, String executionId) { + List flowViewerList = new ArrayList<>(); + FlowViewerDto flowViewerDto; + // 获取任务开始节点(临时处理方式) + List startNodeList = historyService.createHistoricActivityInstanceQuery() + .processInstanceId(procInsId) + .orderByHistoricActivityInstanceStartTime() + .asc().listPage(0, 3); + for (HistoricActivityInstance startInstance : startNodeList) { + if (!"sequenceFlow".equals(startInstance.getActivityType())) { + flowViewerDto = new FlowViewerDto(); + if (!"sequenceFlow".equals(startInstance.getActivityType())) { + flowViewerDto.setKey(startInstance.getActivityId()); + // 根据流程节点处理时间校验改节点是否已完成 + flowViewerDto.setCompleted(!Objects.isNull(startInstance.getEndTime())); + flowViewerList.add(flowViewerDto); + } + } + } + // 历史节点 + List hisActIns = historyService.createHistoricActivityInstanceQuery() + .executionId(executionId) + .orderByHistoricActivityInstanceStartTime() + .asc().list(); + for (HistoricActivityInstance activityInstance : hisActIns) { + if (!"sequenceFlow".equals(activityInstance.getActivityType())) { + flowViewerDto = new FlowViewerDto(); + flowViewerDto.setKey(activityInstance.getActivityId()); + // 根据流程节点处理时间校验改节点是否已完成 + flowViewerDto.setCompleted(!Objects.isNull(activityInstance.getEndTime())); + flowViewerList.add(flowViewerDto); + } + } + return AjaxResult.success(flowViewerList); + } + + /** + * 获取流程变量 + * + * @param taskId + * @return + */ + @Override + public AjaxResult processVariables(String taskId) { + // 流程变量 + HistoricTaskInstance historicTaskInstance = historyService.createHistoricTaskInstanceQuery() + .includeProcessVariables().finished().taskId(taskId).singleResult(); + if (Objects.nonNull(historicTaskInstance)) { + return AjaxResult.success(historicTaskInstance.getProcessVariables()); + } else { + Map variables = taskService.getVariables(taskId); + return AjaxResult.success(variables); + } + } + + /** + * 审批任务获取下一节点 + * + * @param flowTaskVo 任务 + * @return + */ + @Override + public AjaxResult getNextFlowNode(FlowTaskVo flowTaskVo) { + // Step 1. 获取当前节点并找到下一步节点 + Task task = taskService.createTaskQuery().taskId(flowTaskVo.getTaskId()).singleResult(); + if (Objects.isNull(task)) { + return AjaxResult.error("任务不存在或已被审批!"); + } + // Step 2. 获取当前流程所有流程变量(网关节点时需要校验表达式) + Map variables = taskService.getVariables(task.getId()); + List nextUserTask = FindNextNodeUtil.getNextUserTasks(repositoryService, task, variables); + if (CollectionUtils.isEmpty(nextUserTask)) { + return AjaxResult.success("流程已完结!", null); + } + return getFlowAttribute(nextUserTask); + } + + /** + * 发起流程获取下一节点 + * + * @param flowTaskVo 任务 + * @return + */ + @Override + public AjaxResult getNextFlowNodeByStart(FlowTaskVo flowTaskVo) { + // Step 1. 查找流程定义信息 + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() + .deploymentId(flowTaskVo.getDeploymentId()).singleResult(); + if (Objects.isNull(processDefinition)) { + return AjaxResult.error("流程信息不存在!"); + } + // Step 2. 获取下一任务节点(网关节点时需要校验表达式) + List nextUserTask = FindNextNodeUtil.getNextUserTasksByStart(repositoryService, processDefinition, + flowTaskVo.getVariables()); + if (CollectionUtils.isEmpty(nextUserTask)) { + return AjaxResult.error("暂未查找到下一任务,请检查流程设计是否正确!"); + } + return getFlowAttribute(nextUserTask); + } + + /** + * 获取任务节点属性,包含自定义属性等 + * + * @param nextUserTask + */ + private AjaxResult getFlowAttribute(List nextUserTask) { + FlowNextDto flowNextDto = new FlowNextDto(); + for (UserTask userTask : nextUserTask) { + MultiInstanceLoopCharacteristics multiInstance = userTask.getLoopCharacteristics(); + // 会签节点 + if (Objects.nonNull(multiInstance)) { + flowNextDto.setVars(multiInstance.getInputDataItem()); + flowNextDto.setType(ProcessConstants.PROCESS_MULTI_INSTANCE); + flowNextDto.setDataType(ProcessConstants.DYNAMIC); + } else { + // 读取自定义节点属性 判断是否是否需要动态指定任务接收人员、组 + String dataType = userTask.getAttributeValue(ProcessConstants.NAMASPASE, + ProcessConstants.PROCESS_CUSTOM_DATA_TYPE); + String userType = userTask.getAttributeValue(ProcessConstants.NAMASPASE, + ProcessConstants.PROCESS_CUSTOM_USER_TYPE); + flowNextDto.setVars(ProcessConstants.PROCESS_APPROVAL); + flowNextDto.setType(userType); + flowNextDto.setDataType(dataType); + } + } + return AjaxResult.success(flowNextDto); + } + + /** + * 流程初始化表单 + * + * @param deployId + * @return + */ + @Override + public AjaxResult flowFormData(String deployId) { + // 第一次申请获取初始化表单 + if (StringUtils.isNotBlank(deployId)) { + FormTemplate sysForm = sysInstanceFormService.selectSysDeployFormByDeployId(deployId); + if (Objects.isNull(sysForm)) { + return AjaxResult.error("请先配置流程表单!"); + } + return AjaxResult.success(JSONObject.parseObject(sysForm.getFormSchema())); + } else { + return AjaxResult.error("参数错误!"); + } + } + + /** + * 流程节点信息 + * + * @param procInsId + * @return + */ + @Override + public AjaxResult flowXmlAndNode(String procInsId, String deployId) { + try { + List flowViewerList = new ArrayList<>(); + // 获取已经完成的节点 + List listFinished = historyService.createHistoricActivityInstanceQuery() + .processInstanceId(procInsId) + .finished() + .list(); + + // 保存已经完成的流程节点编号 + listFinished.forEach(s -> { + FlowViewerDto flowViewerDto = new FlowViewerDto(); + flowViewerDto.setKey(s.getActivityId()); + flowViewerDto.setCompleted(true); + // 退回节点不进行展示 + if (StringUtils.isBlank(s.getDeleteReason())) { + flowViewerList.add(flowViewerDto); + } + }); + + // 获取代办节点 + List listUnFinished = historyService.createHistoricActivityInstanceQuery() + .processInstanceId(procInsId) + .unfinished() + .list(); + + // 保存需要代办的节点编号 + listUnFinished.forEach(s -> { + // 删除已退回节点 + flowViewerList.removeIf(task -> task.getKey().equals(s.getActivityId())); + FlowViewerDto flowViewerDto = new FlowViewerDto(); + flowViewerDto.setKey(s.getActivityId()); + flowViewerDto.setCompleted(false); + flowViewerList.add(flowViewerDto); + }); + Map result = new HashMap<>(); + // xmlData 数据 + ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().deploymentId(deployId) + .singleResult(); + InputStream inputStream = repositoryService.getResourceAsStream(definition.getDeploymentId(), + definition.getResourceName()); + String xmlData = IOUtils.toString(inputStream, StandardCharsets.UTF_8); + result.put("nodeData", flowViewerList); + result.put("xmlData", xmlData); + return AjaxResult.success(result); + } catch (Exception e) { + return AjaxResult.error("高亮历史任务失败"); + } + } + + /** + * 流程节点表单 + * + * @param taskId 流程任务编号 + * @return + */ + @Override + public AjaxResult flowTaskForm(String taskId) throws Exception { + Task task = taskService.createTaskQuery().taskId(taskId).singleResult(); + // 流程变量 + Map parameters = new HashMap<>(); + HistoricTaskInstance historicTaskInstance = historyService.createHistoricTaskInstanceQuery() + .includeProcessVariables().finished().taskId(taskId).singleResult(); + if (Objects.nonNull(historicTaskInstance)) { + parameters = historicTaskInstance.getProcessVariables(); + } else { + parameters = taskService.getVariables(taskId); + } + JSONObject oldVariables = JSONObject.parseObject(JSON.toJSONString(parameters.get("formJson"))); + List oldFields = JSON.parseObject(JSON.toJSONString(oldVariables.get("widgetList")), + new TypeReference>() { + }); + // 设置已填写的表单为禁用状态 + for (JSONObject oldField : oldFields) { + JSONObject options = oldField.getJSONObject("options"); + options.put("disabled", true); + } + // 暂时只处理用户任务上的表单 + if (StringUtils.isNotBlank(task.getFormKey())) { + FormTemplate sysForm = formTemplateService.selectFormTemplateByFormId(Long.parseLong(task.getFormKey())); + if (Objects.isNull(sysForm)) { + throw new ServiceException("当前流程配置的表单不存在或已被删除"); + } + JSONObject data = JSONObject.parseObject(sysForm.getFormSchema()); + List newFields = JSON.parseObject( + JSON.toJSONString(data.get("widgetList")), + new TypeReference>() { + }); + // 表单回显时 加入子表单信息到流程变量中 + for (JSONObject newField : newFields) { + String key = newField.getString("id"); + // 处理图片上传组件回显问题 + if ("picture-upload".equals(newField.getString("type"))) { + parameters.put(key, new ArrayList<>()); + } else { + parameters.put(key, null); + } + } + oldFields.addAll(newFields); + } + oldVariables.put("widgetList", oldFields); + parameters.put("formJson", oldVariables); + return AjaxResult.success(parameters); + } + + /** + * 流程节点信息 + * + * @param procInsId + * @param elementId + * @return + */ + @Override + public AjaxResult flowTaskInfo(String procInsId, String elementId) { + List list = historyService.createHistoricActivityInstanceQuery() + .processInstanceId(procInsId) + .activityId(elementId) + .list(); + // 退回任务后有多条数据 只取待办任务进行展示 + list.removeIf(task -> StringUtils.isNotBlank(task.getDeleteReason())); + if (CollectionUtils.isEmpty(list)) { + return AjaxResult.success(); + } + if (list.size() > 1) { + list.removeIf(task -> Objects.nonNull(task.getEndTime())); + } + HistoricActivityInstance histIns = list.get(0); + FlowTaskDto flowTask = new FlowTaskDto(); + flowTask.setTaskId(histIns.getTaskId()); + flowTask.setTaskName(histIns.getActivityName()); + flowTask.setCreateTime(histIns.getStartTime()); + flowTask.setFinishTime(histIns.getEndTime()); + if (StringUtils.isNotBlank(histIns.getAssignee())) { + SysUser sysUser = sysUserService.selectUserById(Long.parseLong(histIns.getAssignee())); + flowTask.setAssigneeId(sysUser.getUserId()); + flowTask.setAssigneeName(sysUser.getNickName()); + flowTask.setDeptName(Objects.nonNull(sysUser.getDept()) ? sysUser.getDept().getDeptName() : ""); + + } + // 流程变量信息 + // HistoricTaskInstance historicTaskInstance = + // historyService.createHistoricTaskInstanceQuery() + // .includeProcessVariables().finished().taskId(histIns.getTaskId()).singleResult(); + // flowTask.setVariables(historicTaskInstance.getProcessVariables()); + + // 展示审批人员 + List linksForTask = historyService.getHistoricIdentityLinksForTask(histIns.getTaskId()); + StringBuilder stringBuilder = new StringBuilder(); + for (HistoricIdentityLink identityLink : linksForTask) { + // 获选人,候选组/角色(多个) + if ("candidate".equals(identityLink.getType())) { + if (StringUtils.isNotBlank(identityLink.getUserId())) { + SysUser sysUser = sysUserService.selectUserById(Long.parseLong(identityLink.getUserId())); + stringBuilder.append(sysUser.getNickName()).append(","); + } + if (StringUtils.isNotBlank(identityLink.getGroupId())) { + SysRole sysRole = sysRoleService.selectRoleById(Long.parseLong(identityLink.getGroupId())); + stringBuilder.append(sysRole.getRoleName()).append(","); + } + } + } + if (StringUtils.isNotBlank(stringBuilder)) { + flowTask.setCandidate(stringBuilder.substring(0, stringBuilder.length() - 1)); + } + + flowTask.setDuration(histIns.getDurationInMillis() == null || histIns.getDurationInMillis() == 0 ? null + : getDate(histIns.getDurationInMillis())); + // 获取意见评论内容 + List commentList = taskService.getProcessInstanceComments(histIns.getProcessInstanceId()); + commentList.forEach(comment -> { + if (histIns.getTaskId().equals(comment.getTaskId())) { + flowTask.setComment( + FlowCommentDto.builder().type(comment.getType()).comment(comment.getFullMessage()).build()); + } + }); + return AjaxResult.success(flowTask); + } + + /** + * 将Object类型的数据转化成Map + * + * @param obj + * @return + * @throws Exception + */ + public Map obj2Map(Object obj) throws Exception { + Map map = new HashMap(); + Field[] fields = obj.getClass().getDeclaredFields(); + for (Field field : fields) { + field.setAccessible(true); + map.put(field.getName(), field.get(obj)); + } + return map; + } + + /** + * 流程完成时间处理 + * + * @param ms + * @return + */ + private String getDate(long ms) { + + long day = ms / (24 * 60 * 60 * 1000); + long hour = (ms / (60 * 60 * 1000) - day * 24); + long minute = ((ms / (60 * 1000)) - day * 24 * 60 - hour * 60); + long second = (ms / 1000 - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60); + + if (day > 0) { + return day + "天" + hour + "小时" + minute + "分钟"; + } + if (hour > 0) { + return hour + "小时" + minute + "分钟"; + } + if (minute > 0) { + return minute + "分钟"; + } + if (second > 0) { + return second + "秒"; + } else { + return 0 + "秒"; + } + } +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/impl/SysDeployFormServiceImpl.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/impl/SysDeployFormServiceImpl.java new file mode 100644 index 0000000..fe581b6 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/impl/SysDeployFormServiceImpl.java @@ -0,0 +1,106 @@ +package com.boyue.flowable.service.impl; + +import java.util.List; +import java.util.Objects; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.boyue.flowable.domain.SysDeployForm; +import com.boyue.flowable.mapper.SysDeployFormMapper; +import com.boyue.flowable.service.ISysDeployFormService; +import com.boyue.form.domain.FormTemplate; + +/** + * 流程实例关联表单Service业务层处理 + * + * @author Tony + * @date 2021-04-03 + */ +@Service +public class SysDeployFormServiceImpl implements ISysDeployFormService { + @Autowired + private SysDeployFormMapper sysDeployFormMapper; + + /** + * 查询流程实例关联表单 + * + * @param id 流程实例关联表单ID + * @return 流程实例关联表单 + */ + @Override + public SysDeployForm selectSysDeployFormById(Long id) { + return sysDeployFormMapper.selectSysDeployFormById(id); + } + + /** + * 查询流程实例关联表单列表 + * + * @param sysDeployForm 流程实例关联表单 + * @return 流程实例关联表单 + */ + @Override + public List selectSysDeployFormList(SysDeployForm sysDeployForm) { + return sysDeployFormMapper.selectSysDeployFormList(sysDeployForm); + } + + /** + * 新增流程实例关联表单 + * + * @param sysDeployForm 流程实例关联表单 + * @return 结果 + */ + @Override + public int insertSysDeployForm(SysDeployForm sysDeployForm) { + FormTemplate sysForm = sysDeployFormMapper.selectSysDeployFormByDeployId(sysDeployForm.getDeployId()); + if (Objects.isNull(sysForm)) { + return sysDeployFormMapper.insertSysDeployForm(sysDeployForm); + } else { + return updateSysDeployForm(sysDeployForm); + } + } + + /** + * 修改流程实例关联表单 + * + * @param sysDeployForm 流程实例关联表单 + * @return 结果 + */ + @Override + public int updateSysDeployForm(SysDeployForm sysDeployForm) { + return sysDeployFormMapper.updateSysDeployForm(sysDeployForm); + } + + /** + * 批量删除流程实例关联表单 + * + * @param ids 需要删除的流程实例关联表单ID + * @return 结果 + */ + @Override + public int deleteSysDeployFormByIds(Long[] ids) { + return sysDeployFormMapper.deleteSysDeployFormByIds(ids); + } + + /** + * 删除流程实例关联表单信息 + * + * @param id 流程实例关联表单ID + * @return 结果 + */ + @Override + public int deleteSysDeployFormById(Long id) { + return sysDeployFormMapper.deleteSysDeployFormById(id); + } + + /** + * 查询流程挂着的表单 + * + * @param deployId + * @return + */ + @Override + public FormTemplate selectSysDeployFormByDeployId(String deployId) { + return sysDeployFormMapper.selectSysDeployFormByDeployId(deployId); + } +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/impl/SysExpressionServiceImpl.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/impl/SysExpressionServiceImpl.java new file mode 100644 index 0000000..b7559f4 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/impl/SysExpressionServiceImpl.java @@ -0,0 +1,98 @@ +package com.boyue.flowable.service.impl; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.boyue.common.utils.DateUtils; +import com.boyue.flowable.domain.SysExpression; +import com.boyue.flowable.mapper.SysExpressionMapper; +import com.boyue.flowable.service.ISysExpressionService; + +/** + * 流程达式Service业务层处理 + * + * @author boyue + * @date 2022-12-12 + */ +@Service +public class SysExpressionServiceImpl implements ISysExpressionService +{ + @Autowired + private SysExpressionMapper sysExpressionMapper; + + /** + * 查询流程达式 + * + * @param id 流程达式主键 + * @return 流程达式 + */ + @Override + public SysExpression selectSysExpressionById(Long id) + { + return sysExpressionMapper.selectSysExpressionById(id); + } + + /** + * 查询流程达式列表 + * + * @param sysExpression 流程达式 + * @return 流程达式 + */ + @Override + public List selectSysExpressionList(SysExpression sysExpression) + { + return sysExpressionMapper.selectSysExpressionList(sysExpression); + } + + /** + * 新增流程达式 + * + * @param sysExpression 流程达式 + * @return 结果 + */ + @Override + public int insertSysExpression(SysExpression sysExpression) + { + sysExpression.setCreateTime(DateUtils.getNowDate()); + return sysExpressionMapper.insertSysExpression(sysExpression); + } + + /** + * 修改流程达式 + * + * @param sysExpression 流程达式 + * @return 结果 + */ + @Override + public int updateSysExpression(SysExpression sysExpression) + { + sysExpression.setUpdateTime(DateUtils.getNowDate()); + return sysExpressionMapper.updateSysExpression(sysExpression); + } + + /** + * 批量删除流程达式 + * + * @param ids 需要删除的流程达式主键 + * @return 结果 + */ + @Override + public int deleteSysExpressionByIds(Long[] ids) + { + return sysExpressionMapper.deleteSysExpressionByIds(ids); + } + + /** + * 删除流程达式信息 + * + * @param id 流程达式主键 + * @return 结果 + */ + @Override + public int deleteSysExpressionById(Long id) + { + return sysExpressionMapper.deleteSysExpressionById(id); + } +} diff --git a/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/impl/SysListenerServiceImpl.java b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/impl/SysListenerServiceImpl.java new file mode 100644 index 0000000..d961ba8 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/java/com/boyue/flowable/service/impl/SysListenerServiceImpl.java @@ -0,0 +1,98 @@ +package com.boyue.flowable.service.impl; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.boyue.common.utils.DateUtils; +import com.boyue.flowable.domain.SysListener; +import com.boyue.flowable.mapper.SysListenerMapper; +import com.boyue.flowable.service.ISysListenerService; + +/** + * 流程监听Service业务层处理 + * + * @author Tony + * @date 2022-12-25 + */ +@Service +public class SysListenerServiceImpl implements ISysListenerService +{ + @Autowired + private SysListenerMapper sysListenerMapper; + + /** + * 查询流程监听 + * + * @param id 流程监听主键 + * @return 流程监听 + */ + @Override + public SysListener selectSysListenerById(Long id) + { + return sysListenerMapper.selectSysListenerById(id); + } + + /** + * 查询流程监听列表 + * + * @param sysListener 流程监听 + * @return 流程监听 + */ + @Override + public List selectSysListenerList(SysListener sysListener) + { + return sysListenerMapper.selectSysListenerList(sysListener); + } + + /** + * 新增流程监听 + * + * @param sysListener 流程监听 + * @return 结果 + */ + @Override + public int insertSysListener(SysListener sysListener) + { + sysListener.setCreateTime(DateUtils.getNowDate()); + return sysListenerMapper.insertSysListener(sysListener); + } + + /** + * 修改流程监听 + * + * @param sysListener 流程监听 + * @return 结果 + */ + @Override + public int updateSysListener(SysListener sysListener) + { + sysListener.setUpdateTime(DateUtils.getNowDate()); + return sysListenerMapper.updateSysListener(sysListener); + } + + /** + * 批量删除流程监听 + * + * @param ids 需要删除的流程监听主键 + * @return 结果 + */ + @Override + public int deleteSysListenerByIds(Long[] ids) + { + return sysListenerMapper.deleteSysListenerByIds(ids); + } + + /** + * 删除流程监听信息 + * + * @param id 流程监听主键 + * @return 结果 + */ + @Override + public int deleteSysListenerById(Long id) + { + return sysListenerMapper.deleteSysListenerById(id); + } +} diff --git a/boyue-models/boyue-flowable/src/main/resources/mapper/flowable/FlowDeployMapper.xml b/boyue-models/boyue-flowable/src/main/resources/mapper/flowable/FlowDeployMapper.xml new file mode 100644 index 0000000..b618a7f --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/resources/mapper/flowable/FlowDeployMapper.xml @@ -0,0 +1,31 @@ + + + + + + + + + \ No newline at end of file diff --git a/boyue-models/boyue-flowable/src/main/resources/mapper/flowable/SysDeployFormMapper.xml b/boyue-models/boyue-flowable/src/main/resources/mapper/flowable/SysDeployFormMapper.xml new file mode 100644 index 0000000..7808479 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/resources/mapper/flowable/SysDeployFormMapper.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + select id, form_id, deploy_id from sys_deploy_form + + + + + + + + + + insert into sys_deploy_form + + form_id, + deploy_id, + + + #{formId}, + #{deployId}, + + + + + update sys_deploy_form + + + form_id = #{formId}, + deploy_id = #{deployId}, + + where id = #{id} + + + + form_id = #{formId}, + + where deploy_id = #{deployId} + + + + + delete from sys_deploy_form where id = #{id} + + + + delete from sys_deploy_form where id in + + #{id} + + + \ No newline at end of file diff --git a/boyue-models/boyue-flowable/src/main/resources/mapper/flowable/SysExpressionMapper.xml b/boyue-models/boyue-flowable/src/main/resources/mapper/flowable/SysExpressionMapper.xml new file mode 100644 index 0000000..30e011b --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/resources/mapper/flowable/SysExpressionMapper.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + select id, name, expression, data_type,create_time, update_time, create_by, update_by, status, remark from sys_expression + + + + + + + + insert into sys_expression + + name, + expression, + data_type, + create_time, + update_time, + create_by, + update_by, + status, + remark, + + + #{name}, + #{expression}, + #{dataType}, + #{createTime}, + #{updateTime}, + #{createBy}, + #{updateBy}, + #{status}, + #{remark}, + + + + + update sys_expression + + name = #{name}, + expression = #{expression}, + data_type = #{dataType}, + create_time = #{createTime}, + update_time = #{updateTime}, + create_by = #{createBy}, + update_by = #{updateBy}, + status = #{status}, + remark = #{remark}, + + where id = #{id} + + + + delete from sys_expression where id = #{id} + + + + delete from sys_expression where id in + + #{id} + + + diff --git a/boyue-models/boyue-flowable/src/main/resources/mapper/flowable/SysListenerMapper.xml b/boyue-models/boyue-flowable/src/main/resources/mapper/flowable/SysListenerMapper.xml new file mode 100644 index 0000000..ef5f836 --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/resources/mapper/flowable/SysListenerMapper.xml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + select id, + name, + type, + event_type, + value_type, + value, + create_time, + update_time, + create_by, + update_by, + status, + remark + from sys_listener + + + + + + + + insert into sys_listener + + name, + type, + event_type, + value_type, + value, + create_time, + update_time, + create_by, + update_by, + status, + remark, + + + #{name}, + #{type}, + #{eventType}, + #{valueType}, + #{value}, + #{createTime}, + #{updateTime}, + #{createBy}, + #{updateBy}, + #{status}, + #{remark}, + + + + + update sys_listener + + name = #{name}, + type = #{type}, + event_type = #{eventType}, + value_type = #{valueType}, + value = #{value}, + create_time = #{createTime}, + update_time = #{updateTime}, + create_by = #{createBy}, + update_by = #{updateBy}, + status = #{status}, + remark = #{remark}, + + where id = #{id} + + + + delete + from sys_listener + where id = #{id} + + + + delete from sys_listener where id in + + #{id} + + + \ No newline at end of file diff --git a/boyue-models/boyue-flowable/src/main/resources/mapper/flowable/SysTaskFormMapper.xml b/boyue-models/boyue-flowable/src/main/resources/mapper/flowable/SysTaskFormMapper.xml new file mode 100644 index 0000000..e525c2e --- /dev/null +++ b/boyue-models/boyue-flowable/src/main/resources/mapper/flowable/SysTaskFormMapper.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + select id, form_id, task_id from sys_task_form + + + + + + + + insert into sys_task_form + + form_id, + task_id, + + + #{formId}, + #{taskId}, + + + + + update sys_task_form + + form_id = #{formId}, + task_id = #{taskId}, + + where id = #{id} + + + + delete from sys_task_form where id = #{id} + + + + delete from sys_task_form where id in + + #{id} + + + \ No newline at end of file diff --git a/boyue-models/boyue-form/pom.xml b/boyue-models/boyue-form/pom.xml new file mode 100644 index 0000000..075dbf3 --- /dev/null +++ b/boyue-models/boyue-form/pom.xml @@ -0,0 +1,27 @@ + + + + boyue-models + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-form + + + form表单 + + + + + + + com.boyue + boyue-common + + + + + \ No newline at end of file diff --git a/boyue-models/boyue-form/src/main/java/com/boyue/form/controller/FormDataController.java b/boyue-models/boyue-form/src/main/java/com/boyue/form/controller/FormDataController.java new file mode 100644 index 0000000..fd5ac59 --- /dev/null +++ b/boyue-models/boyue-form/src/main/java/com/boyue/form/controller/FormDataController.java @@ -0,0 +1,118 @@ +package com.boyue.form.controller; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.enums.BusinessType; +import com.boyue.common.utils.poi.ExcelUtil; +import com.boyue.form.domain.FormData; +import com.boyue.form.service.IFormDataService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 单数据Controller + * + * @author boyue + * @date 2025-05-12 + */ +@RestController +@RequestMapping("/form/data") +@Tag(name = "【单数据】管理") +public class FormDataController extends BaseController +{ + @Autowired + private IFormDataService formDataService; + + /** + * 查询单数据列表 + */ + @Operation(summary = "查询单数据列表") + @PreAuthorize("@ss.hasPermi('form:data:list')") + @GetMapping("/list") + public TableDataInfo list(FormData formData) + { + startPage(); + List list = formDataService.selectFormDataList(formData); + return getDataTable(list); + } + + /** + * 导出单数据列表 + */ + @Operation(summary = "导出单数据列表") + @PreAuthorize("@ss.hasPermi('form:data:export')") + @Log(title = "单数据", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, FormData formData) + { + List list = formDataService.selectFormDataList(formData); + ExcelUtil util = new ExcelUtil(FormData.class); + util.exportExcel(response, list, "单数据数据"); + } + + /** + * 获取单数据详细信息 + */ + @Operation(summary = "获取单数据详细信息") + @PreAuthorize("@ss.hasPermi('form:data:query')") + @GetMapping(value = "/{dataId}") + public AjaxResult getInfo(@PathVariable("dataId") Long dataId) + { + return success(formDataService.selectFormDataByDataId(dataId)); + } + + /** + * 新增单数据 + */ + @Operation(summary = "新增单数据") + @PreAuthorize("@ss.hasPermi('form:data:add')") + @Log(title = "单数据", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody FormData formData) + { + formData.setCreateBy(getUsername()); + return toAjax(formDataService.insertFormData(formData)); + } + + /** + * 修改单数据 + */ + @Operation(summary = "修改单数据") + @PreAuthorize("@ss.hasPermi('form:data:edit')") + @Log(title = "单数据", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody FormData formData) + { + formData.setUpdateBy(getUsername()); + return toAjax(formDataService.updateFormData(formData)); + } + + /** + * 删除单数据 + */ + @Operation(summary = "删除单数据") + @PreAuthorize("@ss.hasPermi('form:data:remove')") + @Log(title = "单数据", businessType = BusinessType.DELETE) + @DeleteMapping("/{dataIds}") + public AjaxResult remove(@PathVariable( name = "dataIds" ) Long[] dataIds) + { + return toAjax(formDataService.deleteFormDataByDataIds(dataIds)); + } +} diff --git a/boyue-models/boyue-form/src/main/java/com/boyue/form/controller/FormTemplateController.java b/boyue-models/boyue-form/src/main/java/com/boyue/form/controller/FormTemplateController.java new file mode 100644 index 0000000..c2f939f --- /dev/null +++ b/boyue-models/boyue-form/src/main/java/com/boyue/form/controller/FormTemplateController.java @@ -0,0 +1,113 @@ +package com.boyue.form.controller; + +import java.util.List; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.enums.BusinessType; +import com.boyue.form.domain.FormTemplate; +import com.boyue.form.service.IFormTemplateService; +import com.boyue.common.utils.poi.ExcelUtil; +import com.boyue.common.core.page.TableDataInfo; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; + +/** + * 单模板Controller + * + * @author boyue + * @date 2025-05-12 + */ +@RestController +@RequestMapping("/form/template") +@Tag(name = "【单模板】管理") +public class FormTemplateController extends BaseController +{ + @Autowired + private IFormTemplateService formTemplateService; + + /** + * 查询单模板列表 + */ + @Operation(summary = "查询单模板列表") + @PreAuthorize("@ss.hasPermi('form:template:list')") + @GetMapping("/list") + public TableDataInfo list(FormTemplate formTemplate) + { + startPage(); + List list = formTemplateService.selectFormTemplateList(formTemplate); + return getDataTable(list); + } + + /** + * 导出单模板列表 + */ + @Operation(summary = "导出单模板列表") + @PreAuthorize("@ss.hasPermi('form:template:export')") + @Log(title = "单模板", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, FormTemplate formTemplate) + { + List list = formTemplateService.selectFormTemplateList(formTemplate); + ExcelUtil util = new ExcelUtil(FormTemplate.class); + util.exportExcel(response, list, "单模板数据"); + } + + /** + * 获取单模板详细信息 + */ + @Operation(summary = "获取单模板详细信息") + @PreAuthorize("@ss.hasPermi('form:template:query')") + @GetMapping(value = "/{formId}") + public AjaxResult getInfo(@PathVariable("formId") Long formId) + { + return success(formTemplateService.selectFormTemplateByFormId(formId)); + } + + /** + * 新增单模板 + */ + @Operation(summary = "新增单模板") + @PreAuthorize("@ss.hasPermi('form:template:add')") + @Log(title = "单模板", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody FormTemplate formTemplate) + { + return toAjax(formTemplateService.insertFormTemplate(formTemplate)); + } + + /** + * 修改单模板 + */ + @Operation(summary = "修改单模板") + @PreAuthorize("@ss.hasPermi('form:template:edit')") + @Log(title = "单模板", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody FormTemplate formTemplate) + { + return toAjax(formTemplateService.updateFormTemplate(formTemplate)); + } + + /** + * 删除单模板 + */ + @Operation(summary = "删除单模板") + @PreAuthorize("@ss.hasPermi('form:template:remove')") + @Log(title = "单模板", businessType = BusinessType.DELETE) + @DeleteMapping("/{formIds}") + public AjaxResult remove(@PathVariable( name = "formIds" ) Long[] formIds) + { + return toAjax(formTemplateService.deleteFormTemplateByFormIds(formIds)); + } +} diff --git a/boyue-models/boyue-form/src/main/java/com/boyue/form/domain/FormData.java b/boyue-models/boyue-form/src/main/java/com/boyue/form/domain/FormData.java new file mode 100644 index 0000000..3e03d92 --- /dev/null +++ b/boyue-models/boyue-form/src/main/java/com/boyue/form/domain/FormData.java @@ -0,0 +1,140 @@ +package com.boyue.form.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.boyue.common.annotation.Excel; +import com.boyue.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 单数据对象 form_data + * + * @author boyue + * @date 2025-05-12 + */ +@Schema(description = "单数据对象") +public class FormData extends BaseEntity { + private static final long serialVersionUID = 1L; + + /** 数据ID */ + @Schema(title = "数据ID") + private Long dataId; + + /** 关联的表单ID */ + @Schema(title = "关联的表单ID") + @Excel(name = "关联的表单ID") + private Long formId; + + /** 表单版本(与模板表版本一致) */ + @Schema(title = "表单版本(与模板表版本一致)") + @Excel(name = "表单版本", readConverterExp = "与=模板表版本一致") + private String formVersion; + + /** 表单数据内容(JSON格式) */ + @Schema(title = "表单数据内容(JSON格式)") + @Excel(name = "表单数据内容", readConverterExp = "J=SON格式") + private String dataContent; + + /** 数据状态(draft, submitted, approved, rejected) */ + @Schema(title = "数据状态(draft, submitted, approved, rejected)") + @Excel(name = "数据状态", readConverterExp = "d=raft,,s=ubmitted,,a=pproved,,r=ejected") + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + @Schema(title = "删除标志(0代表存在 2代表删除)") + private String delFlag; + + /** 表单名称 */ + @Schema(title = "表单名称") + @Excel(name = "表单名称") + private String formName; + + /** 表单JSON Schema(vForm配置) */ + @Schema(title = "表单JSON Schema(vForm配置)") + @Excel(name = "表单JSON Schema", readConverterExp = "v=Form配置") + private String formSchema; + + public void setDataId(Long dataId) { + this.dataId = dataId; + } + + public Long getDataId() { + return dataId; + } + + public String getFormSchema() { + return formSchema; + } + + public void setFormSchema(String formSchema) { + this.formSchema = formSchema; + } + + public void setFormId(Long formId) { + this.formId = formId; + } + + public Long getFormId() { + return formId; + } + + public void setFormVersion(String formVersion) { + this.formVersion = formVersion; + } + + public String getFormVersion() { + return formVersion; + } + + public void setDataContent(String dataContent) { + this.dataContent = dataContent; + } + + public String getDataContent() { + return dataContent; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getStatus() { + return status; + } + + public void setDelFlag(String delFlag) { + this.delFlag = delFlag; + } + + public String getDelFlag() { + return delFlag; + } + + public void setFormName(String formName) { + this.formName = formName; + } + + public String getFormName() { + return formName; + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("dataId", getDataId()) + .append("formId", getFormId()) + .append("formVersion", getFormVersion()) + .append("dataContent", getDataContent()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .append("delFlag", getDelFlag()) + .append("formName", getFormName()) + .toString(); + } +} diff --git a/boyue-models/boyue-form/src/main/java/com/boyue/form/domain/FormTemplate.java b/boyue-models/boyue-form/src/main/java/com/boyue/form/domain/FormTemplate.java new file mode 100644 index 0000000..c485f4a --- /dev/null +++ b/boyue-models/boyue-form/src/main/java/com/boyue/form/domain/FormTemplate.java @@ -0,0 +1,131 @@ +package com.boyue.form.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.boyue.common.annotation.Excel; +import com.boyue.common.core.domain.BaseEntity; + +/** + * 单模板对象 form_template + * + * @author boyue + * @date 2025-05-12 + */ +@Schema(description = "单模板对象") +public class FormTemplate extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + + /** 表单ID */ + @Schema(title = "表单ID") + private Long formId; + + /** 表单名称 */ + @Schema(title = "表单名称") + @Excel(name = "表单名称") + private String formName; + + /** 表单JSON Schema(vForm配置) */ + @Schema(title = "表单JSON Schema(vForm配置)") + @Excel(name = "表单JSON Schema", readConverterExp = "v=Form配置") + private String formSchema; + + /** 表单版本(语义化版本) */ + @Schema(title = "表单版本(语义化版本)") + @Excel(name = "表单版本", readConverterExp = "语=义化版本") + private String formVersion; + + /** 发布状态(0: 草稿, 1: 已发布, 2: 已停用) */ + @Schema(title = "发布状态(0: 草稿, 1: 已发布, 2: 已停用)") + @Excel(name = "发布状态", readConverterExp = "0=:,草=稿,,1=:,已=发布,,2=:,已=停用") + private String formStatus; + + /** 删除标志(0代表存在 2代表删除) */ + @Schema(title = "删除标志(0代表存在 2代表删除)") + private String delFlag; + public void setFormId(Long formId) + { + this.formId = formId; + } + + public Long getFormId() + { + return formId; + } + + + public void setFormName(String formName) + { + this.formName = formName; + } + + public String getFormName() + { + return formName; + } + + + public void setFormSchema(String formSchema) + { + this.formSchema = formSchema; + } + + public String getFormSchema() + { + return formSchema; + } + + + public void setFormVersion(String formVersion) + { + this.formVersion = formVersion; + } + + public String getFormVersion() + { + return formVersion; + } + + + public void setFormStatus(String formStatus) + { + this.formStatus = formStatus; + } + + public String getFormStatus() + { + return formStatus; + } + + + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public String getDelFlag() + { + return delFlag; + } + + + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("formId", getFormId()) + .append("formName", getFormName()) + .append("formSchema", getFormSchema()) + .append("formVersion", getFormVersion()) + .append("formStatus", getFormStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .append("delFlag", getDelFlag()) + .toString(); + } +} diff --git a/boyue-models/boyue-form/src/main/java/com/boyue/form/mapper/FormDataMapper.java b/boyue-models/boyue-form/src/main/java/com/boyue/form/mapper/FormDataMapper.java new file mode 100644 index 0000000..80884ce --- /dev/null +++ b/boyue-models/boyue-form/src/main/java/com/boyue/form/mapper/FormDataMapper.java @@ -0,0 +1,61 @@ +package com.boyue.form.mapper; + +import java.util.List; +import com.boyue.form.domain.FormData; + +/** + * 单数据Mapper接口 + * + * @author boyue + * @date 2025-05-12 + */ +public interface FormDataMapper +{ + /** + * 查询单数据 + * + * @param dataId 单数据主键 + * @return 单数据 + */ + public FormData selectFormDataByDataId(Long dataId); + + /** + * 查询单数据列表 + * + * @param formData 单数据 + * @return 单数据集合 + */ + public List selectFormDataList(FormData formData); + + /** + * 新增单数据 + * + * @param formData 单数据 + * @return 结果 + */ + public int insertFormData(FormData formData); + + /** + * 修改单数据 + * + * @param formData 单数据 + * @return 结果 + */ + public int updateFormData(FormData formData); + + /** + * 删除单数据 + * + * @param dataId 单数据主键 + * @return 结果 + */ + public int deleteFormDataByDataId(Long dataId); + + /** + * 批量删除单数据 + * + * @param dataIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteFormDataByDataIds(Long[] dataIds); +} diff --git a/boyue-models/boyue-form/src/main/java/com/boyue/form/mapper/FormTemplateMapper.java b/boyue-models/boyue-form/src/main/java/com/boyue/form/mapper/FormTemplateMapper.java new file mode 100644 index 0000000..0190635 --- /dev/null +++ b/boyue-models/boyue-form/src/main/java/com/boyue/form/mapper/FormTemplateMapper.java @@ -0,0 +1,61 @@ +package com.boyue.form.mapper; + +import java.util.List; +import com.boyue.form.domain.FormTemplate; + +/** + * 单模板Mapper接口 + * + * @author boyue + * @date 2025-05-12 + */ +public interface FormTemplateMapper +{ + /** + * 查询单模板 + * + * @param formId 单模板主键 + * @return 单模板 + */ + public FormTemplate selectFormTemplateByFormId(Long formId); + + /** + * 查询单模板列表 + * + * @param formTemplate 单模板 + * @return 单模板集合 + */ + public List selectFormTemplateList(FormTemplate formTemplate); + + /** + * 新增单模板 + * + * @param formTemplate 单模板 + * @return 结果 + */ + public int insertFormTemplate(FormTemplate formTemplate); + + /** + * 修改单模板 + * + * @param formTemplate 单模板 + * @return 结果 + */ + public int updateFormTemplate(FormTemplate formTemplate); + + /** + * 删除单模板 + * + * @param formId 单模板主键 + * @return 结果 + */ + public int deleteFormTemplateByFormId(Long formId); + + /** + * 批量删除单模板 + * + * @param formIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteFormTemplateByFormIds(Long[] formIds); +} diff --git a/boyue-models/boyue-form/src/main/java/com/boyue/form/service/IFormDataService.java b/boyue-models/boyue-form/src/main/java/com/boyue/form/service/IFormDataService.java new file mode 100644 index 0000000..a4e5718 --- /dev/null +++ b/boyue-models/boyue-form/src/main/java/com/boyue/form/service/IFormDataService.java @@ -0,0 +1,61 @@ +package com.boyue.form.service; + +import java.util.List; +import com.boyue.form.domain.FormData; + +/** + * 单数据Service接口 + * + * @author boyue + * @date 2025-05-12 + */ +public interface IFormDataService +{ + /** + * 查询单数据 + * + * @param dataId 单数据主键 + * @return 单数据 + */ + public FormData selectFormDataByDataId(Long dataId); + + /** + * 查询单数据列表 + * + * @param formData 单数据 + * @return 单数据集合 + */ + public List selectFormDataList(FormData formData); + + /** + * 新增单数据 + * + * @param formData 单数据 + * @return 结果 + */ + public int insertFormData(FormData formData); + + /** + * 修改单数据 + * + * @param formData 单数据 + * @return 结果 + */ + public int updateFormData(FormData formData); + + /** + * 批量删除单数据 + * + * @param dataIds 需要删除的单数据主键集合 + * @return 结果 + */ + public int deleteFormDataByDataIds(Long[] dataIds); + + /** + * 删除单数据信息 + * + * @param dataId 单数据主键 + * @return 结果 + */ + public int deleteFormDataByDataId(Long dataId); +} diff --git a/boyue-models/boyue-form/src/main/java/com/boyue/form/service/IFormTemplateService.java b/boyue-models/boyue-form/src/main/java/com/boyue/form/service/IFormTemplateService.java new file mode 100644 index 0000000..ecc6f05 --- /dev/null +++ b/boyue-models/boyue-form/src/main/java/com/boyue/form/service/IFormTemplateService.java @@ -0,0 +1,61 @@ +package com.boyue.form.service; + +import java.util.List; +import com.boyue.form.domain.FormTemplate; + +/** + * 单模板Service接口 + * + * @author boyue + * @date 2025-05-12 + */ +public interface IFormTemplateService +{ + /** + * 查询单模板 + * + * @param formId 单模板主键 + * @return 单模板 + */ + public FormTemplate selectFormTemplateByFormId(Long formId); + + /** + * 查询单模板列表 + * + * @param formTemplate 单模板 + * @return 单模板集合 + */ + public List selectFormTemplateList(FormTemplate formTemplate); + + /** + * 新增单模板 + * + * @param formTemplate 单模板 + * @return 结果 + */ + public int insertFormTemplate(FormTemplate formTemplate); + + /** + * 修改单模板 + * + * @param formTemplate 单模板 + * @return 结果 + */ + public int updateFormTemplate(FormTemplate formTemplate); + + /** + * 批量删除单模板 + * + * @param formIds 需要删除的单模板主键集合 + * @return 结果 + */ + public int deleteFormTemplateByFormIds(Long[] formIds); + + /** + * 删除单模板信息 + * + * @param formId 单模板主键 + * @return 结果 + */ + public int deleteFormTemplateByFormId(Long formId); +} diff --git a/boyue-models/boyue-form/src/main/java/com/boyue/form/service/impl/FormDataServiceImpl.java b/boyue-models/boyue-form/src/main/java/com/boyue/form/service/impl/FormDataServiceImpl.java new file mode 100644 index 0000000..50abb44 --- /dev/null +++ b/boyue-models/boyue-form/src/main/java/com/boyue/form/service/impl/FormDataServiceImpl.java @@ -0,0 +1,96 @@ +package com.boyue.form.service.impl; + +import java.util.List; +import com.boyue.common.utils.DateUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.boyue.form.mapper.FormDataMapper; +import com.boyue.form.domain.FormData; +import com.boyue.form.service.IFormDataService; + +/** + * 单数据Service业务层处理 + * + * @author boyue + * @date 2025-05-12 + */ +@Service +public class FormDataServiceImpl implements IFormDataService +{ + @Autowired + private FormDataMapper formDataMapper; + + /** + * 查询单数据 + * + * @param dataId 单数据主键 + * @return 单数据 + */ + @Override + public FormData selectFormDataByDataId(Long dataId) + { + return formDataMapper.selectFormDataByDataId(dataId); + } + + /** + * 查询单数据列表 + * + * @param formData 单数据 + * @return 单数据 + */ + @Override + public List selectFormDataList(FormData formData) + { + return formDataMapper.selectFormDataList(formData); + } + + /** + * 新增单数据 + * + * @param formData 单数据 + * @return 结果 + */ + @Override + public int insertFormData(FormData formData) + { + formData.setCreateTime(DateUtils.getNowDate()); + return formDataMapper.insertFormData(formData); + } + + /** + * 修改单数据 + * + * @param formData 单数据 + * @return 结果 + */ + @Override + public int updateFormData(FormData formData) + { + formData.setUpdateTime(DateUtils.getNowDate()); + return formDataMapper.updateFormData(formData); + } + + /** + * 批量删除单数据 + * + * @param dataIds 需要删除的单数据主键 + * @return 结果 + */ + @Override + public int deleteFormDataByDataIds(Long[] dataIds) + { + return formDataMapper.deleteFormDataByDataIds(dataIds); + } + + /** + * 删除单数据信息 + * + * @param dataId 单数据主键 + * @return 结果 + */ + @Override + public int deleteFormDataByDataId(Long dataId) + { + return formDataMapper.deleteFormDataByDataId(dataId); + } +} diff --git a/boyue-models/boyue-form/src/main/java/com/boyue/form/service/impl/FormTemplateServiceImpl.java b/boyue-models/boyue-form/src/main/java/com/boyue/form/service/impl/FormTemplateServiceImpl.java new file mode 100644 index 0000000..ba73d5d --- /dev/null +++ b/boyue-models/boyue-form/src/main/java/com/boyue/form/service/impl/FormTemplateServiceImpl.java @@ -0,0 +1,96 @@ +package com.boyue.form.service.impl; + +import java.util.List; +import com.boyue.common.utils.DateUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.boyue.form.mapper.FormTemplateMapper; +import com.boyue.form.domain.FormTemplate; +import com.boyue.form.service.IFormTemplateService; + +/** + * 单模板Service业务层处理 + * + * @author boyue + * @date 2025-05-12 + */ +@Service +public class FormTemplateServiceImpl implements IFormTemplateService +{ + @Autowired + private FormTemplateMapper formTemplateMapper; + + /** + * 查询单模板 + * + * @param formId 单模板主键 + * @return 单模板 + */ + @Override + public FormTemplate selectFormTemplateByFormId(Long formId) + { + return formTemplateMapper.selectFormTemplateByFormId(formId); + } + + /** + * 查询单模板列表 + * + * @param formTemplate 单模板 + * @return 单模板 + */ + @Override + public List selectFormTemplateList(FormTemplate formTemplate) + { + return formTemplateMapper.selectFormTemplateList(formTemplate); + } + + /** + * 新增单模板 + * + * @param formTemplate 单模板 + * @return 结果 + */ + @Override + public int insertFormTemplate(FormTemplate formTemplate) + { + formTemplate.setCreateTime(DateUtils.getNowDate()); + return formTemplateMapper.insertFormTemplate(formTemplate); + } + + /** + * 修改单模板 + * + * @param formTemplate 单模板 + * @return 结果 + */ + @Override + public int updateFormTemplate(FormTemplate formTemplate) + { + formTemplate.setUpdateTime(DateUtils.getNowDate()); + return formTemplateMapper.updateFormTemplate(formTemplate); + } + + /** + * 批量删除单模板 + * + * @param formIds 需要删除的单模板主键 + * @return 结果 + */ + @Override + public int deleteFormTemplateByFormIds(Long[] formIds) + { + return formTemplateMapper.deleteFormTemplateByFormIds(formIds); + } + + /** + * 删除单模板信息 + * + * @param formId 单模板主键 + * @return 结果 + */ + @Override + public int deleteFormTemplateByFormId(Long formId) + { + return formTemplateMapper.deleteFormTemplateByFormId(formId); + } +} diff --git a/boyue-models/boyue-form/src/main/resources/mapper/form/FormDataMapper.xml b/boyue-models/boyue-form/src/main/resources/mapper/form/FormDataMapper.xml new file mode 100644 index 0000000..5ac84c2 --- /dev/null +++ b/boyue-models/boyue-form/src/main/resources/mapper/form/FormDataMapper.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + select + fd.data_id, + fd.form_id, + fd.form_version, + fd.data_content, + fd.status, + fd.create_by, + fd.create_time, + fd.update_by, + fd.update_time, + fd.remark, + fd.del_flag, + ft.form_name + from form_data fd + left join form_template ft on ft.form_id = fd.form_id + left join sys_user su on su.user_name = fd.create_by + + + + + + + + insert into form_data + + form_id, + form_version, + data_content, + status, + create_by, + create_time, + update_by, + update_time, + remark, + del_flag, + + + #{formId}, + #{formVersion}, + #{dataContent}, + #{status}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + #{delFlag}, + + + + + update form_data + + form_id = #{formId}, + form_version = #{formVersion}, + data_content = #{dataContent}, + status = #{status}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + del_flag = #{delFlag}, + + where form_data.data_id = #{dataId} + + + + delete from form_data where data_id = #{dataId} + + + + delete from form_data where data_id in + + #{dataId} + + + \ No newline at end of file diff --git a/boyue-models/boyue-form/src/main/resources/mapper/form/FormTemplateMapper.xml b/boyue-models/boyue-form/src/main/resources/mapper/form/FormTemplateMapper.xml new file mode 100644 index 0000000..183dde2 --- /dev/null +++ b/boyue-models/boyue-form/src/main/resources/mapper/form/FormTemplateMapper.xml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + select + ft.form_id, + ft.form_name, + ft.form_schema, + ft.form_version, + ft.form_status, + ft.create_by, + ft.create_time, + ft.update_by, + ft.update_time, + ft.remark, + ft.del_flag + from form_template ft + left join sys_user su on su.user_name = ft.create_by + + + + + + + + insert into form_template + + form_name, + form_schema, + form_version, + form_status, + create_by, + create_time, + update_by, + update_time, + remark, + del_flag, + + + #{formName}, + #{formSchema}, + #{formVersion}, + #{formStatus}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + #{delFlag}, + + + + + update form_template + + form_name = #{formName}, + form_schema = #{formSchema}, + form_version = #{formVersion}, + form_status = #{formStatus}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + del_flag = #{delFlag}, + + where form_template.form_id = #{formId} + + + + delete from form_template where form_id = #{formId} + + + + delete from form_template where form_id in + + #{formId} + + + \ No newline at end of file diff --git a/boyue-models/boyue-generator/pom.xml b/boyue-models/boyue-generator/pom.xml new file mode 100644 index 0000000..883ed79 --- /dev/null +++ b/boyue-models/boyue-generator/pom.xml @@ -0,0 +1,40 @@ + + + + boyue-models + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-generator + + + generator代码生成 + + + + + + + org.apache.velocity + velocity-engine-core + + + + + com.boyue + boyue-common + + + + + com.alibaba + druid-spring-boot-3-starter + + + + + \ No newline at end of file diff --git a/boyue-models/boyue-generator/src/main/java/com/boyue/generator/config/GenConfig.java b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/config/GenConfig.java new file mode 100644 index 0000000..77a3ca9 --- /dev/null +++ b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/config/GenConfig.java @@ -0,0 +1,73 @@ +package com.boyue.generator.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.PropertySource; +import org.springframework.stereotype.Component; + +/** + * 读取代码生成相关配置 + * + * @author boyue + */ +@Component +@ConfigurationProperties(prefix = "gen") +@PropertySource(value = { "classpath:generator.yml" }) +public class GenConfig +{ + /** 作者 */ + public static String author; + + /** 生成包路径 */ + public static String packageName; + + /** 自动去除表前缀,默认是false */ + public static boolean autoRemovePre; + + /** 表前缀(类名不会包含表前缀) */ + public static String tablePrefix; + + public static String getAuthor() + { + return author; + } + + @Value("${author}") + public void setAuthor(String author) + { + GenConfig.author = author; + } + + public static String getPackageName() + { + return packageName; + } + + @Value("${packageName}") + public void setPackageName(String packageName) + { + GenConfig.packageName = packageName; + } + + public static boolean getAutoRemovePre() + { + return autoRemovePre; + } + + @Value("${autoRemovePre}") + public void setAutoRemovePre(boolean autoRemovePre) + { + GenConfig.autoRemovePre = autoRemovePre; + } + + public static String getTablePrefix() + { + return tablePrefix; + } + + @Value("${tablePrefix}") + public void setTablePrefix(String tablePrefix) + { + GenConfig.tablePrefix = tablePrefix; + } +} diff --git a/boyue-models/boyue-generator/src/main/java/com/boyue/generator/constant/GenConstants.java b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/constant/GenConstants.java new file mode 100644 index 0000000..d742fac --- /dev/null +++ b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/constant/GenConstants.java @@ -0,0 +1,129 @@ +package com.boyue.generator.constant; + +/** + * 代码生成通用常量 + * + * @author boyue + */ +public class GenConstants +{ + /** 单表(增删改查) */ + public static final String TPL_CRUD = "crud"; + + /** 树表(增删改查) */ + public static final String TPL_TREE = "tree"; + + /** 主子表(增删改查) */ + public static final String TPL_SUB = "sub"; + + /** 树编码字段 */ + public static final String TREE_CODE = "treeCode"; + + /** 树父编码字段 */ + public static final String TREE_PARENT_CODE = "treeParentCode"; + + /** 树名称字段 */ + public static final String TREE_NAME = "treeName"; + + /** 上级菜单ID字段 */ + public static final String PARENT_MENU_ID = "parentMenuId"; + + /** 上级菜单名称字段 */ + public static final String PARENT_MENU_NAME = "parentMenuName"; + + /** 数据库字符串类型 */ + public static final String[] COLUMNTYPE_STR = { "char", "varchar", "nvarchar", "varchar2" }; + + /** 数据库文本类型 */ + public static final String[] COLUMNTYPE_TEXT = { "tinytext", "text", "mediumtext", "longtext" }; + + /** 数据库时间类型 */ + public static final String[] COLUMNTYPE_TIME = { "datetime", "time", "date", "timestamp" }; + + /** 数据库数字类型 */ + public static final String[] COLUMNTYPE_NUMBER = { "tinyint", "smallint", "mediumint", "int", "number", "integer", + "bit", "bigint", "float", "double", "decimal" }; + + /** 页面不需要编辑字段 */ + public static final String[] COLUMNNAME_NOT_EDIT = { "id", "create_by", "create_time", "del_flag" }; + + /** 页面不需要显示的列表字段 */ + public static final String[] COLUMNNAME_NOT_LIST = { "id", "create_by", "create_time", "del_flag", "update_by", + "update_time" }; + + /** 页面不需要查询字段 */ + public static final String[] COLUMNNAME_NOT_QUERY = { "id", "create_by", "create_time", "del_flag", "update_by", + "update_time", "remark" }; + + /** Entity基类字段 */ + public static final String[] BASE_ENTITY = { "createBy", "createTime", "updateBy", "updateTime", "remark" }; + + /** Tree基类字段 */ + public static final String[] TREE_ENTITY = { "parentName", "parentId", "orderNum", "ancestors", "children" }; + + /** 文本框 */ + public static final String HTML_INPUT = "input"; + + /** 文本域 */ + public static final String HTML_TEXTAREA = "textarea"; + + /** 下拉框 */ + public static final String HTML_SELECT = "select"; + + /** 单选框 */ + public static final String HTML_RADIO = "radio"; + + /** 复选框 */ + public static final String HTML_CHECKBOX = "checkbox"; + + /** 日期控件 */ + public static final String HTML_DATE = "date"; + + /** 时间控件 */ + public static final String HTML_TIME = "time"; + + /** 日期时间控件 */ + public static final String HTML_DATETIME = "datetime"; + + /** 图片上传控件 */ + public static final String HTML_IMAGE_UPLOAD = "imageUpload"; + + /** 文件上传控件 */ + public static final String HTML_FILE_UPLOAD = "fileUpload"; + + /** 富文本控件 */ + public static final String HTML_EDITOR = "editor"; + + /** 字符串类型 */ + public static final String TYPE_STRING = "String"; + + /** 整型 */ + public static final String TYPE_INTEGER = "Integer"; + + /** 长整型 */ + public static final String TYPE_LONG = "Long"; + + /** 浮点型 */ + public static final String TYPE_DOUBLE = "Double"; + + /** 高精度计算类型 */ + public static final String TYPE_BIGDECIMAL = "BigDecimal"; + + /** 日期类型 */ + public static final String TYPE_DATE = "LocalDate"; + + /** 时间类型 */ + public static final String TYPE_TIME = "LocalTime"; + + /** 日期时间类型 */ + public static final String TYPE_DATETIME = "LocalDateTime"; + + /** 模糊查询 */ + public static final String QUERY_LIKE = "LIKE"; + + /** 相等查询 */ + public static final String QUERY_EQ = "EQ"; + + /** 需要 */ + public static final String REQUIRE = "1"; +} diff --git a/boyue-models/boyue-generator/src/main/java/com/boyue/generator/controller/GenController.java b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/controller/GenController.java new file mode 100644 index 0000000..6555fb2 --- /dev/null +++ b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/controller/GenController.java @@ -0,0 +1,267 @@ +package com.boyue.generator.controller; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.apache.commons.io.IOUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.alibaba.druid.DbType; +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlCreateTableStatement; +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.core.text.Convert; +import com.boyue.common.enums.BusinessType; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.common.utils.sql.SqlUtil; +import com.boyue.generator.domain.GenJoinTable; +import com.boyue.generator.domain.GenTable; +import com.boyue.generator.domain.GenTableColumn; +import com.boyue.generator.domain.vo.GenTableVo; +import com.boyue.generator.service.IGenJoinTableService; +import com.boyue.generator.service.IGenTableColumnService; +import com.boyue.generator.service.IGenTableService; + +import jakarta.servlet.http.HttpServletResponse; + +/** + * 代码生成 操作处理 + * + * @author boyue + */ +@RestController +@RequestMapping("/tool/gen") +public class GenController extends BaseController { + @Autowired + private IGenTableService genTableService; + + @Autowired + private IGenTableColumnService genTableColumnService; + + @Autowired + private IGenJoinTableService genJoinTableService; + + /** + * 查询代码生成列表 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:list')") + @GetMapping("/list") + public TableDataInfo genList(GenTable genTable) { + startPage(); + List list = genTableService.selectGenTableList(genTable); + return getDataTable(list); + } + + /** + * 修改代码生成业务 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:query')") + @GetMapping(value = "/{tableId}") + public AjaxResult getInfo(@PathVariable(name = "tableId") Long tableId) { + GenTable table = genTableService.selectGenTableById(tableId); + GenTableVo genTableVo = new GenTableVo(); + genTableVo.setTable(table); + genTableVo.setColumns(table.getColumns()); + GenJoinTable genJoinTable = new GenJoinTable(); + genJoinTable.setTableId(tableId); + List selectGenJoinTableList = genJoinTableService.selectGenJoinTableList(genJoinTable); + genTableVo.setJoinTablesMate(selectGenJoinTableList); + Map joinTableMap = new HashMap(); + joinTableMap.put(tableId, table); + selectGenJoinTableList.forEach(i -> { + if(Objects.isNull(joinTableMap.get(i.getLeftTableId()))) { + joinTableMap.put(i.getLeftTableId(), genTableService.selectGenTableById(i.getLeftTableId())); + } + if(Objects.isNull(joinTableMap.get(i.getRightTableId()))) { + joinTableMap.put(i.getRightTableId(), genTableService.selectGenTableById(i.getRightTableId())); + } + }); + genTableVo.setJoinTables(joinTableMap.values()); + return success(genTableVo); + } + + /** + * 查询数据库列表 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:list')") + @GetMapping("/db/list") + public TableDataInfo dataList(GenTable genTable) { + startPage(); + List list = genTableService.selectDbTableList(genTable); + return getDataTable(list); + } + + /** + * 查询数据表字段列表 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:list')") + @GetMapping(value = "/column/{tableId}") + public TableDataInfo columnList(@PathVariable(name = "tableId") Long tableId) { + TableDataInfo dataInfo = new TableDataInfo(); + List list = genTableColumnService.selectGenTableColumnListByTableId(tableId); + dataInfo.setRows(list); + dataInfo.setTotal(list.size()); + return dataInfo; + } + + /** + * 导入表结构(保存) + */ + @PreAuthorize("@ss.hasPermi('tool:gen:import')") + @Log(title = "代码生成", businessType = BusinessType.IMPORT) + @PostMapping("/importTable") + public AjaxResult importTableSave(@RequestParam("tables") String tables) { + String[] tableNames = Convert.toStrArray(tables); + // 查询表信息 + List tableList = genTableService.selectDbTableListByNames(tableNames); + genTableService.importGenTable(tableList, SecurityUtils.getUsername()); + return success(); + } + + /** + * 创建表结构(保存) + */ + @PreAuthorize("@ss.hasRole('admin')") + @Log(title = "创建表", businessType = BusinessType.OTHER) + @PostMapping("/createTable") + public AjaxResult createTableSave(String sql) { + try { + SqlUtil.filterKeyword(sql); + List sqlStatements = SQLUtils.parseStatements(sql, DbType.mysql); + List tableNames = new ArrayList<>(); + for (SQLStatement sqlStatement : sqlStatements) { + if (sqlStatement instanceof MySqlCreateTableStatement) { + MySqlCreateTableStatement createTableStatement = (MySqlCreateTableStatement) sqlStatement; + if (genTableService.createTable(createTableStatement.toString())) { + String tableName = createTableStatement.getTableName().replaceAll("`", ""); + tableNames.add(tableName); + } + } + } + List tableList = genTableService + .selectDbTableListByNames(tableNames.toArray(new String[tableNames.size()])); + String operName = SecurityUtils.getUsername(); + genTableService.importGenTable(tableList, operName); + return AjaxResult.success(); + } catch (Exception e) { + logger.error(e.getMessage(), e); + return AjaxResult.error("创建表结构异常"); + } + } + + /** + * 修改保存代码生成业务 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:edit')") + @Log(title = "代码生成", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult editSave(@Validated @RequestBody GenTableVo genTableVo) { + GenTable genTable = genTableVo.getTable(); + genTableService.validateEdit(genTable); + genTableService.updateGenTable(genTable); + genJoinTableService.deleteGenJoinTableByTableId(genTable.getTableId()); + genTableVo.getJoinTablesMate().forEach(i -> { + genJoinTableService.insertGenJoinTable(i); + }); + return success(); + } + + /** + * 删除代码生成 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:remove')") + @Log(title = "代码生成", businessType = BusinessType.DELETE) + @DeleteMapping("/{tableIds}") + public AjaxResult remove(@PathVariable(name = "tableIds") Long[] tableIds) { + genTableService.deleteGenTableByIds(tableIds); + return success(); + } + + /** + * 预览代码 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:preview')") + @GetMapping("/preview/{tableId}") + public AjaxResult preview(@PathVariable("tableId") Long tableId) throws IOException { + Map dataMap = genTableService.previewCode(tableId); + return success(dataMap); + } + + /** + * 生成代码(下载方式) + */ + @PreAuthorize("@ss.hasPermi('tool:gen:code')") + @Log(title = "代码生成", businessType = BusinessType.GENCODE) + @GetMapping("/download/{tableName}") + public void download(HttpServletResponse response, @PathVariable("tableName") String tableName) throws IOException { + byte[] data = genTableService.downloadCode(tableName); + genCode(response, data); + } + + /** + * 生成代码(自定义路径) + */ + @PreAuthorize("@ss.hasPermi('tool:gen:code')") + @Log(title = "代码生成", businessType = BusinessType.GENCODE) + @GetMapping("/genCode/{tableName}") + public AjaxResult genCode(@PathVariable("tableName") String tableName) { + genTableService.generatorCode(tableName); + return success(); + } + + /** + * 同步数据库 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:edit')") + @Log(title = "代码生成", businessType = BusinessType.UPDATE) + @GetMapping("/synchDb/{tableName}") + public AjaxResult synchDb(@PathVariable("tableName") String tableName) { + genTableService.synchDb(tableName); + return success(); + } + + /** + * 批量生成代码 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:code')") + @Log(title = "代码生成", businessType = BusinessType.GENCODE) + @GetMapping("/batchGenCode") + public void batchGenCode(HttpServletResponse response, @RequestParam(name = "tables") String tables) + throws IOException { + String[] tableNames = Convert.toStrArray(tables); + byte[] data = genTableService.downloadCode(tableNames); + genCode(response, data); + } + + /** + * 生成zip文件 + */ + private void genCode(HttpServletResponse response, byte[] data) throws IOException { + response.reset(); + response.addHeader("Access-Control-Allow-Origin", "*"); + response.addHeader("Access-Control-Expose-Headers", "Content-Disposition"); + response.setHeader("Content-Disposition", "attachment; filename=\"boyue.zip\""); + response.addHeader("Content-Length", "" + data.length); + response.setContentType("application/octet-stream; charset=UTF-8"); + IOUtils.write(data, response.getOutputStream()); + } +} \ No newline at end of file diff --git a/boyue-models/boyue-generator/src/main/java/com/boyue/generator/domain/GenJoinTable.java b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/domain/GenJoinTable.java new file mode 100644 index 0000000..af34090 --- /dev/null +++ b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/domain/GenJoinTable.java @@ -0,0 +1,45 @@ +package com.boyue.generator.domain; + +import java.util.List; + +import com.boyue.common.core.domain.BaseEntity; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class GenJoinTable extends BaseEntity { + + /** 表编号 */ + private Long tableId; + + private Long leftTableId; + + /** 关联表编号 */ + private Long rightTableId; + + /** 新引入的表 */ + private Long newTableId; + + /** 主表别名 */ + private String leftTableAlias; + + /** 关联表别名 */ + private String rightTableAlias; + + /** 主表外键 */ + private Long leftTableFk; + + /** 关联表外键 */ + private Long rightTableFk; + + /** 连接类型 */ + private String joinType; + + /** 关联字段 */ + private List joinColumns; + + private Long orderNum; + +} diff --git a/boyue-models/boyue-generator/src/main/java/com/boyue/generator/domain/GenTable.java b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/domain/GenTable.java new file mode 100644 index 0000000..b7ec585 --- /dev/null +++ b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/domain/GenTable.java @@ -0,0 +1,149 @@ +package com.boyue.generator.domain; + +import java.util.List; + +import org.apache.commons.lang3.ArrayUtils; + +import com.boyue.common.core.domain.BaseEntity; +import com.boyue.common.utils.StringUtils; +import com.boyue.generator.constant.GenConstants; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 业务表 gen_table + * + * @author boyue + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class GenTable extends BaseEntity { + private static final long serialVersionUID = 1L; + + /** 编号 */ + private Long tableId; + + /** 表名称 */ + @NotBlank(message = "表名称不能为空") + private String tableName; + + /** 表别名 */ + private String tableAlias; + + /** 表描述 */ + @NotBlank(message = "表描述不能为空") + private String tableComment; + + /** 关联父表的表名 */ + private String subTableName; + + /** 本表关联父表的外键名 */ + private String subTableFkName; + + /** 实体类名称(首字母大写) */ + @NotBlank(message = "实体类名称不能为空") + private String className; + + /** 使用的模板(crud单表操作 tree树表操作 sub主子表操作) */ + private String tplCategory; + + /** 前端类型(element-ui模版 element-plus模版) */ + private String tplWebType; + + /** 生成包路径 */ + @NotBlank(message = "生成包路径不能为空") + private String packageName; + + /** 生成模块名 */ + @NotBlank(message = "生成模块名不能为空") + private String moduleName; + + /** 生成业务名 */ + @NotBlank(message = "生成业务名不能为空") + private String businessName; + + /** 生成功能名 */ + @NotBlank(message = "生成功能名不能为空") + private String functionName; + + /** 生成作者 */ + @NotBlank(message = "作者不能为空") + private String functionAuthor; + + /** 生成代码方式(0zip压缩包 1自定义路径) */ + private String genType; + + /** 生成路径(不填默认项目路径) */ + private String genPath; + + /** 主键信息 */ + private GenTableColumn pkColumn; + + /** 子表信息 */ + private GenTable subTable; + + /** 表列信息 */ + @Valid + private List columns; + + /** 其它生成选项 */ + private String options; + + /** 树编码字段 */ + private String treeCode; + + /** 树父编码字段 */ + private String treeParentCode; + + /** 树名称字段 */ + private String treeName; + + /** 上级菜单ID字段 */ + private String parentMenuId; + + /** 上级菜单名称字段 */ + private String parentMenuName; + + /** 是否含有关联字段 */ + @Deprecated + private String haveSubColumn; + + public boolean isSub() { + return isSub(this.tplCategory); + } + + public static boolean isSub(String tplCategory) { + return tplCategory != null && StringUtils.equals(GenConstants.TPL_SUB, tplCategory); + } + + public boolean isTree() { + return isTree(this.tplCategory); + } + + public static boolean isTree(String tplCategory) { + return tplCategory != null && StringUtils.equals(GenConstants.TPL_TREE, tplCategory); + } + + public boolean isCrud() { + return isCrud(this.tplCategory); + } + + public static boolean isCrud(String tplCategory) { + return tplCategory != null && StringUtils.equals(GenConstants.TPL_CRUD, tplCategory); + } + + public boolean isSuperColumn(String javaField) { + return isSuperColumn(this.tplCategory, javaField); + } + + public static boolean isSuperColumn(String tplCategory, String javaField) { + if (isTree(tplCategory)) { + return StringUtils.equalsAnyIgnoreCase(javaField, + ArrayUtils.addAll(GenConstants.TREE_ENTITY, GenConstants.BASE_ENTITY)); + } + return StringUtils.equalsAnyIgnoreCase(javaField, GenConstants.BASE_ENTITY); + } +} \ No newline at end of file diff --git a/boyue-models/boyue-generator/src/main/java/com/boyue/generator/domain/GenTableColumn.java b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/domain/GenTableColumn.java new file mode 100644 index 0000000..ce16360 --- /dev/null +++ b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/domain/GenTableColumn.java @@ -0,0 +1,194 @@ +package com.boyue.generator.domain; + +import com.boyue.common.core.domain.BaseEntity; +import com.boyue.common.utils.StringUtils; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 代码生成业务字段表 gen_table_column + * + * @author boyue + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class GenTableColumn extends BaseEntity { + private static final long serialVersionUID = 1L; + + /** 编号 */ + private Long columnId; + + /** 归属表编号 */ + private Long tableId; + + /** 列名称 */ + private String columnName; + + /** 列描述 */ + private String columnComment; + + /** 列类型 */ + private String columnType; + + /** JAVA类型 */ + private String javaType; + + /** JAVA字段名 */ + @NotBlank(message = "Java属性不能为空") + private String javaField; + + /** 是否主键(1是) */ + private String isPk; + + /** 是否自增(1是) */ + private String isIncrement; + + /** 是否必填(1是) */ + private String isRequired; + + /** 是否为插入字段(1是) */ + private String isInsert; + + /** 是否编辑字段(1是) */ + private String isEdit; + + /** 是否列表字段(1是) */ + private String isList; + + /** 是否查询字段(1是) */ + private String isQuery; + + /** 查询方式(EQ等于、NE不等于、GT大于、LT小于、LIKE模糊、BETWEEN范围) */ + private String queryType; + + /** + * 显示类型(input文本框、textarea文本域、select下拉框、checkbox复选框、radio单选框、datetime日期控件、image图片上传控件、upload文件上传控件、editor富文本控件) + */ + private String htmlType; + + /** 字典类型 */ + private String dictType; + + /** 排序 */ + private Integer sort; + + /** 关联表名称 */ + @Deprecated + private String subColumnTableName; + + /** 关联字段名称 */ + @Deprecated + private String subColumnFkName; + + /** 映射字段名称 */ + @Deprecated + private String subColumnName; + + /** 映射字段Java字段名 */ + @Deprecated + private String subColumnJavaField; + + /** 映射字段Java类型 */ + @Deprecated + private String subColumnJavaType; + + public String getCapJavaField() { + return StringUtils.capitalize(javaField); + } + + public boolean isPk() { + return isPk(this.isPk); + } + + public boolean isPk(String isPk) { + return isPk != null && StringUtils.equals("1", isPk); + } + + public boolean isIncrement() { + return isIncrement(this.isIncrement); + } + + public boolean isIncrement(String isIncrement) { + return isIncrement != null && StringUtils.equals("1", isIncrement); + } + + public boolean isRequired() { + return isRequired(this.isRequired); + } + + public boolean isRequired(String isRequired) { + return isRequired != null && StringUtils.equals("1", isRequired); + } + + public boolean isInsert() { + return isInsert(this.isInsert); + } + + public boolean isInsert(String isInsert) { + return isInsert != null && StringUtils.equals("1", isInsert); + } + + public boolean isEdit() { + return isInsert(this.isEdit); + } + + public boolean isEdit(String isEdit) { + return isEdit != null && StringUtils.equals("1", isEdit); + } + + public boolean isList() { + return isList(this.isList); + } + + public boolean isList(String isList) { + return isList != null && StringUtils.equals("1", isList); + } + + public boolean isQuery() { + return isQuery(this.isQuery); + } + + public boolean isQuery(String isQuery) { + return isQuery != null && StringUtils.equals("1", isQuery); + } + + public boolean isSuperColumn() { + return isSuperColumn(this.javaField); + } + + public static boolean isSuperColumn(String javaField) { + return StringUtils.equalsAnyIgnoreCase(javaField, + // BaseEntity + "createBy", "createTime", "updateBy", "updateTime", "remark", + // TreeEntity + "parentName", "parentId", "orderNum", "ancestors"); + } + + public boolean isUsableColumn() { + return isUsableColumn(javaField); + } + + public static boolean isUsableColumn(String javaField) { + // isSuperColumn()中的名单用于避免生成多余Domain属性,若某些属性在生成页面时需要用到不能忽略,则放在此处白名单 + return StringUtils.equalsAnyIgnoreCase(javaField, "parentId", "orderNum", "remark"); + } + + public String readConverterExp() { + String remarks = StringUtils.substringBetween(this.columnComment, "(", ")"); + StringBuffer sb = new StringBuffer(); + if (StringUtils.isNotEmpty(remarks)) { + for (String value : remarks.split(" ")) { + if (StringUtils.isNotEmpty(value)) { + Object startStr = value.subSequence(0, 1); + String endStr = value.substring(1); + sb.append("").append(startStr).append("=").append(endStr).append(","); + } + } + return sb.deleteCharAt(sb.length() - 1).toString(); + } else { + return this.columnComment; + } + } +} diff --git a/boyue-models/boyue-generator/src/main/java/com/boyue/generator/domain/vo/GenTableVo.java b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/domain/vo/GenTableVo.java new file mode 100644 index 0000000..c477385 --- /dev/null +++ b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/domain/vo/GenTableVo.java @@ -0,0 +1,108 @@ +package com.boyue.generator.domain.vo; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.boyue.common.core.domain.BaseEntity; +import com.boyue.generator.domain.GenJoinTable; +import com.boyue.generator.domain.GenTable; +import com.boyue.generator.domain.GenTableColumn; + +import jakarta.validation.Valid; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Setter; + +/** + * 业务表 gen_table + * + * @author boyue + */ +@Data +@Setter +@EqualsAndHashCode(callSuper = true) +public class GenTableVo extends BaseEntity { + private static final long serialVersionUID = 1L; + + /** 业务表 */ + @Valid + private GenTable table; + + /** 业务表的列 */ + @Valid + private List columns; + + /** 关联信息 */ + @Valid + private List joinTablesMate; + + /** 参与关联的表 */ + private Collection joinTables; + + /** 参与关联的列 */ + private List joinColumns; + + public List getAllGenTables() { + List allGenTables = new ArrayList<>(); + allGenTables.add(table); + allGenTables.addAll(joinTables); + return allGenTables; + } + + public List getAllGenTableColumns() { + List allGenTableColumns = new ArrayList<>(); + if (columns != null) { + allGenTableColumns.addAll(columns); + } + if (joinColumns != null) { + allGenTableColumns.addAll(joinColumns); + } + return allGenTableColumns; + } + + public Map getTableMap() { + Map tableMap = new HashMap<>(); + if (table != null) { + tableMap.put(table.getTableId(), table); + } + if (joinTables != null) { + for (GenTable genTable : joinTables) { + if (genTable != null) { + tableMap.put(genTable.getTableId(), genTable); + } + } + } + return tableMap; + } + + public Map getTableAliasMap() { + Map tableMap = new HashMap<>(); + if (table != null) { + tableMap.put(table.getTableId(), table.getTableAlias()); + } + if (joinTablesMate != null) { + for (GenJoinTable genTable : joinTablesMate) { + if (genTable != null) { + tableMap.put(genTable.getLeftTableId(), genTable.getLeftTableAlias()); + tableMap.put(genTable.getRightTableId(), genTable.getRightTableAlias()); + } + } + } + return tableMap; + } + + public Map getColumnMap() { + Map columnMap = new HashMap<>(); + List genTables = getAllGenTables(); + for (GenTable genTable : genTables) { + for (GenTableColumn genTableColumn : genTable.getColumns()) { + columnMap.put(genTableColumn.getColumnId(), genTableColumn); + } + } + return columnMap; + } + +} \ No newline at end of file diff --git a/boyue-models/boyue-generator/src/main/java/com/boyue/generator/mapper/GenJoinTableMapper.java b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/mapper/GenJoinTableMapper.java new file mode 100644 index 0000000..99a52ad --- /dev/null +++ b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/mapper/GenJoinTableMapper.java @@ -0,0 +1,40 @@ +package com.boyue.generator.mapper; + +import java.util.List; + +import com.boyue.generator.domain.GenJoinTable; + +/** + * 代码生成关联字段Mapper接口 + * + * @author boyue + * @date 2025-02-19 + */ +public interface GenJoinTableMapper { + /** + * 查询代码生成关联字段列表 + * + * @param genJoinTable 代码生成关联字段 + * @return 代码生成关联字段集合 + */ + public List selectGenJoinTableList(GenJoinTable genJoinTable); + + /** + * 新增代码生成关联字段 + * + * @param genJoinTable 代码生成关联字段 + * @return 结果 + */ + public int insertGenJoinTable(GenJoinTable genJoinTable); + + /** + * 修改代码生成关联字段 + * + * @param genJoinTable 代码生成关联字段 + * @return 结果 + */ + public int updateGenJoinTable(GenJoinTable genJoinTable); + + public int deleteGenJoinTableByTableId(Long tableId); + +} diff --git a/boyue-models/boyue-generator/src/main/java/com/boyue/generator/mapper/GenTableColumnMapper.java b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/mapper/GenTableColumnMapper.java new file mode 100644 index 0000000..3267b06 --- /dev/null +++ b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/mapper/GenTableColumnMapper.java @@ -0,0 +1,60 @@ +package com.boyue.generator.mapper; + +import java.util.List; +import com.boyue.generator.domain.GenTableColumn; + +/** + * 业务字段 数据层 + * + * @author boyue + */ +public interface GenTableColumnMapper +{ + /** + * 根据表名称查询列信息 + * + * @param tableName 表名称 + * @return 列信息 + */ + public List selectDbTableColumnsByName(String tableName); + + /** + * 查询业务字段列表 + * + * @param tableId 业务字段编号 + * @return 业务字段集合 + */ + public List selectGenTableColumnListByTableId(Long tableId); + + /** + * 新增业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int insertGenTableColumn(GenTableColumn genTableColumn); + + /** + * 修改业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int updateGenTableColumn(GenTableColumn genTableColumn); + + /** + * 删除业务字段 + * + * @param genTableColumns 列数据 + * @return 结果 + */ + public int deleteGenTableColumns(List genTableColumns); + + /** + * 批量删除业务字段 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteGenTableColumnByIds(Long[] ids); +} diff --git a/boyue-models/boyue-generator/src/main/java/com/boyue/generator/mapper/GenTableMapper.java b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/mapper/GenTableMapper.java new file mode 100644 index 0000000..f6b0328 --- /dev/null +++ b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/mapper/GenTableMapper.java @@ -0,0 +1,90 @@ +package com.boyue.generator.mapper; + +import java.util.List; +import com.boyue.generator.domain.GenTable; + +/** + * 业务 数据层 + * + * @author boyue + */ +public interface GenTableMapper { + /** + * 查询业务列表 + * + * @param genTable 业务信息 + * @return 业务集合 + */ + public List selectGenTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param genTable 业务信息 + * @return 数据库表集合 + */ + public List selectDbTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param tableNames 表名称组 + * @return 数据库表集合 + */ + public List selectDbTableListByNames(String[] tableNames); + + /** + * 查询所有表信息 + * + * @return 表信息集合 + */ + public List selectGenTableAll(); + + /** + * 查询表ID业务信息 + * + * @param id 业务ID + * @return 业务信息 + */ + public GenTable selectGenTableById(Long id); + + /** + * 查询表名称业务信息 + * + * @param tableName 表名称 + * @return 业务信息 + */ + public GenTable selectGenTableByName(String tableName); + + /** + * 新增业务 + * + * @param genTable 业务信息 + * @return 结果 + */ + public int insertGenTable(GenTable genTable); + + /** + * 修改业务 + * + * @param genTable 业务信息 + * @return 结果 + */ + public int updateGenTable(GenTable genTable); + + /** + * 批量删除业务 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteGenTableByIds(Long[] ids); + + /** + * 创建表 + * + * @param sql 表结构 + * @return 结果 + */ + public int createTable(String sql); +} diff --git a/boyue-models/boyue-generator/src/main/java/com/boyue/generator/service/GenJoinTableServiceImpl.java b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/service/GenJoinTableServiceImpl.java new file mode 100644 index 0000000..3f81e85 --- /dev/null +++ b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/service/GenJoinTableServiceImpl.java @@ -0,0 +1,145 @@ +package com.boyue.generator.service; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.boyue.common.exception.ServiceException; +import com.boyue.common.utils.DateUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.generator.constant.GenConstants; +import com.boyue.generator.domain.GenJoinTable; +import com.boyue.generator.domain.GenTable; +import com.boyue.generator.domain.GenTableColumn; +import com.boyue.generator.domain.vo.GenTableVo; +import com.boyue.generator.mapper.GenJoinTableMapper; +import com.boyue.generator.mapper.GenTableMapper; + +/** + * 代码生成关联字段Service业务层处理 + * + * @author boyue + * @date 2025-02-19 + */ +@Service +public class GenJoinTableServiceImpl implements IGenJoinTableService { + @Autowired + private GenJoinTableMapper genJoinTableMapper; + + @Autowired + private GenTableMapper genTableMapper; + + /** + * 查询代码生成关联字段列表 + * + * @param genJoinTable 代码生成关联字段 + * @return 代码生成关联字段 + */ + @Override + public List selectGenJoinTableList(GenJoinTable genJoinTable) { + return genJoinTableMapper.selectGenJoinTableList(genJoinTable); + } + + public GenTable selectGenTableById(Long id) { + GenTable genTable = genTableMapper.selectGenTableById(id); + setTableFromOptions(genTable); + return genTable; + } + + /** + * 设置代码生成其他选项值 + * + * @param genTable 设置后的生成对象 + */ + public void setTableFromOptions(GenTable genTable) { + JSONObject paramsObj = JSON.parseObject(genTable.getOptions()); + if (StringUtils.isNotNull(paramsObj)) { + String treeCode = paramsObj.getString(GenConstants.TREE_CODE); + String treeParentCode = paramsObj.getString(GenConstants.TREE_PARENT_CODE); + String treeName = paramsObj.getString(GenConstants.TREE_NAME); + String parentMenuId = paramsObj.getString(GenConstants.PARENT_MENU_ID); + String parentMenuName = paramsObj.getString(GenConstants.PARENT_MENU_NAME); + + genTable.setTreeCode(treeCode); + genTable.setTreeParentCode(treeParentCode); + genTable.setTreeName(treeName); + genTable.setParentMenuId(parentMenuId); + genTable.setParentMenuName(parentMenuName); + } + } + + @Override + public GenTableVo selectGenJoinTableVoListByGenTable(GenTable table) { + GenTableVo genTableVo = new GenTableVo(); + genTableVo.setTable(table); + genTableVo.setColumns(table.getColumns()); + + GenJoinTable genJoinTable = new GenJoinTable(); + genJoinTable.setTableId(table.getTableId()); + List selectGenJoinTableList = this.selectGenJoinTableList(genJoinTable); + genTableVo.setJoinTablesMate(selectGenJoinTableList); + + List joinColumns = new ArrayList(); + Map joinTableMap = new HashMap(); + joinTableMap.put(table.getTableId(), table); + selectGenJoinTableList.forEach(i -> { + if (Objects.isNull(joinTableMap.get(i.getLeftTableId()))) { + joinTableMap.put(i.getLeftTableId(), this.selectGenTableById(i.getLeftTableId())); + } + if (Objects.isNull(joinTableMap.get(i.getRightTableId()))) { + joinTableMap.put(i.getRightTableId(), this.selectGenTableById(i.getRightTableId())); + } + GenTable newTable = joinTableMap.get(i.getNewTableId()); + if(Objects.isNull(newTable)) throw new ServiceException("关联表不存在"); + List joinColumnNames = i.getJoinColumns(); + if(Objects.isNull(joinColumnNames)) return; + newTable.getColumns().forEach(j -> { + if (joinColumnNames.contains(j.getColumnName())) { + joinColumns.add(j); + } + }); + }); + genTableVo.setJoinColumns(joinColumns); + genTableVo.setJoinTables(joinTableMap.values()); + return genTableVo; + } + + /** + * 新增代码生成关联字段 + * + * @param genJoinTable 代码生成关联字段 + * @return 结果 + */ + @Override + public int insertGenJoinTable(GenJoinTable genJoinTable) { + genJoinTable.setCreateTime(DateUtils.getNowDate()); + return genJoinTableMapper.insertGenJoinTable(genJoinTable); + } + + /** + * 修改代码生成关联字段 + * + * @param genJoinTable 代码生成关联字段 + * @return 结果 + */ + @Override + public int updateGenJoinTable(GenJoinTable genJoinTable) { + genJoinTable.setUpdateTime(DateUtils.getNowDate()); + return genJoinTableMapper.updateGenJoinTable(genJoinTable); + } + + /** + * 根据tableId删除字段关联 + */ + public int deleteGenJoinTableByTableId(Long tableId) { + return genJoinTableMapper.deleteGenJoinTableByTableId(tableId); + } + +} diff --git a/boyue-models/boyue-generator/src/main/java/com/boyue/generator/service/GenTableColumnServiceImpl.java b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/service/GenTableColumnServiceImpl.java new file mode 100644 index 0000000..c4f0bf8 --- /dev/null +++ b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/service/GenTableColumnServiceImpl.java @@ -0,0 +1,68 @@ +package com.boyue.generator.service; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.boyue.common.core.text.Convert; +import com.boyue.generator.domain.GenTableColumn; +import com.boyue.generator.mapper.GenTableColumnMapper; + +/** + * 业务字段 服务层实现 + * + * @author boyue + */ +@Service +public class GenTableColumnServiceImpl implements IGenTableColumnService +{ + @Autowired + private GenTableColumnMapper genTableColumnMapper; + + /** + * 查询业务字段列表 + * + * @param tableId 业务字段编号 + * @return 业务字段集合 + */ + @Override + public List selectGenTableColumnListByTableId(Long tableId) + { + return genTableColumnMapper.selectGenTableColumnListByTableId(tableId); + } + + /** + * 新增业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + @Override + public int insertGenTableColumn(GenTableColumn genTableColumn) + { + return genTableColumnMapper.insertGenTableColumn(genTableColumn); + } + + /** + * 修改业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + @Override + public int updateGenTableColumn(GenTableColumn genTableColumn) + { + return genTableColumnMapper.updateGenTableColumn(genTableColumn); + } + + /** + * 删除业务字段对象 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + @Override + public int deleteGenTableColumnByIds(String ids) + { + return genTableColumnMapper.deleteGenTableColumnByIds(Convert.toLongArray(ids)); + } +} diff --git a/boyue-models/boyue-generator/src/main/java/com/boyue/generator/service/GenTableServiceImpl.java b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/service/GenTableServiceImpl.java new file mode 100644 index 0000000..99fc804 --- /dev/null +++ b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/service/GenTableServiceImpl.java @@ -0,0 +1,497 @@ +package com.boyue.generator.service; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.Velocity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.boyue.common.constant.Constants; +import com.boyue.common.core.text.CharsetKit; +import com.boyue.common.exception.ServiceException; +import com.boyue.common.utils.StringUtils; +import com.boyue.generator.constant.GenConstants; +import com.boyue.generator.domain.GenTable; +import com.boyue.generator.domain.GenTableColumn; +import com.boyue.generator.domain.vo.GenTableVo; +import com.boyue.generator.mapper.GenTableColumnMapper; +import com.boyue.generator.mapper.GenTableMapper; +import com.boyue.generator.util.GenUtils; +import com.boyue.generator.util.VelocityInitializer; +import com.boyue.generator.util.VelocityUtils; + +/** + * 业务 服务层实现 + * + * @author boyue + */ +@Service +public class GenTableServiceImpl implements IGenTableService { + private static final Logger log = LoggerFactory.getLogger(GenTableServiceImpl.class); + + @Autowired + private GenTableMapper genTableMapper; + + @Autowired + private GenTableColumnMapper genTableColumnMapper; + + @Autowired + private IGenJoinTableService genJoinTableService; + + /** + * 查询业务信息 + * + * @param id 业务ID + * @return 业务信息 + */ + @Override + public GenTable selectGenTableById(Long id) { + GenTable genTable = genTableMapper.selectGenTableById(id); + setTableFromOptions(genTable); + return genTable; + } + + /** + * 查询业务列表 + * + * @param genTable 业务信息 + * @return 业务集合 + */ + @Override + public List selectGenTableList(GenTable genTable) { + return genTableMapper.selectGenTableList(genTable); + } + + /** + * 查询据库列表 + * + * @param genTable 业务信息 + * @return 数据库表集合 + */ + @Override + public List selectDbTableList(GenTable genTable) { + return genTableMapper.selectDbTableList(genTable); + } + + /** + * 查询据库列表 + * + * @param tableNames 表名称组 + * @return 数据库表集合 + */ + @Override + public List selectDbTableListByNames(String[] tableNames) { + List genTables = genTableMapper.selectDbTableListByNames(tableNames); + genTables.forEach(i -> i.setTableAlias(generateTableAlias(i.getTableName()))); + return genTables; + } + + public static String generateTableAlias(String tableName) { + if (StringUtils.isEmpty(tableName)) { + return "t"; + } + + // 改进的正则表达式,更准确地处理所有三种情况 + Pattern pattern = Pattern.compile("([A-Z][a-z0-9]*)|([a-z0-9]+)(?=[A-Z])|([a-z0-9]+)(?=_)|([a-z0-9]+)$"); + Matcher matcher = pattern.matcher(tableName); + StringBuilder alias = new StringBuilder(); + + while (matcher.find()) { + String word = matcher.group(); + if (!word.isEmpty()) { + alias.append(word.charAt(0)); + } + } + + return alias.toString().toLowerCase(); + } + + /** + * 查询所有表信息 + * + * @return 表信息集合 + */ + @Override + public List selectGenTableAll() { + return genTableMapper.selectGenTableAll(); + } + + /** + * 修改业务 + * + * @param genTable 业务信息 + * @return 结果 + */ + @Override + @Transactional + public void updateGenTable(GenTable genTable) { + String options = JSON.toJSONString(genTable.getParams()); + genTable.setOptions(options); + int row = genTableMapper.updateGenTable(genTable); + if (row > 0) { + for (GenTableColumn cenTableColumn : genTable.getColumns()) { + genTableColumnMapper.updateGenTableColumn(cenTableColumn); + } + } + } + + /** + * 删除业务对象 + * + * @param tableIds 需要删除的数据ID + * @return 结果 + */ + @Override + @Transactional + public void deleteGenTableByIds(Long[] tableIds) { + genTableMapper.deleteGenTableByIds(tableIds); + genTableColumnMapper.deleteGenTableColumnByIds(tableIds); + } + + /** + * 创建表 + * + * @param sql 创建表语句 + * @return 结果 + */ + @Override + public boolean createTable(String sql) { + return genTableMapper.createTable(sql) == 0; + } + + /** + * 导入表结构 + * + * @param tableList 导入表列表 + */ + @Override + @Transactional + public void importGenTable(List tableList, String operName) { + try { + for (GenTable table : tableList) { + String tableName = table.getTableName(); + GenUtils.initTable(table, operName); + int row = genTableMapper.insertGenTable(table); + if (row > 0) { + // 保存列信息 + List genTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName); + for (GenTableColumn column : genTableColumns) { + GenUtils.initColumnField(column, table); + genTableColumnMapper.insertGenTableColumn(column); + } + } + } + } catch (Exception e) { + throw new ServiceException("导入失败:" + e.getMessage()); + } + } + + /** + * 预览代码 + * + * @param tableId 表编号 + * @return 预览数据列表 + */ + @Override + public Map previewCode(Long tableId) { + Map dataMap = new LinkedHashMap<>(); + // 查询表信息 + GenTable table = genTableMapper.selectGenTableById(tableId); + // 设置主子表信息 + setSubTable(table); + // 设置主键列信息 + setPkColumn(table); + VelocityInitializer.initVelocity(); + GenTableVo genTableVo = genJoinTableService.selectGenJoinTableVoListByGenTable(table); + + VelocityContext context = VelocityUtils.prepareContext(genTableVo); + + // 获取模板列表 + List templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType()); + for (String template : templates) { + // 渲染模板 + StringWriter sw = new StringWriter(); + Template tpl = Velocity.getTemplate(template, Constants.UTF8); + tpl.merge(context, sw); + dataMap.put(template, sw.toString()); + } + return dataMap; + } + + /** + * 生成代码(下载方式) + * + * @param tableName 表名称 + * @return 数据 + */ + @Override + public byte[] downloadCode(String tableName) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ZipOutputStream zip = new ZipOutputStream(outputStream); + generatorCode(tableName, zip); + IOUtils.closeQuietly(zip); + return outputStream.toByteArray(); + } + + /** + * 生成代码(自定义路径) + * + * @param tableName 表名称 + */ + @Override + public void generatorCode(String tableName) { + // 查询表信息 + GenTable table = genTableMapper.selectGenTableByName(tableName); + // 设置主子表信息 + setSubTable(table); + // 设置主键列信息 + setPkColumn(table); + + VelocityInitializer.initVelocity(); + GenTableVo genTableVo = genJoinTableService.selectGenJoinTableVoListByGenTable(table); + + VelocityContext context = VelocityUtils.prepareContext(genTableVo); + + // 获取模板列表 + List templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType()); + for (String template : templates) { + // 渲染模板 + StringWriter sw = new StringWriter(); + Template tpl = Velocity.getTemplate(template, Constants.UTF8); + tpl.merge(context, sw); + try { + String path = getGenPath(table, template); + FileUtils.writeStringToFile(new File(path), sw.toString(), CharsetKit.UTF_8); + } catch (IOException e) { + throw new ServiceException("渲染模板失败,表名:" + table.getTableName()); + } + } + } + + /** + * 同步数据库 + * + * @param tableName 表名称 + */ + @Override + @Transactional + public void synchDb(String tableName) { + GenTable table = genTableMapper.selectGenTableByName(tableName); + List tableColumns = table.getColumns(); + Map tableColumnMap = tableColumns.stream() + .collect(Collectors.toMap(GenTableColumn::getColumnName, Function.identity())); + + List dbTableColumns = genTableColumnMapper.selectDbTableColumnsByName(tableName); + if (StringUtils.isEmpty(dbTableColumns)) { + throw new ServiceException("同步数据失败,原表结构不存在"); + } + List dbTableColumnNames = dbTableColumns.stream().map(GenTableColumn::getColumnName) + .collect(Collectors.toList()); + + dbTableColumns.forEach(column -> { + GenUtils.initColumnField(column, table); + if (tableColumnMap.containsKey(column.getColumnName())) { + GenTableColumn prevColumn = tableColumnMap.get(column.getColumnName()); + column.setColumnId(prevColumn.getColumnId()); + if (column.isList()) { + // 如果是列表,继续保留查询方式/字典类型选项 + column.setDictType(prevColumn.getDictType()); + column.setQueryType(prevColumn.getQueryType()); + } + if (StringUtils.isNotEmpty(prevColumn.getIsRequired()) && !column.isPk() + && (column.isInsert() || column.isEdit()) + && ((column.isUsableColumn()) || (!column.isSuperColumn()))) { + // 如果是(新增/修改&非主键/非忽略及父属性),继续保留必填/显示类型选项 + column.setIsRequired(prevColumn.getIsRequired()); + column.setHtmlType(prevColumn.getHtmlType()); + } + genTableColumnMapper.updateGenTableColumn(column); + } else { + genTableColumnMapper.insertGenTableColumn(column); + } + }); + + List delColumns = tableColumns.stream() + .filter(column -> !dbTableColumnNames.contains(column.getColumnName())).collect(Collectors.toList()); + if (StringUtils.isNotEmpty(delColumns)) { + genTableColumnMapper.deleteGenTableColumns(delColumns); + } + } + + /** + * 批量生成代码(下载方式) + * + * @param tableNames 表数组 + * @return 数据 + */ + @Override + public byte[] downloadCode(String[] tableNames) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ZipOutputStream zip = new ZipOutputStream(outputStream); + for (String tableName : tableNames) { + generatorCode(tableName, zip); + } + IOUtils.closeQuietly(zip); + return outputStream.toByteArray(); + } + + /** + * 查询表信息并生成代码 + */ + private void generatorCode(String tableName, ZipOutputStream zip) { + // 查询表信息 + GenTable table = genTableMapper.selectGenTableByName(tableName); + // 设置主子表信息 + setSubTable(table); + // 设置主键列信息 + setPkColumn(table); + + VelocityInitializer.initVelocity(); + GenTableVo genTableVo = genJoinTableService.selectGenJoinTableVoListByGenTable(table); + VelocityContext context = VelocityUtils.prepareContext(genTableVo); + + // 获取模板列表 + List templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getTplWebType()); + for (String template : templates) { + // 渲染模板 + StringWriter sw = new StringWriter(); + Template tpl = Velocity.getTemplate(template, Constants.UTF8); + tpl.merge(context, sw); + try { + // 添加到zip + zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template, table))); + IOUtils.write(sw.toString(), zip, Constants.UTF8); + IOUtils.closeQuietly(sw); + zip.flush(); + zip.closeEntry(); + } catch (IOException e) { + log.error("渲染模板失败,表名:" + table.getTableName(), e); + } + } + } + + /** + * 修改保存参数校验 + * + * @param genTable 业务信息 + */ + @Override + public void validateEdit(GenTable genTable) { + if (GenConstants.TPL_TREE.equals(genTable.getTplCategory())) { + String options = JSON.toJSONString(genTable.getParams()); + JSONObject paramsObj = JSON.parseObject(options); + if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_CODE))) { + throw new ServiceException("树编码字段不能为空"); + } else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_PARENT_CODE))) { + throw new ServiceException("树父编码字段不能为空"); + } else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_NAME))) { + throw new ServiceException("树名称字段不能为空"); + } else if (GenConstants.TPL_SUB.equals(genTable.getTplCategory())) { + if (StringUtils.isEmpty(genTable.getSubTableName())) { + throw new ServiceException("关联子表的表名不能为空"); + } else if (StringUtils.isEmpty(genTable.getSubTableFkName())) { + throw new ServiceException("子表关联的外键名不能为空"); + } + } + } + } + + /** + * 设置主键列信息 + * + * @param table 业务表信息 + */ + public void setPkColumn(GenTable table) { + for (GenTableColumn column : table.getColumns()) { + if (column.isPk()) { + table.setPkColumn(column); + break; + } + } + if (StringUtils.isNull(table.getPkColumn())) { + table.setPkColumn(table.getColumns().get(0)); + } + if (GenConstants.TPL_SUB.equals(table.getTplCategory())) { + for (GenTableColumn column : table.getSubTable().getColumns()) { + if (column.isPk()) { + table.getSubTable().setPkColumn(column); + break; + } + } + if (StringUtils.isNull(table.getSubTable().getPkColumn())) { + table.getSubTable().setPkColumn(table.getSubTable().getColumns().get(0)); + } + } + } + + /** + * 设置主子表信息 + * + * @param table 业务表信息 + */ + public void setSubTable(GenTable table) { + String subTableName = table.getSubTableName(); + if (StringUtils.isNotEmpty(subTableName)) { + table.setSubTable(genTableMapper.selectGenTableByName(subTableName)); + } + } + + /** + * 设置代码生成其他选项值 + * + * @param genTable 设置后的生成对象 + */ + public void setTableFromOptions(GenTable genTable) { + JSONObject paramsObj = JSON.parseObject(genTable.getOptions()); + if (StringUtils.isNotNull(paramsObj)) { + String treeCode = paramsObj.getString(GenConstants.TREE_CODE); + String treeParentCode = paramsObj.getString(GenConstants.TREE_PARENT_CODE); + String treeName = paramsObj.getString(GenConstants.TREE_NAME); + String parentMenuId = paramsObj.getString(GenConstants.PARENT_MENU_ID); + String parentMenuName = paramsObj.getString(GenConstants.PARENT_MENU_NAME); + + genTable.setTreeCode(treeCode); + genTable.setTreeParentCode(treeParentCode); + genTable.setTreeName(treeName); + genTable.setParentMenuId(parentMenuId); + genTable.setParentMenuName(parentMenuName); + } + } + + /** + * 获取代码生成地址 + * + * @param table 业务表信息 + * @param template 模板文件路径 + * @return 生成地址 + */ + public static String getGenPath(GenTable table, String template) { + String genPath = table.getGenPath(); + if (StringUtils.equals(genPath, "/")) { + return System.getProperty("user.dir") + File.separator + "src" + File.separator + + VelocityUtils.getFileName(template, table); + } + return genPath + File.separator + VelocityUtils.getFileName(template, table); + } +} \ No newline at end of file diff --git a/boyue-models/boyue-generator/src/main/java/com/boyue/generator/service/IGenJoinTableService.java b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/service/IGenJoinTableService.java new file mode 100644 index 0000000..10aa305 --- /dev/null +++ b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/service/IGenJoinTableService.java @@ -0,0 +1,46 @@ +package com.boyue.generator.service; + +import java.util.List; + +import com.boyue.generator.domain.GenJoinTable; +import com.boyue.generator.domain.GenTable; +import com.boyue.generator.domain.vo.GenTableVo; + +/** + * 代码生成关联字段Service接口 + * + * @author boyue + * @date 2025-02-19 + */ +public interface IGenJoinTableService { + /** + * 查询代码生成关联字段列表 + * + * @param genJoinTable 代码生成关联字段 + * @return 代码生成关联字段集合 + */ + public List selectGenJoinTableList(GenJoinTable genJoinTable); + + /** + * 新增代码生成关联字段 + * + * @param genJoinTable 代码生成关联字段 + * @return 结果 + */ + public int insertGenJoinTable(GenJoinTable genJoinTable); + + /** + * 修改代码生成关联字段 + * + * @param genJoinTable 代码生成关联字段 + * @return 结果 + */ + public int updateGenJoinTable(GenJoinTable genJoinTable); + + /** + * 根据tableId删除字段关联 + */ + public int deleteGenJoinTableByTableId(Long tableId); + + public GenTableVo selectGenJoinTableVoListByGenTable(GenTable table); +} diff --git a/boyue-models/boyue-generator/src/main/java/com/boyue/generator/service/IGenTableColumnService.java b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/service/IGenTableColumnService.java new file mode 100644 index 0000000..e895fb6 --- /dev/null +++ b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/service/IGenTableColumnService.java @@ -0,0 +1,44 @@ +package com.boyue.generator.service; + +import java.util.List; +import com.boyue.generator.domain.GenTableColumn; + +/** + * 业务字段 服务层 + * + * @author boyue + */ +public interface IGenTableColumnService +{ + /** + * 查询业务字段列表 + * + * @param tableId 业务字段编号 + * @return 业务字段集合 + */ + public List selectGenTableColumnListByTableId(Long tableId); + + /** + * 新增业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int insertGenTableColumn(GenTableColumn genTableColumn); + + /** + * 修改业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int updateGenTableColumn(GenTableColumn genTableColumn); + + /** + * 删除业务字段信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteGenTableColumnByIds(String ids); +} diff --git a/boyue-models/boyue-generator/src/main/java/com/boyue/generator/service/IGenTableService.java b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/service/IGenTableService.java new file mode 100644 index 0000000..647d257 --- /dev/null +++ b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/service/IGenTableService.java @@ -0,0 +1,128 @@ +package com.boyue.generator.service; + +import java.util.List; +import java.util.Map; +import com.boyue.generator.domain.GenTable; + +/** + * 业务 服务层 + * + * @author boyue + */ +public interface IGenTableService { + /** + * 查询业务列表 + * + * @param genTable 业务信息 + * @return 业务集合 + */ + public List selectGenTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param genTable 业务信息 + * @return 数据库表集合 + */ + public List selectDbTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param tableNames 表名称组 + * @return 数据库表集合 + */ + public List selectDbTableListByNames(String[] tableNames); + + /** + * 查询所有表信息 + * + * @return 表信息集合 + */ + public List selectGenTableAll(); + + /** + * 查询业务信息 + * + * @param id 业务ID + * @return 业务信息 + */ + public GenTable selectGenTableById(Long id); + + /** + * 修改业务 + * + * @param genTable 业务信息 + * @return 结果 + */ + public void updateGenTable(GenTable genTable); + + /** + * 删除业务信息 + * + * @param tableIds 需要删除的表数据ID + * @return 结果 + */ + public void deleteGenTableByIds(Long[] tableIds); + + /** + * 创建表 + * + * @param sql 创建表语句 + * @return 结果 + */ + public boolean createTable(String sql); + + /** + * 导入表结构 + * + * @param tableList 导入表列表 + */ + public void importGenTable(List tableList, String operName); + + /** + * 预览代码 + * + * @param tableId 表编号 + * @return 预览数据列表 + */ + public Map previewCode(Long tableId); + + /** + * 生成代码(下载方式) + * + * @param tableName 表名称 + * @return 数据 + */ + public byte[] downloadCode(String tableName); + + /** + * 生成代码(自定义路径) + * + * @param tableName 表名称 + * @return 数据 + */ + public void generatorCode(String tableName); + + /** + * 同步数据库 + * + * @param tableName 表名称 + */ + public void synchDb(String tableName); + + /** + * 批量生成代码(下载方式) + * + * @param tableNames 表数组 + * @return 数据 + */ + public byte[] downloadCode(String[] tableNames); + + /** + * 修改保存参数校验 + * + * @param genTable 业务信息 + */ + public void validateEdit(GenTable genTable); +} diff --git a/boyue-models/boyue-generator/src/main/java/com/boyue/generator/util/GenUtils.java b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/util/GenUtils.java new file mode 100644 index 0000000..eecbc59 --- /dev/null +++ b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/util/GenUtils.java @@ -0,0 +1,257 @@ +package com.boyue.generator.util; + +import java.util.Arrays; +import org.apache.commons.lang3.RegExUtils; +import com.boyue.generator.constant.GenConstants; +import com.boyue.common.utils.StringUtils; +import com.boyue.generator.config.GenConfig; +import com.boyue.generator.domain.GenTable; +import com.boyue.generator.domain.GenTableColumn; + +/** + * 代码生成器 工具类 + * + * @author boyue + */ +public class GenUtils +{ + /** + * 初始化表信息 + */ + public static void initTable(GenTable genTable, String operName) + { + genTable.setClassName(convertClassName(genTable.getTableName())); + genTable.setPackageName(GenConfig.getPackageName()); + genTable.setModuleName(getModuleName(GenConfig.getPackageName())); + genTable.setBusinessName(getBusinessName(genTable.getTableName())); + genTable.setFunctionName(replaceText(genTable.getTableComment())); + genTable.setFunctionAuthor(GenConfig.getAuthor()); + genTable.setCreateBy(operName); + } + + /** + * 初始化列属性字段 + */ + public static void initColumnField(GenTableColumn column, GenTable table) + { + String dataType = getDbType(column.getColumnType()); + String columnName = column.getColumnName(); + column.setTableId(table.getTableId()); + column.setCreateBy(table.getCreateBy()); + // 设置java字段名 + column.setJavaField(StringUtils.toCamelCase(columnName)); + // 设置默认类型 + column.setJavaType(GenConstants.TYPE_STRING); + column.setQueryType(GenConstants.QUERY_EQ); + + if (arraysContains(GenConstants.COLUMNTYPE_STR, dataType) || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType)) + { + // 字符串长度超过500设置为文本域 + Integer columnLength = getColumnLength(column.getColumnType()); + String htmlType = columnLength >= 500 || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType) ? GenConstants.HTML_TEXTAREA : GenConstants.HTML_INPUT; + column.setHtmlType(htmlType); + } + else if (arraysContains(GenConstants.COLUMNTYPE_TIME, dataType)) + { + column.setJavaType(GenConstants.TYPE_DATE); + column.setHtmlType(GenConstants.HTML_DATETIME); + } + else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER, dataType)) + { + column.setHtmlType(GenConstants.HTML_INPUT); + + // 如果是浮点型 统一用BigDecimal + String[] str = StringUtils.split(StringUtils.substringBetween(column.getColumnType(), "(", ")"), ","); + if (str != null && str.length == 2 && Integer.parseInt(str[1]) > 0) + { + column.setJavaType(GenConstants.TYPE_BIGDECIMAL); + } + // 如果是整形 + else if (str != null && str.length == 1 && Integer.parseInt(str[0]) <= 10) + { + column.setJavaType(GenConstants.TYPE_INTEGER); + } + // 长整形 + else + { + column.setJavaType(GenConstants.TYPE_LONG); + } + } + + // 插入字段(默认所有字段都需要插入) + column.setIsInsert(GenConstants.REQUIRE); + + // 编辑字段 + if (!arraysContains(GenConstants.COLUMNNAME_NOT_EDIT, columnName) && !column.isPk()) + { + column.setIsEdit(GenConstants.REQUIRE); + } + // 列表字段 + if (!arraysContains(GenConstants.COLUMNNAME_NOT_LIST, columnName) && !column.isPk()) + { + column.setIsList(GenConstants.REQUIRE); + } + // 查询字段 + if (!arraysContains(GenConstants.COLUMNNAME_NOT_QUERY, columnName) && !column.isPk()) + { + column.setIsQuery(GenConstants.REQUIRE); + } + + // 查询字段类型 + if (StringUtils.endsWithIgnoreCase(columnName, "name")) + { + column.setQueryType(GenConstants.QUERY_LIKE); + } + // 状态字段设置单选框 + if (StringUtils.endsWithIgnoreCase(columnName, "status")) + { + column.setHtmlType(GenConstants.HTML_RADIO); + } + // 类型&性别字段设置下拉框 + else if (StringUtils.endsWithIgnoreCase(columnName, "type") + || StringUtils.endsWithIgnoreCase(columnName, "sex")) + { + column.setHtmlType(GenConstants.HTML_SELECT); + } + // 图片字段设置图片上传控件 + else if (StringUtils.endsWithIgnoreCase(columnName, "image")) + { + column.setHtmlType(GenConstants.HTML_IMAGE_UPLOAD); + } + // 文件字段设置文件上传控件 + else if (StringUtils.endsWithIgnoreCase(columnName, "file")) + { + column.setHtmlType(GenConstants.HTML_FILE_UPLOAD); + } + // 内容字段设置富文本控件 + else if (StringUtils.endsWithIgnoreCase(columnName, "content")) + { + column.setHtmlType(GenConstants.HTML_EDITOR); + } + } + + /** + * 校验数组是否包含指定值 + * + * @param arr 数组 + * @param targetValue 值 + * @return 是否包含 + */ + public static boolean arraysContains(String[] arr, String targetValue) + { + return Arrays.asList(arr).contains(targetValue); + } + + /** + * 获取模块名 + * + * @param packageName 包名 + * @return 模块名 + */ + public static String getModuleName(String packageName) + { + int lastIndex = packageName.lastIndexOf("."); + int nameLength = packageName.length(); + return StringUtils.substring(packageName, lastIndex + 1, nameLength); + } + + /** + * 获取业务名 + * + * @param tableName 表名 + * @return 业务名 + */ + public static String getBusinessName(String tableName) + { + int lastIndex = tableName.lastIndexOf("_"); + int nameLength = tableName.length(); + return StringUtils.substring(tableName, lastIndex + 1, nameLength); + } + + /** + * 表名转换成Java类名 + * + * @param tableName 表名称 + * @return 类名 + */ + public static String convertClassName(String tableName) + { + boolean autoRemovePre = GenConfig.getAutoRemovePre(); + String tablePrefix = GenConfig.getTablePrefix(); + if (autoRemovePre && StringUtils.isNotEmpty(tablePrefix)) + { + String[] searchList = StringUtils.split(tablePrefix, ","); + tableName = replaceFirst(tableName, searchList); + } + return StringUtils.convertToCamelCase(tableName); + } + + /** + * 批量替换前缀 + * + * @param replacementm 替换值 + * @param searchList 替换列表 + * @return + */ + public static String replaceFirst(String replacementm, String[] searchList) + { + String text = replacementm; + for (String searchString : searchList) + { + if (replacementm.startsWith(searchString)) + { + text = replacementm.replaceFirst(searchString, ""); + break; + } + } + return text; + } + + /** + * 关键字替换 + * + * @param text 需要被替换的名字 + * @return 替换后的名字 + */ + public static String replaceText(String text) + { + return RegExUtils.replaceAll(text, "(?:表|若依)", ""); + } + + /** + * 获取数据库类型字段 + * + * @param columnType 列类型 + * @return 截取后的列类型 + */ + public static String getDbType(String columnType) + { + if (StringUtils.indexOf(columnType, "(") > 0) + { + return StringUtils.substringBefore(columnType, "("); + } + else + { + return columnType; + } + } + + /** + * 获取字段长度 + * + * @param columnType 列类型 + * @return 截取后的列类型 + */ + public static Integer getColumnLength(String columnType) + { + if (StringUtils.indexOf(columnType, "(") > 0) + { + String length = StringUtils.substringBetween(columnType, "(", ")"); + return Integer.valueOf(length); + } + else + { + return 0; + } + } +} diff --git a/boyue-models/boyue-generator/src/main/java/com/boyue/generator/util/VelocityInitializer.java b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/util/VelocityInitializer.java new file mode 100644 index 0000000..fcec463 --- /dev/null +++ b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/util/VelocityInitializer.java @@ -0,0 +1,34 @@ +package com.boyue.generator.util; + +import java.util.Properties; +import org.apache.velocity.app.Velocity; +import com.boyue.common.constant.Constants; + +/** + * VelocityEngine工厂 + * + * @author boyue + */ +public class VelocityInitializer +{ + /** + * 初始化vm方法 + */ + public static void initVelocity() + { + Properties p = new Properties(); + try + { + // 加载classpath目录下的vm文件 + p.setProperty("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); + // 定义字符集 + p.setProperty(Velocity.INPUT_ENCODING, Constants.UTF8); + // 初始化Velocity引擎,指定配置Properties + Velocity.init(p); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } +} diff --git a/boyue-models/boyue-generator/src/main/java/com/boyue/generator/util/VelocityUtils.java b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/util/VelocityUtils.java new file mode 100644 index 0000000..81679b2 --- /dev/null +++ b/boyue-models/boyue-generator/src/main/java/com/boyue/generator/util/VelocityUtils.java @@ -0,0 +1,448 @@ +package com.boyue.generator.util; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.velocity.VelocityContext; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.boyue.common.utils.DateUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.generator.constant.GenConstants; +import com.boyue.generator.domain.GenTable; +import com.boyue.generator.domain.GenTableColumn; +import com.boyue.generator.domain.vo.GenTableVo; + +/** + * 模板处理工具类 + * + * @author boyue + */ +public class VelocityUtils +{ + /** 项目空间路径 */ + private static final String PROJECT_PATH = "main/java"; + + /** mybatis空间路径 */ + private static final String MYBATIS_PATH = "main/resources/mapper"; + + /** 默认上级菜单,系统工具 */ + private static final String DEFAULT_PARENT_MENU_ID = "3"; + + /** + * 设置模板变量信息 + * + * @return 模板列表 + */ + public static VelocityContext prepareContext(GenTableVo genTableVo) + { + GenTable genTable = genTableVo.getTable(); + String moduleName = genTable.getModuleName(); + String businessName = genTable.getBusinessName(); + String packageName = genTable.getPackageName(); + String tplCategory = genTable.getTplCategory(); + String functionName = genTable.getFunctionName(); + + VelocityContext velocityContext = new VelocityContext(); + velocityContext.put("tplCategory", genTable.getTplCategory()); + velocityContext.put("tableName", genTable.getTableName()); + velocityContext.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "【请填写功能名称】"); + velocityContext.put("ClassName", genTable.getClassName()); + velocityContext.put("className", StringUtils.uncapitalize(genTable.getClassName())); + velocityContext.put("moduleName", genTable.getModuleName()); + velocityContext.put("BusinessName", StringUtils.capitalize(genTable.getBusinessName())); + velocityContext.put("businessName", genTable.getBusinessName()); + velocityContext.put("basePackage", getPackagePrefix(packageName)); + velocityContext.put("packageName", packageName); + velocityContext.put("author", genTable.getFunctionAuthor()); + velocityContext.put("datetime", DateUtils.getDate()); + velocityContext.put("pkColumn", genTable.getPkColumn()); + velocityContext.put("importList", getImportList(genTable)); + velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName)); + velocityContext.put("columns", genTable.getColumns()); + velocityContext.put("table", genTable); + velocityContext.put("tableMap", genTableVo.getTableMap()); + velocityContext.put("tableAliasMap", genTableVo.getTableAliasMap()); + velocityContext.put("columnMap", genTableVo.getColumnMap()); + velocityContext.put("allColumns", genTableVo.getAllGenTableColumns()); + velocityContext.put("joinColunms", genTableVo.getJoinColumns()); + velocityContext.put("joinTablesMate", genTableVo.getJoinTablesMate()); + velocityContext.put("dicts", getDicts(genTable)); + setMenuVelocityContext(velocityContext, genTable); + if (GenConstants.TPL_TREE.equals(tplCategory)) + { + setTreeVelocityContext(velocityContext, genTable); + } + if (GenConstants.TPL_SUB.equals(tplCategory)) + { + setSubVelocityContext(velocityContext, genTable); + } + return velocityContext; + } + + public static void setMenuVelocityContext(VelocityContext context, GenTable genTable) + { + String options = genTable.getOptions(); + JSONObject paramsObj = JSON.parseObject(options); + String parentMenuId = getParentMenuId(paramsObj); + context.put("parentMenuId", parentMenuId); + } + + public static void setTreeVelocityContext(VelocityContext context, GenTable genTable) + { + String options = genTable.getOptions(); + JSONObject paramsObj = JSON.parseObject(options); + String treeCode = getTreecode(paramsObj); + String treeParentCode = getTreeParentCode(paramsObj); + String treeName = getTreeName(paramsObj); + + context.put("treeCode", treeCode); + context.put("treeParentCode", treeParentCode); + context.put("treeName", treeName); + context.put("expandColumn", getExpandColumn(genTable)); + if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) + { + context.put("tree_parent_code", paramsObj.getString(GenConstants.TREE_PARENT_CODE)); + } + if (paramsObj.containsKey(GenConstants.TREE_NAME)) + { + context.put("tree_name", paramsObj.getString(GenConstants.TREE_NAME)); + } + } + + public static void setSubVelocityContext(VelocityContext context, GenTable genTable) + { + GenTable subTable = genTable.getSubTable(); + String subTableName = genTable.getSubTableName(); + String subTableFkName = genTable.getSubTableFkName(); + String subClassName = genTable.getSubTable().getClassName(); + String subTableFkClassName = StringUtils.convertToCamelCase(subTableFkName); + + context.put("subTable", subTable); + context.put("subTableName", subTableName); + context.put("subTableFkName", subTableFkName); + context.put("subTableFkClassName", subTableFkClassName); + context.put("subTableFkclassName", StringUtils.uncapitalize(subTableFkClassName)); + context.put("subClassName", subClassName); + context.put("subclassName", StringUtils.uncapitalize(subClassName)); + context.put("subImportList", getImportList(genTable.getSubTable())); + } + + /** + * 获取模板信息 + * + * @return 模板列表 + */ + public static List getTemplateList(String tplCategory, String tplWebType) + { + String useWebType = "vm/vue/v2"; + if ("element-plus".equals(tplWebType)) + { + useWebType = "vm/vue/v3"; + } + List templates = new ArrayList(); + templates.add("vm/java/domain.java.vm"); + templates.add("vm/java/mapper.java.vm"); + templates.add("vm/java/service.java.vm"); + templates.add("vm/java/serviceImpl.java.vm"); + templates.add("vm/java/controller.java.vm"); + templates.add("vm/xml/mapper.xml.vm"); + templates.add("vm/sql/sql.vm"); + templates.add("vm/js/api.js.vm"); + if (GenConstants.TPL_CRUD.equals(tplCategory)) + { + templates.add(useWebType + "/index.vue.vm"); + } + else if (GenConstants.TPL_TREE.equals(tplCategory)) + { + templates.add(useWebType + "/index-tree.vue.vm"); + } + else if (GenConstants.TPL_SUB.equals(tplCategory)) + { + templates.add(useWebType + "/index.vue.vm"); + templates.add("vm/java/sub-domain.java.vm"); + } + + templates.add("vm/uniapp/edit.vue.vm"); + templates.add("vm/uniapp/list.vue.vm"); + templates.add("vm/uniapp/show.vue.vm"); + return templates; + } + + /** + * 获取文件名 + */ + public static String getFileName(String template, GenTable genTable) + { + // 文件名称 + String fileName = ""; + // 包路径 + String packageName = genTable.getPackageName(); + // 模块名 + String moduleName = genTable.getModuleName(); + // 大写类名 + String className = genTable.getClassName(); + // 业务名称 + String businessName = genTable.getBusinessName(); + + String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/"); + String mybatisPath = MYBATIS_PATH + "/" + moduleName; + String vuePath = "vue"; + String uniPath = "uniapp"; + + if (template.contains("domain.java.vm")) + { + fileName = StringUtils.format("{}/domain/{}.java", javaPath, className); + } + if (template.contains("sub-domain.java.vm") && StringUtils.equals(GenConstants.TPL_SUB, genTable.getTplCategory())) + { + fileName = StringUtils.format("{}/domain/{}.java", javaPath, genTable.getSubTable().getClassName()); + } + else if (template.contains("mapper.java.vm")) + { + fileName = StringUtils.format("{}/mapper/{}Mapper.java", javaPath, className); + } + else if (template.contains("service.java.vm")) + { + fileName = StringUtils.format("{}/service/I{}Service.java", javaPath, className); + } + else if (template.contains("serviceImpl.java.vm")) + { + fileName = StringUtils.format("{}/service/impl/{}ServiceImpl.java", javaPath, className); + } + else if (template.contains("controller.java.vm")) + { + fileName = StringUtils.format("{}/controller/{}Controller.java", javaPath, className); + } + else if (template.contains("mapper.xml.vm")) + { + fileName = StringUtils.format("{}/{}Mapper.xml", mybatisPath, className); + } + else if (template.contains("sql.vm")) + { + fileName = businessName + "Menu.sql"; + } + else if (template.contains("api.js.vm")) + { + fileName = StringUtils.format("{}/api/{}/{}.js", vuePath, moduleName, businessName); + } + else if (template.contains("index.vue.vm")) + { + fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName); + } + else if (template.contains("index-tree.vue.vm")) + { + fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName); + } + else if (template.contains("entity.js.vm")) + { + fileName = StringUtils.format("{}/entity/{}/{}.js", vuePath, moduleName, businessName); + } + else if (template.contains("edit.vue.vm")) + { + fileName = StringUtils.format("{}/pages/{}/{}/edit.vue", uniPath, moduleName, businessName); + } + else if (template.contains("list.vue.vm")) + { + fileName = StringUtils.format("{}/pages/{}/{}/list.vue", uniPath, moduleName, businessName); + } + else if (template.contains("show.vue.vm")) + { + fileName = StringUtils.format("{}/pages/{}/{}/show.vue", uniPath, moduleName, businessName); + } + return fileName; + } + + /** + * 获取包前缀 + * + * @param packageName 包名称 + * @return 包前缀名称 + */ + public static String getPackagePrefix(String packageName) + { + int lastIndex = packageName.lastIndexOf("."); + return StringUtils.substring(packageName, 0, lastIndex); + } + + /** + * 根据列类型获取导入包 + * + * @param genTable 业务表对象 + * @return 返回需要导入的包列表 + */ + public static HashSet getImportList(GenTable genTable) + { + List columns = genTable.getColumns(); + GenTable subGenTable = genTable.getSubTable(); + HashSet importList = new HashSet(); + if (StringUtils.isNotNull(subGenTable)) + { + importList.add("java.util.List"); + } + for (GenTableColumn column : columns) + { + if (!column.isSuperColumn() && GenConstants.TYPE_DATE.equals(column.getJavaType())) + { + importList.add("java.time.LocalDate"); + importList.add("com.fasterxml.jackson.annotation.JsonFormat"); + } + else if (!column.isSuperColumn() && GenConstants.TYPE_TIME.equals(column.getJavaType())) + { + importList.add("java.time.LocalTime"); + importList.add("com.fasterxml.jackson.annotation.JsonFormat"); + } + else if (!column.isSuperColumn() && GenConstants.TYPE_DATETIME.equals(column.getJavaType())) + { + importList.add("java.time.LocalDateTime"); + importList.add("com.fasterxml.jackson.annotation.JsonFormat"); + } + else if (!column.isSuperColumn() && GenConstants.TYPE_BIGDECIMAL.equals(column.getJavaType())) + { + importList.add("java.math.BigDecimal"); + } + } + return importList; + } + + /** + * 根据列类型获取字典组 + * + * @param genTable 业务表对象 + * @return 返回字典组 + */ + public static String getDicts(GenTable genTable) + { + List columns = genTable.getColumns(); + Set dicts = new HashSet(); + addDicts(dicts, columns); + if (StringUtils.isNotNull(genTable.getSubTable())) + { + List subColumns = genTable.getSubTable().getColumns(); + addDicts(dicts, subColumns); + } + return StringUtils.join(dicts, ", "); + } + + /** + * 添加字典列表 + * + * @param dicts 字典列表 + * @param columns 列集合 + */ + public static void addDicts(Set dicts, List columns) + { + for (GenTableColumn column : columns) + { + if (!column.isSuperColumn() && StringUtils.isNotEmpty(column.getDictType()) && StringUtils.equalsAny( + column.getHtmlType(), + new String[] { GenConstants.HTML_SELECT, GenConstants.HTML_RADIO, GenConstants.HTML_CHECKBOX })) + { + dicts.add("'" + column.getDictType() + "'"); + } + } + } + + /** + * 获取权限前缀 + * + * @param moduleName 模块名称 + * @param businessName 业务名称 + * @return 返回权限前缀 + */ + public static String getPermissionPrefix(String moduleName, String businessName) + { + return StringUtils.format("{}:{}", moduleName, businessName); + } + + /** + * 获取上级菜单ID字段 + * + * @param paramsObj 生成其他选项 + * @return 上级菜单ID字段 + */ + public static String getParentMenuId(JSONObject paramsObj) + { + if (StringUtils.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID) + && StringUtils.isNotEmpty(paramsObj.getString(GenConstants.PARENT_MENU_ID))) + { + return paramsObj.getString(GenConstants.PARENT_MENU_ID); + } + return DEFAULT_PARENT_MENU_ID; + } + + /** + * 获取树编码 + * + * @param paramsObj 生成其他选项 + * @return 树编码 + */ + public static String getTreecode(JSONObject paramsObj) + { + if (paramsObj.containsKey(GenConstants.TREE_CODE)) + { + return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_CODE)); + } + return StringUtils.EMPTY; + } + + /** + * 获取树父编码 + * + * @param paramsObj 生成其他选项 + * @return 树父编码 + */ + public static String getTreeParentCode(JSONObject paramsObj) + { + if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) + { + return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_PARENT_CODE)); + } + return StringUtils.EMPTY; + } + + /** + * 获取树名称 + * + * @param paramsObj 生成其他选项 + * @return 树名称 + */ + public static String getTreeName(JSONObject paramsObj) + { + if (paramsObj.containsKey(GenConstants.TREE_NAME)) + { + return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_NAME)); + } + return StringUtils.EMPTY; + } + + /** + * 获取需要在哪一列上面显示展开按钮 + * + * @param genTable 业务表对象 + * @return 展开按钮列序号 + */ + public static int getExpandColumn(GenTable genTable) + { + String options = genTable.getOptions(); + JSONObject paramsObj = JSON.parseObject(options); + String treeName = paramsObj.getString(GenConstants.TREE_NAME); + int num = 0; + for (GenTableColumn column : genTable.getColumns()) + { + if (column.isList()) + { + num++; + String columnName = column.getColumnName(); + if (columnName.equals(treeName)) + { + break; + } + } + } + return num; + } +} diff --git a/boyue-models/boyue-generator/src/main/resources/generator.yml b/boyue-models/boyue-generator/src/main/resources/generator.yml new file mode 100644 index 0000000..cf4f07f --- /dev/null +++ b/boyue-models/boyue-generator/src/main/resources/generator.yml @@ -0,0 +1,10 @@ +# 基础配置 +gen: + # 作者 + author: boyue + # 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool + packageName: com.boyue.system + # 自动去除表前缀,默认是false + autoRemovePre: false + # 表前缀(生成类名不会包含表前缀,多个用逗号分隔) + tablePrefix: sys_ diff --git a/boyue-models/boyue-generator/src/main/resources/mapper/generator/GenJoinTableMapper.xml b/boyue-models/boyue-generator/src/main/resources/mapper/generator/GenJoinTableMapper.xml new file mode 100644 index 0000000..b1a057e --- /dev/null +++ b/boyue-models/boyue-generator/src/main/resources/mapper/generator/GenJoinTableMapper.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + select table_id, left_table_id, right_table_id, left_table_alias, right_table_alias, left_table_fk, right_table_fk, join_type, join_columns, order_num, new_table_id from gen_join_table + + + + + + insert into gen_join_table + + table_id, + left_table_id, + right_table_id, + left_table_alias, + right_table_alias, + left_table_fk, + right_table_fk, + join_type, + join_columns, + order_num, + new_table_id, + + + #{tableId}, + #{leftTableId}, + #{rightTableId}, + #{leftTableAlias}, + #{rightTableAlias}, + #{leftTableFk}, + #{rightTableFk}, + #{joinType}, + #{joinColumns,typeHandler=com.boyue.common.handler.GenericListTypeHandler$StringList}, + #{orderNum}, + #{newTableId}, + + + + + update gen_join_table + + left_table_id = #{leftTableId}, + right_table_id = #{rightTableId}, + left_table_alias = #{leftTableAlias}, + right_table_alias = #{rightTableAlias}, + left_table_fk = #{leftTableFk}, + right_table_fk = #{rightTableFk}, + join_type = #{joinType}, + join_columns = #{joinColumns,typeHandler=com.boyue.common.handler.GenericListTypeHandler$StringList}, + order_num = #{orderNum}, + new_table_id = #{newTableId}, + + where gen_join_table.table_id = #{tableId} and gen_join_table.right_table_id = #{rightTableId} + + + + delete from gen_join_table where table_id = #{tableId} + + \ No newline at end of file diff --git a/boyue-models/boyue-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml b/boyue-models/boyue-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml new file mode 100644 index 0000000..c3cd889 --- /dev/null +++ b/boyue-models/boyue-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select column_id, table_id, column_name, column_comment, column_type, java_type, java_field, is_pk, is_increment, is_required, is_insert, is_edit, is_list, is_query, query_type, html_type, dict_type, sort,sub_column_table_name,sub_column_fk_name,sub_column_name,sub_column_java_type,sub_column_java_field, create_by, create_time, update_by, update_time from gen_table_column + + + + + + + + insert into gen_table_column ( + table_id, + column_name, + column_comment, + column_type, + java_type, + java_field, + is_pk, + is_increment, + is_required, + is_insert, + is_edit, + is_list, + is_query, + query_type, + html_type, + dict_type, + sort, + sub_column_table_name, + sub_column_fk_name, + sub_column_name, + sub_column_java_field, + sub_column_java_type, + create_by, + create_time + )values( + #{tableId}, + #{columnName}, + #{columnComment}, + #{columnType}, + #{javaType}, + #{javaField}, + #{isPk}, + #{isIncrement}, + #{isRequired}, + #{isInsert}, + #{isEdit}, + #{isList}, + #{isQuery}, + #{queryType}, + #{htmlType}, + #{dictType}, + #{sort}, + #{subColumnTableName}, + #{subColumnFkName}, + #{subColumnName}, + #{subColumnJavaField}, + #{subColumnJavaType}, + #{createBy}, + sysdate() + ) + + + + update gen_table_column + + column_comment = #{columnComment}, + java_type = #{javaType}, + java_field = #{javaField}, + is_insert = #{isInsert}, + is_edit = #{isEdit}, + is_list = #{isList}, + is_query = #{isQuery}, + is_required = #{isRequired}, + query_type = #{queryType}, + html_type = #{htmlType}, + dict_type = #{dictType}, + sort = #{sort}, + sub_column_table_name = #{subColumnTableName}, + sub_column_fk_name = #{subColumnFkName}, + sub_column_name = #{subColumnName}, + sub_column_java_field = #{subColumnJavaField}, + sub_column_java_type = #{subColumnJavaType}, + update_by = #{updateBy}, + update_time = sysdate() + + where column_id = #{columnId} + + + + delete from gen_table_column where table_id in + + #{tableId} + + + + + delete from gen_table_column where column_id in + + #{item.columnId} + + + + \ No newline at end of file diff --git a/boyue-models/boyue-generator/src/main/resources/mapper/generator/GenTableMapper.xml b/boyue-models/boyue-generator/src/main/resources/mapper/generator/GenTableMapper.xml new file mode 100644 index 0000000..0b8dcce --- /dev/null +++ b/boyue-models/boyue-generator/src/main/resources/mapper/generator/GenTableMapper.xml @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select table_id, table_name,table_alias, table_comment, sub_table_name, sub_table_fk_name, class_name, tpl_category, tpl_web_type, package_name, module_name, business_name, function_name, function_author, gen_type, gen_path, options, have_sub_column,create_by, create_time, update_by, update_time, remark from gen_table + + + + + + + + + + + + + + + + + + insert into gen_table ( + table_name, + table_alias, + table_comment, + class_name, + tpl_category, + tpl_web_type, + package_name, + module_name, + business_name, + function_name, + function_author, + gen_type, + gen_path, + remark, + create_by, + have_sub_column, + create_time + )values( + #{tableName}, + #{tableAlias}, + #{tableComment}, + #{className}, + #{tplCategory}, + #{tplWebType}, + #{packageName}, + #{moduleName}, + #{businessName}, + #{functionName}, + #{functionAuthor}, + #{genType}, + #{genPath}, + #{remark}, + #{haveSubColumn}, + #{createBy}, + sysdate() + ) + + + + ${sql} + + + + update gen_table + + table_name = #{tableName}, + table_alias = #{tableAlias}, + table_comment = #{tableComment}, + sub_table_name = #{subTableName}, + sub_table_fk_name = #{subTableFkName}, + class_name = #{className}, + function_author = #{functionAuthor}, + gen_type = #{genType}, + gen_path = #{genPath}, + tpl_category = #{tplCategory}, + tpl_web_type = #{tplWebType}, + package_name = #{packageName}, + module_name = #{moduleName}, + business_name = #{businessName}, + function_name = #{functionName}, + options = #{options}, + update_by = #{updateBy}, + remark = #{remark}, + have_sub_column = #{haveSubColumn}, + update_time = sysdate() + + where table_id = #{tableId} + + + + delete from gen_table where table_id in + + #{tableId} + + + + \ No newline at end of file diff --git a/boyue-models/boyue-generator/src/main/resources/vm/java/controller.java.vm b/boyue-models/boyue-generator/src/main/resources/vm/java/controller.java.vm new file mode 100644 index 0000000..b37c887 --- /dev/null +++ b/boyue-models/boyue-generator/src/main/resources/vm/java/controller.java.vm @@ -0,0 +1,124 @@ +package ${packageName}.controller; + +import java.util.List; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.enums.BusinessType; +import ${packageName}.domain.${ClassName}; +import ${packageName}.service.I${ClassName}Service; +import com.boyue.common.utils.poi.ExcelUtil; +#if($table.crud || $table.sub) +import com.boyue.common.core.page.TableDataInfo; +#elseif($table.tree) +#end +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; + +/** + * ${functionName}Controller + * + * @author ${author} + * @date ${datetime} + */ +@RestController +@RequestMapping("/${moduleName}/${businessName}") +@Tag(name = "【${functionName}】管理") +public class ${ClassName}Controller extends BaseController +{ + @Autowired + private I${ClassName}Service ${className}Service; + + /** + * 查询${functionName}列表 + */ + @Operation(summary = "查询${functionName}列表") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:list')") + @GetMapping("/list") +#if($table.crud || $table.sub) + public TableDataInfo list(${ClassName} ${className}) + { + startPage(); + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + return getDataTable(list); + } +#elseif($table.tree) + public AjaxResult list(${ClassName} ${className}) + { + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + return success(list); + } +#end + + /** + * 导出${functionName}列表 + */ + @Operation(summary = "导出${functionName}列表") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:export')") + @Log(title = "${functionName}", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, ${ClassName} ${className}) + { + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + ExcelUtil<${ClassName}> util = new ExcelUtil<${ClassName}>(${ClassName}.class); + util.exportExcel(response, list, "${functionName}数据"); + } + + /** + * 获取${functionName}详细信息 + */ + @Operation(summary = "获取${functionName}详细信息") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:query')") + @GetMapping(value = "/{${pkColumn.javaField}}") + public AjaxResult getInfo(@PathVariable("${pkColumn.javaField}") ${pkColumn.javaType} ${pkColumn.javaField}) + { + return success(${className}Service.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField})); + } + + /** + * 新增${functionName} + */ + @Operation(summary = "新增${functionName}") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:add')") + @Log(title = "${functionName}", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody ${ClassName} ${className}) + { + return toAjax(${className}Service.insert${ClassName}(${className})); + } + + /** + * 修改${functionName} + */ + @Operation(summary = "修改${functionName}") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:edit')") + @Log(title = "${functionName}", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody ${ClassName} ${className}) + { + return toAjax(${className}Service.update${ClassName}(${className})); + } + + /** + * 删除${functionName} + */ + @Operation(summary = "删除${functionName}") + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:remove')") + @Log(title = "${functionName}", businessType = BusinessType.DELETE) + @DeleteMapping("/{${pkColumn.javaField}s}") + public AjaxResult remove(@PathVariable( name = "${pkColumn.javaField}s" ) ${pkColumn.javaType}[] ${pkColumn.javaField}s) + { + return toAjax(${className}Service.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s)); + } +} diff --git a/boyue-models/boyue-generator/src/main/resources/vm/java/domain.java.vm b/boyue-models/boyue-generator/src/main/resources/vm/java/domain.java.vm new file mode 100644 index 0000000..4073221 --- /dev/null +++ b/boyue-models/boyue-generator/src/main/resources/vm/java/domain.java.vm @@ -0,0 +1,143 @@ +package ${packageName}.domain; + +#foreach ($import in $importList) +import ${import}; +#end +import io.swagger.v3.oas.annotations.media.Schema; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.boyue.common.annotation.Excel; +#if($table.crud || $table.sub) +#elseif($table.tree) +#end + +/** + * ${functionName}对象 ${tableName} + * + * @author ${author} + * @date ${datetime} + */ +#if($table.crud || $table.sub) +#set($Entity="BaseEntity") +#elseif($table.tree) +#set($Entity="TreeEntity") +#end +@Schema(description = "${functionName}对象") +public class ${ClassName} extends ${Entity} +{ + private static final long serialVersionUID = 1L; + +#foreach ($column in $allColumns) +#if(!$table.isSuperColumn($column.javaField)) + + /** $column.columnComment */ + @Schema(title = "$column.columnComment") +#if($column.list) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end +#if($parentheseIndex != -1) + @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") +#elseif($column.javaType == 'Date') + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd") +#elseif($column.javaType == 'Time') + @JsonFormat(pattern = "HH:mm:ss") + @Excel(name = "${comment}", width = 30, dateFormat = "HH:mm:ss") +#elseif($column.javaType == 'DateTime') + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") +#else + @Excel(name = "${comment}") +#end +#end + private $column.javaType $column.javaField; +#if($column.subColumnTableName && $table.haveSubColumn == '1') + + /** $column.javaField 到 $column.subColumnTableName 映射 */ + private $column.subColumnJavaType $column.subColumnJavaField; +#end +#end +#end +#if($table.sub) + /** $table.subTable.functionName信息 */ + private List<${subClassName}> ${subclassName}List; + +#end +#foreach ($column in $allColumns) +#if(!$table.isSuperColumn($column.javaField)) +#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.javaField) +#else +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#end + public void set${AttrName}($column.javaType $column.javaField) + { + this.$column.javaField = $column.javaField; + } + + public $column.javaType get${AttrName}() + { + return $column.javaField; + } + +#if($column.subColumnTableName && $table.haveSubColumn == '1') +#if($column.subColumnJavaField.length() > 2 && $column.subColumnJavaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.subColumnJavaField) +#else +#set($AttrName=$column.subColumnJavaField.substring(0,1).toUpperCase() + ${column.subColumnJavaField.substring(1)}) +#end + public void set${AttrName}($column.subColumnJavaType $column.subColumnJavaField) + { + this.$column.subColumnJavaField = $column.subColumnJavaField; + } + + public $column.subColumnJavaType get${AttrName}() + { + return $column.subColumnJavaField; + } +#end + +#end +#end + +#if($table.sub) + public List<${subClassName}> get${subClassName}List() + { + return ${subclassName}List; + } + + public void set${subClassName}List(List<${subClassName}> ${subclassName}List) + { + this.${subclassName}List = ${subclassName}List; + } + +#end + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) +#foreach ($column in $allColumns) +#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.javaField) +#else +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#end + .append("${column.javaField}", get${AttrName}()) +#if($column.subColumnTableName && $table.haveSubColumn == '1') +#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.subColumnJavaField) +#else +#set($AttrName=$column.subColumnJavaField.substring(0,1).toUpperCase() + ${column.subColumnJavaField.substring(1)}) +#end + .append("${column.subColumnJavaField}", get${AttrName}()) +#end +#end +#if($table.sub) + .append("${subclassName}List", get${subClassName}List()) +#end + .toString(); + } +} diff --git a/boyue-models/boyue-generator/src/main/resources/vm/java/mapper.java.vm b/boyue-models/boyue-generator/src/main/resources/vm/java/mapper.java.vm new file mode 100644 index 0000000..7e7d7c2 --- /dev/null +++ b/boyue-models/boyue-generator/src/main/resources/vm/java/mapper.java.vm @@ -0,0 +1,91 @@ +package ${packageName}.mapper; + +import java.util.List; +import ${packageName}.domain.${ClassName}; +#if($table.sub) +import ${packageName}.domain.${subClassName}; +#end + +/** + * ${functionName}Mapper接口 + * + * @author ${author} + * @date ${datetime} + */ +public interface ${ClassName}Mapper +{ + /** + * 查询${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 查询${functionName}列表 + * + * @param ${className} ${functionName} + * @return ${functionName}集合 + */ + public List<${ClassName}> select${ClassName}List(${ClassName} ${className}); + + /** + * 新增${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int insert${ClassName}(${ClassName} ${className}); + + /** + * 修改${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int update${ClassName}(${ClassName} ${className}); + + /** + * 删除${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 批量删除${functionName} + * + * @param ${pkColumn.javaField}s 需要删除的数据主键集合 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); +#if($table.sub) + + /** + * 批量删除${subTable.functionName} + * + * @param ${pkColumn.javaField}s 需要删除的数据主键集合 + * @return 结果 + */ + public int delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); + + /** + * 批量新增${subTable.functionName} + * + * @param ${subclassName}List ${subTable.functionName}列表 + * @return 结果 + */ + public int batch${subClassName}(List<${subClassName}> ${subclassName}List); + + + /** + * 通过${functionName}主键删除${subTable.functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}ID + * @return 结果 + */ + public int delete${subClassName}By${subTableFkClassName}(${pkColumn.javaType} ${pkColumn.javaField}); +#end +} diff --git a/boyue-models/boyue-generator/src/main/resources/vm/java/service.java.vm b/boyue-models/boyue-generator/src/main/resources/vm/java/service.java.vm new file mode 100644 index 0000000..264882b --- /dev/null +++ b/boyue-models/boyue-generator/src/main/resources/vm/java/service.java.vm @@ -0,0 +1,61 @@ +package ${packageName}.service; + +import java.util.List; +import ${packageName}.domain.${ClassName}; + +/** + * ${functionName}Service接口 + * + * @author ${author} + * @date ${datetime} + */ +public interface I${ClassName}Service +{ + /** + * 查询${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 查询${functionName}列表 + * + * @param ${className} ${functionName} + * @return ${functionName}集合 + */ + public List<${ClassName}> select${ClassName}List(${ClassName} ${className}); + + /** + * 新增${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int insert${ClassName}(${ClassName} ${className}); + + /** + * 修改${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int update${ClassName}(${ClassName} ${className}); + + /** + * 批量删除${functionName} + * + * @param ${pkColumn.javaField}s 需要删除的${functionName}主键集合 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); + + /** + * 删除${functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); +} diff --git a/boyue-models/boyue-generator/src/main/resources/vm/java/serviceImpl.java.vm b/boyue-models/boyue-generator/src/main/resources/vm/java/serviceImpl.java.vm new file mode 100644 index 0000000..419362a --- /dev/null +++ b/boyue-models/boyue-generator/src/main/resources/vm/java/serviceImpl.java.vm @@ -0,0 +1,169 @@ +package ${packageName}.service.impl; + +import java.util.List; +#foreach ($column in $columns) +#if($column.javaField == 'createTime' || $column.javaField == 'updateTime') +import com.boyue.common.utils.DateUtils; +#break +#end +#end +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +#if($table.sub) +import java.util.ArrayList; +import com.boyue.common.utils.StringUtils; +import org.springframework.transaction.annotation.Transactional; +import ${packageName}.domain.${subClassName}; +#end +import ${packageName}.mapper.${ClassName}Mapper; +import ${packageName}.domain.${ClassName}; +import ${packageName}.service.I${ClassName}Service; + +/** + * ${functionName}Service业务层处理 + * + * @author ${author} + * @date ${datetime} + */ +@Service +public class ${ClassName}ServiceImpl implements I${ClassName}Service +{ + @Autowired + private ${ClassName}Mapper ${className}Mapper; + + /** + * 查询${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + @Override + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) + { + return ${className}Mapper.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); + } + + /** + * 查询${functionName}列表 + * + * @param ${className} ${functionName} + * @return ${functionName} + */ + @Override + public List<${ClassName}> select${ClassName}List(${ClassName} ${className}) + { + return ${className}Mapper.select${ClassName}List(${className}); + } + + /** + * 新增${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ +#if($table.sub) + @Transactional +#end + @Override + public int insert${ClassName}(${ClassName} ${className}) + { +#foreach ($column in $columns) +#if($column.javaField == 'createTime') + ${className}.setCreateTime(DateUtils.getNowDate()); +#end +#end +#if($table.sub) + int rows = ${className}Mapper.insert${ClassName}(${className}); + insert${subClassName}(${className}); + return rows; +#else + return ${className}Mapper.insert${ClassName}(${className}); +#end + } + + /** + * 修改${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ +#if($table.sub) + @Transactional +#end + @Override + public int update${ClassName}(${ClassName} ${className}) + { +#foreach ($column in $columns) +#if($column.javaField == 'updateTime') + ${className}.setUpdateTime(DateUtils.getNowDate()); +#end +#end +#if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${className}.get${pkColumn.capJavaField}()); + insert${subClassName}(${className}); +#end + return ${className}Mapper.update${ClassName}(${className}); + } + + /** + * 批量删除${functionName} + * + * @param ${pkColumn.javaField}s 需要删除的${functionName}主键 + * @return 结果 + */ +#if($table.sub) + @Transactional +#end + @Override + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s) + { +#if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaField}s); +#end + return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s); + } + + /** + * 删除${functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ +#if($table.sub) + @Transactional +#end + @Override + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) + { +#if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${pkColumn.javaField}); +#end + return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); + } +#if($table.sub) + + /** + * 新增${subTable.functionName}信息 + * + * @param ${className} ${functionName}对象 + */ + public void insert${subClassName}(${ClassName} ${className}) + { + List<${subClassName}> ${subclassName}List = ${className}.get${subClassName}List(); + ${pkColumn.javaType} ${pkColumn.javaField} = ${className}.get${pkColumn.capJavaField}(); + if (StringUtils.isNotNull(${subclassName}List)) + { + List<${subClassName}> list = new ArrayList<${subClassName}>(); + for (${subClassName} ${subclassName} : ${subclassName}List) + { + ${subclassName}.set${subTableFkClassName}(${pkColumn.javaField}); + list.add(${subclassName}); + } + if (list.size() > 0) + { + ${className}Mapper.batch${subClassName}(list); + } + } + } +#end +} diff --git a/boyue-models/boyue-generator/src/main/resources/vm/java/sub-domain.java.vm b/boyue-models/boyue-generator/src/main/resources/vm/java/sub-domain.java.vm new file mode 100644 index 0000000..7c63236 --- /dev/null +++ b/boyue-models/boyue-generator/src/main/resources/vm/java/sub-domain.java.vm @@ -0,0 +1,75 @@ +package ${packageName}.domain; + +#foreach ($import in $subImportList) +import ${import}; +#end +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.boyue.common.annotation.Excel; + +/** + * ${subTable.functionName}对象 ${subTableName} + * + * @author ${author} + * @date ${datetime} + */ +public class ${subClassName} extends BaseEntity +{ + private static final long serialVersionUID = 1L; + +#foreach ($column in $subTable.columns) +#if(!$table.isSuperColumn($column.javaField)) + /** $column.columnComment */ +#if($column.list) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end +#if($parentheseIndex != -1) + @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") +#elseif($column.javaType == 'Date') + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd") +#else + @Excel(name = "${comment}") +#end +#end + private $column.javaType $column.javaField; + +#end +#end +#foreach ($column in $subTable.columns) +#if(!$table.isSuperColumn($column.javaField)) +#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.javaField) +#else +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#end + public void set${AttrName}($column.javaType $column.javaField) + { + this.$column.javaField = $column.javaField; + } + + public $column.javaType get${AttrName}() + { + return $column.javaField; + } +#end +#end + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) +#foreach ($column in $subTable.columns) +#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.javaField) +#else +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#end + .append("${column.javaField}", get${AttrName}()) +#end + .toString(); + } +} diff --git a/boyue-models/boyue-generator/src/main/resources/vm/js/api.js.vm b/boyue-models/boyue-generator/src/main/resources/vm/js/api.js.vm new file mode 100644 index 0000000..9295524 --- /dev/null +++ b/boyue-models/boyue-generator/src/main/resources/vm/js/api.js.vm @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询${functionName}列表 +export function list${BusinessName}(query) { + return request({ + url: '/${moduleName}/${businessName}/list', + method: 'get', + params: query + }) +} + +// 查询${functionName}详细 +export function get${BusinessName}(${pkColumn.javaField}) { + return request({ + url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField}, + method: 'get' + }) +} + +// 新增${functionName} +export function add${BusinessName}(data) { + return request({ + url: '/${moduleName}/${businessName}', + method: 'post', + data: data + }) +} + +// 修改${functionName} +export function update${BusinessName}(data) { + return request({ + url: '/${moduleName}/${businessName}', + method: 'put', + data: data + }) +} + +// 删除${functionName} +export function del${BusinessName}(${pkColumn.javaField}) { + return request({ + url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField}, + method: 'delete' + }) +} diff --git a/boyue-models/boyue-generator/src/main/resources/vm/sql/sql.vm b/boyue-models/boyue-generator/src/main/resources/vm/sql/sql.vm new file mode 100644 index 0000000..0575583 --- /dev/null +++ b/boyue-models/boyue-generator/src/main/resources/vm/sql/sql.vm @@ -0,0 +1,22 @@ +-- 菜单 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 'admin', sysdate(), '', null, '${functionName}菜单'); + +-- 按钮父菜单ID +SELECT @parentId := LAST_INSERT_ID(); + +-- 按钮 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}查询', @parentId, '1', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}新增', @parentId, '2', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}修改', @parentId, '3', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}删除', @parentId, '4', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}导出', @parentId, '5', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export', '#', 'admin', sysdate(), '', null, ''); \ No newline at end of file diff --git a/boyue-models/boyue-generator/src/main/resources/vm/uniapp/edit.vue.vm b/boyue-models/boyue-generator/src/main/resources/vm/uniapp/edit.vue.vm new file mode 100644 index 0000000..c0b02a8 --- /dev/null +++ b/boyue-models/boyue-generator/src/main/resources/vm/uniapp/edit.vue.vm @@ -0,0 +1,155 @@ + + + diff --git a/boyue-models/boyue-generator/src/main/resources/vm/uniapp/list.vue.vm b/boyue-models/boyue-generator/src/main/resources/vm/uniapp/list.vue.vm new file mode 100644 index 0000000..98acb4e --- /dev/null +++ b/boyue-models/boyue-generator/src/main/resources/vm/uniapp/list.vue.vm @@ -0,0 +1,140 @@ + + + diff --git a/boyue-models/boyue-generator/src/main/resources/vm/uniapp/show.vue.vm b/boyue-models/boyue-generator/src/main/resources/vm/uniapp/show.vue.vm new file mode 100644 index 0000000..1b4a86e --- /dev/null +++ b/boyue-models/boyue-generator/src/main/resources/vm/uniapp/show.vue.vm @@ -0,0 +1,85 @@ + + + diff --git a/boyue-models/boyue-generator/src/main/resources/vm/vue/v2/index-tree.vue.vm b/boyue-models/boyue-generator/src/main/resources/vm/vue/v2/index-tree.vue.vm new file mode 100644 index 0000000..b4059ff --- /dev/null +++ b/boyue-models/boyue-generator/src/main/resources/vm/vue/v2/index-tree.vue.vm @@ -0,0 +1,575 @@ + + + diff --git a/boyue-models/boyue-generator/src/main/resources/vm/vue/v2/index.vue.vm b/boyue-models/boyue-generator/src/main/resources/vm/vue/v2/index.vue.vm new file mode 100644 index 0000000..77ddd15 --- /dev/null +++ b/boyue-models/boyue-generator/src/main/resources/vm/vue/v2/index.vue.vm @@ -0,0 +1,672 @@ + + + diff --git a/boyue-models/boyue-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm b/boyue-models/boyue-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm new file mode 100644 index 0000000..7bbd2fc --- /dev/null +++ b/boyue-models/boyue-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm @@ -0,0 +1,474 @@ + + + diff --git a/boyue-models/boyue-generator/src/main/resources/vm/vue/v3/index.vue.vm b/boyue-models/boyue-generator/src/main/resources/vm/vue/v3/index.vue.vm new file mode 100644 index 0000000..ceef8d4 --- /dev/null +++ b/boyue-models/boyue-generator/src/main/resources/vm/vue/v3/index.vue.vm @@ -0,0 +1,642 @@ + + + diff --git a/boyue-models/boyue-generator/src/main/resources/vm/xml/mapper.xml.vm b/boyue-models/boyue-generator/src/main/resources/vm/xml/mapper.xml.vm new file mode 100644 index 0000000..f7866f4 --- /dev/null +++ b/boyue-models/boyue-generator/src/main/resources/vm/xml/mapper.xml.vm @@ -0,0 +1,167 @@ + + + + + +#foreach ($column in $columns) + +#end +#if($joinColunms) +#foreach ($column in $joinColunms) + +#end +#end + +#if($table.sub) + + + + + + +#foreach ($column in $subTable.columns) + +#end + +#end + + + select +#foreach ($column in $allColumns) +#set($columnName=$columnMap[$column.columnId].columnName) +#set($tableAlias=$tableAliasMap[$column.tableId]) + $tableAlias.$columnName#if($foreach.hasNext),#end +#end + from $table.tableName $table.tableAlias +#if($joinTablesMate) +#foreach($joinTable in $joinTablesMate) +#set($leftColumnName=$columnMap[$joinTable.leftTableFk].columnName) +#set($leftTableAlias=$joinTable.leftTableAlias) +#set($rightColumnName=$columnMap[$joinTable.rightTableFk].columnName) +#set($rightTableAlias=$joinTable.rightTableAlias) +#set($newTableName=$tableMap[$joinTable.newTableId].tableName) +#set($newTableAlias=$tableAliasMap[$joinTable.newTableId]) + ${joinTable.joinType} join $newTableName $newTableAlias on $rightTableAlias.$rightColumnName = $leftTableAlias.$leftColumnName +#end +#end + + + + + + + + insert into ${tableName} + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName || !$pkColumn.increment) + $column.columnName, +#end +#end + + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName || !$pkColumn.increment) + #{$column.javaField}, +#end +#end + + + + + update ${tableName} + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName) + $column.columnName = #{$column.javaField}, +#end +#end + +#if($table.haveSubColumn == '1') + where ${pkColumn.columnName} = #{${pkColumn.javaField}} +#else + where ${tableName}.${pkColumn.columnName} = #{${pkColumn.javaField}} +#end + + + + delete from ${tableName} where ${pkColumn.columnName} = #{${pkColumn.javaField}} + + + + delete from ${tableName} where ${pkColumn.columnName} in + + #{${pkColumn.javaField}} + + +#if($table.sub) + + + delete from ${subTableName} where ${subTableFkName} in + + #{${subTableFkclassName}} + + + + + delete from ${subTableName} where ${subTableFkName} = #{${subTableFkclassName}} + + + + insert into ${subTableName}(#foreach($column in $subTable.columns) $column.columnName#if($foreach.count != $subTable.columns.size()),#end#end) values + + (#foreach($column in $subTable.columns) #{item.$column.javaField}#if($foreach.count != $subTable.columns.size()),#end#end) + + +#end + \ No newline at end of file diff --git a/boyue-models/boyue-hasfj/pom.xml b/boyue-models/boyue-hasfj/pom.xml new file mode 100644 index 0000000..1a07eb3 --- /dev/null +++ b/boyue-models/boyue-hasfj/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + + com.boyue + boyue-models + 3.8.9-G + + + boyue-hasfj + + + 21 + 21 + UTF-8 + + + 淮安市司法局模块 + + + + + + com.boyue + boyue-framework + + + + + com.boyue + boyue-common + + + + \ No newline at end of file diff --git a/boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/controller/ApiController.java b/boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/controller/ApiController.java new file mode 100644 index 0000000..368031c --- /dev/null +++ b/boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/controller/ApiController.java @@ -0,0 +1,46 @@ +package com.boyue.hasfj.controller; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +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 com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.hasfj.domain.HtmlPages; +import com.boyue.hasfj.service.IHtmlPagesService; +import com.github.pagehelper.PageHelper; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 司法局前端API接口 + * + * @author boyue + */ +@RestController +@RequestMapping("/api/hasfj") +@Tag(name = "司法局前端API接口") +public class ApiController extends BaseController +{ + @Autowired + private IHtmlPagesService htmlPagesService; + + /** + * 获取二维码数据列表 + */ + @Operation(summary = "获取二维码数据列表") + @GetMapping("/qrcodes") + public TableDataInfo getQRCodes( + @RequestParam(value = "type", required = false, defaultValue = "law") String type, + @RequestParam(value = "pageNum", required = false, defaultValue = "1") Integer pageNum, + @RequestParam(value = "pageSize", required = false, defaultValue = "6") Integer pageSize) { + PageHelper.startPage(pageNum, pageSize); + HtmlPages htmlPages = new HtmlPages(); + htmlPages.setPageType(type); + htmlPages.setStatus(1); // 只查询启用状态的 + List list = htmlPagesService.selectHtmlPagesList(htmlPages); + return getDataTable(list); + } +} \ No newline at end of file diff --git a/boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/controller/FrontendController.java b/boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/controller/FrontendController.java new file mode 100644 index 0000000..6992b60 --- /dev/null +++ b/boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/controller/FrontendController.java @@ -0,0 +1,47 @@ +package com.boyue.hasfj.controller; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +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 com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.constant.HttpStatus; +import com.boyue.hasfj.domain.HtmlPages; +import com.boyue.hasfj.service.IHtmlPagesService; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import io.swagger.v3.oas.annotations.Operation; + +@RestController +@RequestMapping("/frontend") +public class FrontendController extends BaseController +{ + @Autowired + private IHtmlPagesService htmlPagesService; + + /** + * 获取二维码数据列表 + */ + @Operation(summary = "获取二维码数据列表") + @GetMapping("/qrcodes") + public AjaxResult getQRCodes( + @RequestParam(value = "type", required = false, defaultValue = "law") String type, + @RequestParam(value = "pageNum", required = false, defaultValue = "1") Integer pageNum, + @RequestParam(value = "pageSize", required = false, defaultValue = "6") Integer pageSize) { + PageHelper.startPage(pageNum, pageSize); + HtmlPages htmlPages = new HtmlPages(); + htmlPages.setPageType(type); + htmlPages.setStatus(1); // 只查询启用状态的 + List list = htmlPagesService.selectHtmlPagesList(htmlPages); + TableDataInfo tableDataInfo = new TableDataInfo(); + tableDataInfo.setCode(HttpStatus.SUCCESS); + tableDataInfo.setMsg("查询成功"); + tableDataInfo.setRows(list); + tableDataInfo.setTotal(new PageInfo(list).getTotal()); + return success(tableDataInfo); + } +} \ No newline at end of file diff --git a/boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/controller/HtmlPageApiController.java b/boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/controller/HtmlPageApiController.java new file mode 100644 index 0000000..6315371 --- /dev/null +++ b/boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/controller/HtmlPageApiController.java @@ -0,0 +1,569 @@ +package com.boyue.hasfj.controller; + +import java.util.List; +import java.util.ArrayList; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.hasfj.domain.HtmlPages; +import com.boyue.hasfj.service.IHtmlPagesService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +import jakarta.servlet.http.HttpServletRequest; + +/** + * 页面API控制器 + * + * @author boyue + */ +@RestController +@RequestMapping("/api/hasfj") +@Tag(name = "页面API接口") +public class HtmlPageApiController extends BaseController { + + @Autowired + private IHtmlPagesService htmlPagesService; + + /** + * 获取页面详情 + */ + @GetMapping("/page") + @Operation(summary = "获取页面详情") + public AjaxResult getPageDetail(@RequestParam(value = "type", required = true) String pageType, + @RequestParam(value = "formatId", required = true) String formatId) { + HtmlPages htmlPages = new HtmlPages(); + htmlPages.setPageType(pageType); + htmlPages.setFormatId(formatId); + htmlPages.setStatus(Integer.valueOf(1)); // 只查询启用状态的 + + List list = htmlPagesService.selectHtmlPagesList(htmlPages); + if (list != null && !list.isEmpty()) { + HtmlPages page = list.get(0); + // 使用专门的方法更新浏览次数 + htmlPagesService.updateHtmlPagesViewCount(page.getId()); + // 重新查询最新数据 + page = htmlPagesService.selectHtmlPagesById(page.getId()); + + return AjaxResult.success(page); + } + + return AjaxResult.error("未找到相关页面"); + } + + /** + * 根据ID获取页面 - 增加类型检查防止非数字字符串作为ID传入 + */ + @GetMapping("/page/{id}") + @Operation(summary = "根据ID获取页面") + public AjaxResult getPageById(@PathVariable(value = "id", required = true) String idStr) { + // 添加参数验证,确保ID是有效的数字 + try { + // 尝试将ID转换为Long类型 + Long id = Long.valueOf(idStr); + HtmlPages htmlPages = htmlPagesService.selectHtmlPagesById(id); + if (htmlPages != null) { + // 使用专门的方法更新浏览次数 + htmlPagesService.updateHtmlPagesViewCount(htmlPages.getId()); + // 重新查询最新数据 + htmlPages = htmlPagesService.selectHtmlPagesById(htmlPages.getId()); + + return AjaxResult.success(htmlPages); + } + return AjaxResult.error("未找到ID为" + id + "的页面"); + } catch (NumberFormatException e) { + // 如果ID不是有效的数字,返回错误信息 + return AjaxResult.error("无效的页面ID: " + idStr + ",请提供有效的数字ID"); + } + } + + /** + * 获取页面列表 + */ + @GetMapping("/pages") + @Operation(summary = "获取页面列表") + public AjaxResult getPages(@RequestParam(value = "type", required = false) String pageType) { + HtmlPages htmlPages = new HtmlPages(); + if (pageType != null && !pageType.isEmpty()) { + htmlPages.setPageType(pageType); + } + htmlPages.setStatus(Integer.valueOf(1)); // 只查询启用状态的 + + List list = htmlPagesService.selectHtmlPagesList(htmlPages); + return AjaxResult.success(list); + } + + /** + * 根据类型获取页面列表 + */ + @GetMapping("/hasfjpages/listByType/{pageType}") + @Operation(summary = "根据类型获取页面列表") + public AjaxResult getPagesByType(@PathVariable("pageType") String pageType) { + HtmlPages htmlPages = new HtmlPages(); + htmlPages.setPageType(pageType); + htmlPages.setStatus(Integer.valueOf(1)); // 只查询启用状态的 + + startPage(); // 使用不带参数的分页方法,会自动从请求参数中获取分页信息 + List list = htmlPagesService.selectHtmlPagesList(htmlPages); + return AjaxResult.success(getDataTable(list)); + } + + /** + * 获取法律规定列表 + */ + @GetMapping("/laws") + @Operation(summary = "获取法律规定列表") + public AjaxResult getLaws() { + HtmlPages htmlPages = new HtmlPages(); + htmlPages.setPageType("law"); + htmlPages.setStatus(Integer.valueOf(1)); + + List list = htmlPagesService.selectHtmlPagesList(htmlPages); + return AjaxResult.success(list); + } + + /** + * 获取典型案例列表 + */ + @GetMapping("/cases") + @Operation(summary = "获取典型案例列表") + public AjaxResult getCases() { + HtmlPages htmlPages = new HtmlPages(); + htmlPages.setPageType("case"); + htmlPages.setStatus(Integer.valueOf(1)); + + List list = htmlPagesService.selectHtmlPagesList(htmlPages); + return AjaxResult.success(list); + } + + /** + * 获取表单下载列表 + */ + @GetMapping("/forms") + @Operation(summary = "获取表单下载列表") + public AjaxResult getForms() { + HtmlPages htmlPages = new HtmlPages(); + htmlPages.setPageType("form"); + htmlPages.setStatus(Integer.valueOf(1)); + + List list = htmlPagesService.selectHtmlPagesList(htmlPages); + return AjaxResult.success(list); + } + + /** + * 搜索API + */ + @GetMapping("/search") + @Operation(summary = "搜索内容") + public AjaxResult search( + @RequestParam(value = "keyword", required = false) String keyword, + @RequestParam(value = "q", required = false) String q, + @RequestParam(value = "content", required = false) String content, + @RequestParam(value = "type", required = false) String pageType) { + + // 按优先级选择搜索关键词 + String searchText = keyword; + if (searchText == null || searchText.trim().isEmpty()) { + searchText = q; + } + if (searchText == null || searchText.trim().isEmpty()) { + searchText = content; + } + + if (searchText == null || searchText.trim().isEmpty()) { + return AjaxResult.error("搜索关键词不能为空"); + } + + HtmlPages searchCondition = new HtmlPages(); + searchCondition.setStatus(Integer.valueOf(1)); // 只搜索启用状态的内容 + if (pageType != null && !pageType.trim().isEmpty()) { + searchCondition.setPageType(pageType); + } + + List allPages = htmlPagesService.selectHtmlPagesList(searchCondition); + List searchResults = new ArrayList<>(); + + if (allPages != null && !allPages.isEmpty()) { + // 在标题和内容中搜索关键词 + String keywordLower = searchText.toLowerCase(); + searchResults = allPages.stream() + .filter(page -> { + String title = page.getTitle() != null ? page.getTitle().toLowerCase() : ""; + String pageContent = page.getContent() != null ? page.getContent().toLowerCase() : ""; + String desc = page.getDescription() != null ? page.getDescription().toLowerCase() : ""; + String keys = page.getKeywords() != null ? page.getKeywords().toLowerCase() : ""; + + return title.contains(keywordLower) || + pageContent.contains(keywordLower) || + desc.contains(keywordLower) || + keys.contains(keywordLower); + }) + .collect(Collectors.toList()); + } + + return AjaxResult.success(searchResults); + } + + /** + * 搜索API - 前端路径兼容版本 + */ + @GetMapping({"/hasfjpages/search", "/search"}) + @Operation(summary = "搜索内容(前端兼容版)") + public AjaxResult searchFrontend( + @RequestParam(value = "keyword", required = false) String keyword, + @RequestParam(value = "q", required = false) String q, + @RequestParam(value = "content", required = false) String content, + @RequestParam(value = "type", required = false) String pageType, + @RequestParam(value = "pageType", required = false) String pageTypeAlt, + @RequestParam(value = "stringQuery", required = false) String stringQuery) { + + try { + // 获取可能的ID参数,但不使用@RequestParam注解 + // 因为@RequestParam对于特定类型的参数会尝试自动类型转换,可能导致错误 + String idStr = null; + try { + // 手动从请求参数中获取id,避免Spring自动类型转换导致的错误 + idStr = this.getRequest().getParameter("id"); + System.out.println("手动获取id参数: " + idStr); + } catch (Exception e) { + System.out.println("获取id参数时出错: " + e.getMessage()); + } + + System.out.println("搜索请求参数: keyword=" + keyword + ", q=" + q + ", content=" + content + + ", type=" + pageType + ", pageType=" + pageTypeAlt + + ", id=" + idStr + ", stringQuery=" + stringQuery); + + // 检测参数值是否含有前缀,如果有则移除前缀 + if (keyword != null && keyword.startsWith("keyword:")) { + keyword = keyword.substring("keyword:".length()); + } + if (q != null && q.startsWith("keyword:")) { + q = q.substring("keyword:".length()); + } + if (content != null && content.startsWith("keyword:")) { + content = content.substring("keyword:".length()); + } + + // 特殊关键词处理,如果搜索关键词为"search"、"如何"等特殊词,强制使用字符串搜索模式 + boolean forceStringSearch = "true".equalsIgnoreCase(stringQuery); + + // 扩展特殊关键词列表,包括"如何"、"search"等 + String[] specialKeywords = {"search", "如何", "怎么", "怎样", "什么", "哪些", "为什么"}; + + // 检查所有可能的参数是否包含特殊关键词 + if (containsSpecialKeyword(keyword, specialKeywords) || + containsSpecialKeyword(q, specialKeywords) || + containsSpecialKeyword(content, specialKeywords) || + containsSpecialKeyword(idStr, specialKeywords)) { + forceStringSearch = true; + System.out.println("检测到特殊关键词,启用强制字符串搜索模式"); + } + + // 按优先级选择搜索关键词 + String searchText = keyword; + if (searchText == null || searchText.trim().isEmpty()) { + searchText = q; + } + if (searchText == null || searchText.trim().isEmpty()) { + searchText = content; + } + if (searchText == null || searchText.trim().isEmpty() && !forceStringSearch && idStr != null && !isSpecialKeyword(idStr)) { + searchText = idStr; // 如果ID不是数字且不是特殊关键词,当作关键词使用 + } + + if (searchText == null || searchText.trim().isEmpty()) { + return AjaxResult.error("搜索关键词不能为空"); + } + + System.out.println("最终使用的搜索文本: " + searchText + ", 强制字符串搜索模式: " + forceStringSearch); + + // 确定页面类型 + String finalPageType = pageType; + if (finalPageType == null || finalPageType.trim().isEmpty()) { + finalPageType = pageTypeAlt; + } + + // 直接调用专用搜索API + return searchContent(searchText, finalPageType); + } catch (Exception ex) { + System.out.println("搜索接口全局异常: " + ex.getMessage()); + ex.printStackTrace(); + return AjaxResult.error("搜索服务异常,请稍后重试"); + } + } + + /** + * 判断字符串是否包含特殊关键词 + */ + private boolean containsSpecialKeyword(String text, String[] specialKeywords) { + if (text == null || text.trim().isEmpty()) { + return false; + } + + String lowerText = text.toLowerCase(); + for (String keyword : specialKeywords) { + if (lowerText.contains(keyword.toLowerCase())) { + System.out.println("检测到特殊关键词: " + keyword + " 在文本: " + text); + return true; + } + } + + return false; + } + + /** + * 判断字符串是否为特殊关键词 + */ + private boolean isSpecialKeyword(String text) { + if (text == null || text.trim().isEmpty()) { + return false; + } + + String[] specialKeywords = {"search", "如何", "怎么", "怎样", "什么", "哪些", "为什么"}; + String lowerText = text.toLowerCase(); + + for (String keyword : specialKeywords) { + if (lowerText.equals(keyword.toLowerCase())) { + return true; + } + } + + // 检查是否为纯数字 + try { + Long.parseLong(text); + return false; // 是纯数字,不是特殊关键词 + } catch (NumberFormatException e) { + return true; // 不是纯数字,视为特殊关键词 + } + } + + /** + * 搜索API - 纯文本搜索端点,无ID参数干扰 + */ + @GetMapping("/search-text") + @Operation(summary = "全文搜索(无ID冲突)") + public AjaxResult searchText( + @RequestParam(value = "keyword", required = false) String keyword, + @RequestParam(value = "type", required = false) String pageType) { + + System.out.println("全文搜索请求: keyword=" + keyword + ", type=" + pageType); + + if (keyword == null || keyword.trim().isEmpty()) { + return AjaxResult.error("搜索关键词不能为空"); + } + + HtmlPages searchCondition = new HtmlPages(); + searchCondition.setStatus(Integer.valueOf(1)); // 只搜索启用状态的内容 + if (pageType != null && !pageType.trim().isEmpty()) { + searchCondition.setPageType(pageType); + } + + try { + // 获取所有启用状态的页面 + List allPages = htmlPagesService.selectHtmlPagesList(searchCondition); + List searchResults = new ArrayList<>(); + + if (allPages != null && !allPages.isEmpty()) { + // 在标题和内容中搜索关键词 + String keywordLower = keyword.toLowerCase(); + searchResults = allPages.stream() + .filter(page -> { + String title = page.getTitle() != null ? page.getTitle().toLowerCase() : ""; + String pageContent = page.getContent() != null ? page.getContent().toLowerCase() : ""; + String desc = page.getDescription() != null ? page.getDescription().toLowerCase() : ""; + String keys = page.getKeywords() != null ? page.getKeywords().toLowerCase() : ""; + + return title.contains(keywordLower) || + pageContent.contains(keywordLower) || + desc.contains(keywordLower) || + keys.contains(keywordLower); + }) + .collect(Collectors.toList()); + } + + System.out.println("全文搜索结果数量: " + searchResults.size()); + return AjaxResult.success(searchResults); + } catch (Exception e) { + System.out.println("全文搜索出错: " + e.getMessage()); + e.printStackTrace(); + return AjaxResult.error("搜索服务异常,请稍后重试"); + } + } + + /** + * 专用搜索API - 全新路径,避免与任何现有API冲突 + */ + @GetMapping("/search-content") + @Operation(summary = "专用内容搜索API") + public AjaxResult searchContent(@RequestParam(value = "keyword", required = false) String keyword, + @RequestParam(value = "type", required = false) String pageType) { + System.out.println("专用搜索API调用: keyword=" + keyword + ", type=" + pageType); + + try { + // 参数验证和处理 + if (keyword == null || keyword.trim().isEmpty()) { + System.out.println("搜索关键词为空,返回错误"); + return AjaxResult.error("搜索关键词不能为空"); + } + + // 记录关键词信息 + System.out.println("处理搜索关键词: " + keyword); + + // 检查是否为特殊关键词 + String[] specialKeywords = {"search", "如何", "怎么", "怎样", "什么", "哪些", "为什么"}; + boolean isSpecial = false; + for (String special : specialKeywords) { + if (keyword.toLowerCase().contains(special.toLowerCase())) { + isSpecial = true; + System.out.println("检测到特殊关键词: " + special + " 在搜索文本中"); + break; + } + } + + // 构建搜索条件 + HtmlPages searchCondition = new HtmlPages(); + searchCondition.setStatus(Integer.valueOf(1)); // 只搜索启用状态的内容 + + if (pageType != null && !pageType.trim().isEmpty()) { + searchCondition.setPageType(pageType); + System.out.println("按类型搜索: " + pageType); + } else { + System.out.println("搜索所有类型"); + } + + try { + // 获取所有启用状态的页面 + System.out.println("开始查询数据库..."); + List allPages = htmlPagesService.selectHtmlPagesList(searchCondition); + System.out.println("数据库查询完成,总记录数: " + (allPages != null ? allPages.size() : 0)); + + List searchResults = new ArrayList<>(); + + if (allPages != null && !allPages.isEmpty()) { + // 在标题和内容中搜索关键词 + String keywordLower = keyword.toLowerCase(); + searchResults = allPages.stream() + .filter(page -> { + // 获取页面各字段,防止空值 + String title = page.getTitle() != null ? page.getTitle().toLowerCase() : ""; + String pageContent = page.getContent() != null ? page.getContent().toLowerCase() : ""; + String desc = page.getDescription() != null ? page.getDescription().toLowerCase() : ""; + String keys = page.getKeywords() != null ? page.getKeywords().toLowerCase() : ""; + String formatId = page.getFormatId() != null ? page.getFormatId().toLowerCase() : ""; + String author = page.getAuthor() != null ? page.getAuthor().toLowerCase() : ""; + + // 对所有字段进行关键词匹配,实现全局搜索 + boolean matches = title.contains(keywordLower) || + pageContent.contains(keywordLower) || + desc.contains(keywordLower) || + keys.contains(keywordLower) || + formatId.contains(keywordLower) || + author.contains(keywordLower); + + if (matches) { + System.out.println("找到匹配: ID=" + page.getId() + ", 标题=" + page.getTitle() + ", 类型=" + page.getPageType()); + } + + return matches; + }) + .collect(Collectors.toList()); + + // 将匹配度最高的结果排在前面(标题匹配优先于内容匹配) + searchResults.sort((a, b) -> { + String aTitle = a.getTitle() != null ? a.getTitle().toLowerCase() : ""; + String bTitle = b.getTitle() != null ? b.getTitle().toLowerCase() : ""; + + boolean aTitleMatch = aTitle.contains(keywordLower); + boolean bTitleMatch = bTitle.contains(keywordLower); + + if (aTitleMatch && !bTitleMatch) { + return -1; + } else if (!aTitleMatch && bTitleMatch) { + return 1; + } else { + return 0; + } + }); + } + + System.out.println("专用搜索API搜索结果数量: " + searchResults.size()); + + // 返回成功结果 + AjaxResult result = AjaxResult.success(searchResults); + result.put("total", searchResults.size()); + return result; + } catch (Exception e) { + System.out.println("专用搜索API出错: " + e.getMessage()); + e.printStackTrace(); + return AjaxResult.error("搜索服务异常,请稍后重试: " + e.getMessage()); + } + } catch (Exception ex) { + System.out.println("专用搜索API全局异常: " + ex.getMessage()); + ex.printStackTrace(); + return AjaxResult.error("搜索服务异常,请稍后重试"); + } + } + + /** + * 为确保向后兼容,添加一个额外的搜索端点,与前端路径匹配 + */ + @GetMapping("/hasfjpages/search-content") + @Operation(summary = "专用内容搜索API(前端兼容版)") + public AjaxResult searchContentFrontend(@RequestParam(value = "keyword", required = true) String keyword, + @RequestParam(value = "type", required = false) String pageType) { + // 调用主要的搜索方法 + return searchContent(keyword, pageType); + } + + /** + * 新增:前端搜索API - 无需前缀路径,直接支持/hasfj/search-content + */ + @GetMapping("/hasfj/search-content") + @Operation(summary = "前端直接搜索API") + public AjaxResult directSearchContent(@RequestParam(value = "keyword", required = true) String keyword, + @RequestParam(value = "type", required = false) String pageType) { + // 调用主要的搜索方法 + return searchContent(keyword, pageType); + } + + /** + * 新增:搜索API - 处理所有搜索请求的通用端点 + * 此端点不会与ID路径冲突,专门用于处理搜索请求 + */ + @GetMapping("/search-api") + @Operation(summary = "通用搜索API") + public AjaxResult searchApi(@RequestParam(value = "keyword", required = true) String keyword, + @RequestParam(value = "type", required = false) String pageType) { + // 调用主要的搜索方法 + return searchContent(keyword, pageType); + } + + /** + * 新增:前端搜索API - 前端兼容版本 + */ + @GetMapping("/hasfjpages/search-api") + @Operation(summary = "通用搜索API(前端兼容版)") + public AjaxResult searchApiFrontend(@RequestParam(value = "keyword", required = true) String keyword, + @RequestParam(value = "type", required = false) String pageType) { + // 调用主要的搜索方法 + return searchContent(keyword, pageType); + } + + /** + * 获取当前请求对象 + */ + private HttpServletRequest getRequest() { + return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + } +} \ No newline at end of file diff --git a/boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/controller/HtmlPageViewController.java b/boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/controller/HtmlPageViewController.java new file mode 100644 index 0000000..17e9553 --- /dev/null +++ b/boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/controller/HtmlPageViewController.java @@ -0,0 +1,171 @@ +package com.boyue.hasfj.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.hasfj.domain.HtmlPages; +import com.boyue.hasfj.service.IHtmlPagesService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +import java.util.List; + +/** + * 页面前端视图控制器 + * + * @author boyue + */ +@Controller +@Tag(name = "页面前端视图") +public class HtmlPageViewController extends BaseController { + + @Autowired + private IHtmlPagesService htmlPagesService; + + /** + * 首页 + */ + @GetMapping("/hasfj") + @Operation(summary = "淮安市司法局首页") + public String index() { + return "hasfj/index"; + } + + /** + * 法律规定页面列表 + */ + @GetMapping("/hasfjlaw") + @Operation(summary = "法律规定页面列表") + public String lawList(ModelMap modelMap) { + HtmlPages htmlPages = new HtmlPages(); + htmlPages.setPageType("law"); + htmlPages.setStatus(1); // 只查询启用状态的 + List list = htmlPagesService.selectHtmlPagesList(htmlPages); + modelMap.put("lawList", list); + return "hasfj/law_list"; + } + + /** + * 典型案例页面列表 + */ + @GetMapping("/hasfjcase") + @Operation(summary = "典型案例页面列表") + public String caseList(ModelMap modelMap) { + HtmlPages htmlPages = new HtmlPages(); + htmlPages.setPageType("case"); + htmlPages.setStatus(1); // 只查询启用状态的 + List list = htmlPagesService.selectHtmlPagesList(htmlPages); + modelMap.put("caseList", list); + return "hasfj/case_list"; + } + + /** + * 表单下载页面列表 + */ + @GetMapping("/hasfjform") + @Operation(summary = "表单下载页面列表") + public String formList(ModelMap modelMap) { + HtmlPages htmlPages = new HtmlPages(); + htmlPages.setPageType("form"); + htmlPages.setStatus(1); // 只查询启用状态的 + List list = htmlPagesService.selectHtmlPagesList(htmlPages); + modelMap.put("formList", list); + return "hasfj/form_list"; + } + + /** + * 法律规定页面 + */ + @GetMapping("/show.html") + @Operation(summary = "法律规定页面") + public String showLawPage(@RequestParam(value = "Id", required = true) String formatId, ModelMap modelMap) { + HtmlPages htmlPages = new HtmlPages(); + htmlPages.setFormatId(formatId); + htmlPages.setPageType("law"); + htmlPages.setStatus(1); // 只查询启用状态的 + List list = htmlPagesService.selectHtmlPagesList(htmlPages); + + if (list != null && !list.isEmpty()) { + HtmlPages page = list.get(0); + // 使用专门的方法更新浏览次数 + htmlPagesService.updateHtmlPagesViewCount(page.getId()); + // 重新查询最新数据 + page = htmlPagesService.selectHtmlPagesById(page.getId()); + + modelMap.put("page", page); + return "hasfj/law"; + } + return "error/404"; + } + + /** + * 典型案例页面 + */ + @GetMapping("/showcase.html") + @Operation(summary = "典型案例页面") + public String showCasePage(@RequestParam(value = "Id", required = true) String formatId, ModelMap modelMap) { + HtmlPages htmlPages = new HtmlPages(); + htmlPages.setFormatId(formatId); + htmlPages.setPageType("case"); + htmlPages.setStatus(1); // 只查询启用状态的 + List list = htmlPagesService.selectHtmlPagesList(htmlPages); + + if (list != null && !list.isEmpty()) { + HtmlPages page = list.get(0); + // 使用专门的方法更新浏览次数 + htmlPagesService.updateHtmlPagesViewCount(page.getId()); + // 重新查询最新数据 + page = htmlPagesService.selectHtmlPagesById(page.getId()); + + modelMap.put("page", page); + return "hasfj/case"; + } + return "error/404"; + } + + /** + * 表单下载页面 + */ + @GetMapping("/table.html") + @Operation(summary = "表单下载页面") + public String showFormPage(@RequestParam(value = "Id", required = true) String formatId, ModelMap modelMap) { + HtmlPages htmlPages = new HtmlPages(); + htmlPages.setFormatId(formatId); + htmlPages.setPageType("form"); + htmlPages.setStatus(1); // 只查询启用状态的 + List list = htmlPagesService.selectHtmlPagesList(htmlPages); + + if (list != null && !list.isEmpty()) { + HtmlPages page = list.get(0); + // 使用专门的方法更新浏览次数 + htmlPagesService.updateHtmlPagesViewCount(page.getId()); + // 重新查询最新数据 + page = htmlPagesService.selectHtmlPagesById(page.getId()); + + modelMap.put("page", page); + return "hasfj/form"; + } + return "error/404"; + } + + /** + * 获取页面列表(按类型) + */ + @GetMapping("/api/pages") + @Operation(summary = "获取页面列表(按类型)") + public AjaxResult getPagesByType(@RequestParam(value = "type", required = false) String pageType) { + HtmlPages htmlPages = new HtmlPages(); + if (pageType != null && !pageType.isEmpty()) { + htmlPages.setPageType(pageType); + } + htmlPages.setStatus(1); // 只查询启用状态的 + List list = htmlPagesService.selectHtmlPagesList(htmlPages); + return AjaxResult.success(list); + } +} \ No newline at end of file diff --git a/boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/controller/HtmlPagesController.java b/boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/controller/HtmlPagesController.java new file mode 100644 index 0000000..aceb049 --- /dev/null +++ b/boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/controller/HtmlPagesController.java @@ -0,0 +1,296 @@ +package com.boyue.hasfj.controller; + +import java.util.List; +import java.util.Map; +import java.nio.charset.StandardCharsets; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.enums.BusinessType; +import com.boyue.hasfj.domain.HtmlPages; +import com.boyue.hasfj.service.IHtmlPagesService; +import com.boyue.common.utils.poi.ExcelUtil; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.file.FileUtils; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.web.multipart.MultipartFile; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.pagehelper.PageHelper; +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.web.bind.annotation.RequestMethod; + +/** + * 司法局法律规定、典型案例、单下载Controller + * + * @author boyue + * @date 2025-05-28 + */ +@RestController +@RequestMapping("/hasfj/hasfjpages") +@Tag(name = "【司法局法律规定、典型案例、单下载】管理") +public class HtmlPagesController extends BaseController +{ + @Autowired + private IHtmlPagesService htmlPagesService; + + @Value("${boyue.profile}") + private String uploadPath; + + @Autowired + private HttpServletRequest request; + + /** + * 查询司法局法律规定、典型案例、单下载列表 + */ + @Operation(summary = "查询司法局法律规定、典型案例、单下载列表") +// @PreAuthorize("@ss.hasPermi('hasfj:hasfjpages:list')") + @GetMapping("/list") + public TableDataInfo list(HtmlPages htmlPages) + { + startPage(); + List list = htmlPagesService.selectHtmlPagesList(htmlPages); + return getDataTable(list); + } + + /** + * 查询指定类型的页面列表 + */ + @Operation(summary = "查询指定类型的页面列表") + @GetMapping("/listByType/{pageType}") + public AjaxResult listByType(@PathVariable("pageType") String pageType) + { + HtmlPages htmlPages = new HtmlPages(); + htmlPages.setPageType(pageType); + htmlPages.setStatus(1); // 只查询启用状态的 + List list = htmlPagesService.selectHtmlPagesList(htmlPages); + return success(list); + } + + /** + * 导出司法局法律规定、典型案例、单下载列表 + */ + @Operation(summary = "导出司法局法律规定、典型案例、单下载列表") +// @PreAuthorize("@ss.hasPermi('hasfj:hasfjpages:export')") + @Log(title = "司法局法律规定、典型案例、单下载", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, HtmlPages htmlPages) + { + List list = htmlPagesService.selectHtmlPagesList(htmlPages); + ExcelUtil util = new ExcelUtil(HtmlPages.class); + util.exportExcel(response, list, "司法局法律规定、典型案例、单下载数据"); + } + + /** + * 导入数据 + */ + @Operation(summary = "导入数据") + @PreAuthorize("@ss.hasPermi('hasfj:hasfjpages:import')") + @Log(title = "司法局法律规定、典型案例、单下载", businessType = BusinessType.IMPORT) + @PostMapping("/importData") + public AjaxResult importData(MultipartFile file, boolean updateSupport, String importType) throws Exception + { + try { + // 根据importType决定导入方式 + if ("excel".equals(importType)) { + // Excel导入 + ExcelUtil util = new ExcelUtil(HtmlPages.class); + List htmlPagesList = util.importExcel(file.getInputStream()); + String operName = getUsername(); + String message = htmlPagesService.importHtmlPages(htmlPagesList, updateSupport, operName); + return success(message); + } else if ("batch".equals(importType)) { + // JSON批量导入 + String jsonContent = new String(file.getBytes(), StandardCharsets.UTF_8); + ObjectMapper objectMapper = new ObjectMapper(); + List htmlPagesList = objectMapper.readValue(jsonContent, + objectMapper.getTypeFactory().constructCollectionType(List.class, HtmlPages.class)); + + String operName = getUsername(); + String message = htmlPagesService.importHtmlPages(htmlPagesList, updateSupport, operName); + return success(message); + } else { + return error("不支持的导入类型"); + } + } catch (Exception e) { + logger.error("导入数据异常", e); + return error("导入数据失败: " + e.getMessage()); + } + } + + /** + * 下载导入模板 + */ + @Operation(summary = "下载导入模板") + @PostMapping("/importTemplate") + public void importTemplate(HttpServletResponse response) + { + ExcelUtil util = new ExcelUtil(HtmlPages.class); + util.importTemplateExcel(response, "司法局法律规定、典型案例、单下载数据"); + } + + /** + * 删除附件 + */ + @Operation(summary = "删除附件") + @PreAuthorize("@ss.hasPermi('hasfj:hasfjpages:edit')") + @Log(title = "司法局法律规定、典型案例、单下载-删除附件", businessType = BusinessType.DELETE) + @PostMapping("/deleteAttachment") + public AjaxResult deleteAttachment(@RequestBody Map params) + { + try { + String attachmentUrl = params.get("attachmentUrl"); + if (StringUtils.isEmpty(attachmentUrl)) { + return error("附件URL不能为空"); + } + + logger.info("删除附件请求: {}", attachmentUrl); + + boolean result = htmlPagesService.deleteAttachment(attachmentUrl); + if (result) { + return success("附件删除成功"); + } else { + return error("附件删除失败,可能文件不存在或没有权限"); + } + } catch (Exception e) { + logger.error("删除附件异常", e); + return error("删除附件失败: " + e.getMessage()); + } + } + + /** + * 根据路径删除文件 + */ + @Operation(summary = "根据路径删除文件") + @PreAuthorize("@ss.hasPermi('common:profile:delete')") + @Log(title = "附件管理-删除文件", businessType = BusinessType.DELETE) + @PostMapping("/deleteFileByPath") + public AjaxResult deleteFile(@RequestBody Map requestBody) { + try { + String filePath = requestBody.get("path"); + if (StringUtils.isEmpty(filePath)) { + return error("文件路径不能为空"); + } + + logger.info("删除文件请求: {}", filePath); + + boolean result = htmlPagesService.deleteFileByPath(filePath); + if (result) { + return success("文件删除成功"); + } else { + return error("文件删除失败,可能文件不存在或没有权限"); + } + } catch (Exception e) { + logger.error("删除文件异常", e); + return error("删除文件失败: " + e.getMessage()); + } + } + + /** + * 获取司法局法律规定、典型案例、单下载详细信息 + */ + @Operation(summary = "获取司法局法律规定、典型案例、单下载详细信息") +// @PreAuthorize("@ss.hasPermi('hasfj:hasfjpages:query')") + @GetMapping(value = "/{id}") + public AjaxResult getInfo(@PathVariable("id") Long id) + { + return success(htmlPagesService.selectHtmlPagesById(id)); + } + + /** + * 根据访问ID获取页面详情 + */ + @Operation(summary = "根据访问ID获取页面详情") + @GetMapping(value = "/format/{formatId}") + public AjaxResult getInfoByFormatId(@PathVariable("formatId") String formatId) + { + HtmlPages htmlPages = new HtmlPages(); + htmlPages.setFormatId(formatId); + List list = htmlPagesService.selectHtmlPagesList(htmlPages); + if (list != null && !list.isEmpty()) { + // 获取页面 + HtmlPages page = list.get(0); + // 使用专门的方法更新浏览次数,避免并发问题 + htmlPagesService.updateHtmlPagesViewCount(page.getId()); + // 重新查询最新数据 + page = htmlPagesService.selectHtmlPagesById(page.getId()); + return success(page); + } + return error("未找到对应页面"); + } + + /** + * 新增司法局法律规定、典型案例、单下载 + */ + @Operation(summary = "新增司法局法律规定、典型案例、单下载") +// @PreAuthorize("@ss.hasPermi('hasfj:hasfjpages:add')") + @Log(title = "司法局法律规定、典型案例、单下载", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody HtmlPages htmlPages) + { + return toAjax(htmlPagesService.insertHtmlPages(htmlPages)); + } + + /** + * 修改司法局法律规定、典型案例、单下载 + */ + @Operation(summary = "修改司法局法律规定、典型案例、单下载") +// @PreAuthorize("@ss.hasPermi('hasfj:hasfjpages:edit')") + @Log(title = "司法局法律规定、典型案例、单下载", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody HtmlPages htmlPages) + { + return toAjax(htmlPagesService.updateHtmlPages(htmlPages)); + } + + /** + * 删除司法局法律规定、典型案例、单下载 + */ + @Operation(summary = "删除司法局法律规定、典型案例、单下载") +// @PreAuthorize("@ss.hasPermi('hasfj:hasfjpages:remove')") + @Log(title = "司法局法律规定、典型案例、单下载", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable( name = "ids" ) Long[] ids) + { + return toAjax(htmlPagesService.deleteHtmlPagesByIds(ids)); + } + + /** + * 获取指定类型的页面列表用于二维码展示 + */ + @Operation(summary = "获取指定类型的页面列表用于二维码展示") + @GetMapping("/qrcodes") + public TableDataInfo getQRCodes( + @RequestParam(value = "type", required = false, defaultValue = "law") String type, + @RequestParam(value = "pageNum", required = false, defaultValue = "1") Integer pageNum, + @RequestParam(value = "pageSize", required = false, defaultValue = "6") Integer pageSize) { + PageHelper.startPage(pageNum, pageSize); + HtmlPages htmlPages = new HtmlPages(); + htmlPages.setPageType(type); + htmlPages.setStatus(1); // 只查询启用状态的 + List list = htmlPagesService.selectHtmlPagesList(htmlPages); + return getDataTable(list); + } +} \ No newline at end of file diff --git a/boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/domain/HtmlPages.java b/boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/domain/HtmlPages.java new file mode 100644 index 0000000..3e3b0ec --- /dev/null +++ b/boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/domain/HtmlPages.java @@ -0,0 +1,250 @@ +package com.boyue.hasfj.domain; + +import com.boyue.common.core.domain.BaseEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.boyue.common.annotation.Excel; + +/** + * 司法局法律规定、典型案例、单下载对象 html_pages + * + * @author boyue + * @date 2025-05-28 + */ +@Schema(description = "司法局法律规定、典型案例、单下载对象") +public class HtmlPages extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + + /** 序号 */ + @Schema(title = "序号") + @Excel(name = "序号") + private Long id; + + /** 访问id */ + @Schema(title = "访问id") + @Excel(name = "访问id") + private String formatId; + + /** 页面标题 */ + @Schema(title = "页面标题") + @Excel(name = "页面标题") + private String title; + + /** HTML内容 */ + @Schema(title = "HTML内容") + @Excel(name = "HTML内容") + private String content; + + /** 页面类型:法律规定 law、典型案例 case、表单下载from */ + @Schema(title = "页面类型:法律规定 law、典型案例 case、表单下载from") + @Excel(name = "页面类型:法律规定 law、典型案例 case、表单下载from") + private String pageType; + + /** 页面访问URL */ + @Schema(title = "页面访问URL") + @Excel(name = "页面访问URL") + private String pageUrl; + + /** 状态:1-启用,0-禁用 */ + @Schema(title = "状态:1-启用,0-禁用") + @Excel(name = "状态:1-启用,0-禁用") + private Integer status; + + /** 排序序号,数值越小排序越靠前,用于自定义显示顺序 */ + @Schema(title = "排序序号,数值越小排序越靠前,用于自定义显示顺序") + @Excel(name = "排序序号,数值越小排序越靠前,用于自定义显示顺序") + private Long sortOrder; + + /** 浏览次数,记录页面被访问的总次数 */ + @Schema(title = "浏览次数,记录页面被访问的总次数") + @Excel(name = "浏览次数,记录页面被访问的总次数") + private Long viewCount; + + /** 作者/负责人,记录页面的创建者或负责维护的人员 */ + @Schema(title = "作者/负责人,记录页面的创建者或负责维护的人员") + @Excel(name = "作者/负责人,记录页面的创建者或负责维护的人员") + private String author; + + /** SEO关键词,用于搜索引擎优化 */ + @Schema(title = "SEO关键词,用于搜索引擎优化") + @Excel(name = "SEO关键词,用于搜索引擎优化") + private String keywords; + + /** SEO描述,用于搜索引擎结果展示的页面摘要 */ + @Schema(title = "SEO描述,用于搜索引擎结果展示的页面摘要") + @Excel(name = "SEO描述,用于搜索引擎结果展示的页面摘要") + private String description; + + /** 多附件URLs,JSON格式存储多个附件信息 + * 格式: [{"name":"文件名","url":"文件路径","size":"文件大小"}] + */ + @Schema(title = "多附件URLs,JSON格式存储", description = "格式: [{'name':'文件名','url':'文件路径','size':'文件大小'}]") + @Excel(name = "多附件URLs", type = Excel.Type.EXPORT) + private String multiAttachments; + + public void setId(Long id) + { + this.id = id; + } + + public Long getId() + { + return id; + } + + + public void setFormatId(String formatId) + { + this.formatId = formatId; + } + + public String getFormatId() + { + return formatId; + } + + + public void setTitle(String title) + { + this.title = title; + } + + public String getTitle() + { + return title; + } + + + public void setContent(String content) + { + this.content = content; + } + + public String getContent() + { + return content; + } + + + public void setPageType(String pageType) + { + this.pageType = pageType; + } + + public String getPageType() + { + return pageType; + } + + + public void setPageUrl(String pageUrl) + { + this.pageUrl = pageUrl; + } + + public String getPageUrl() + { + return pageUrl; + } + + + public void setStatus(Integer status) + { + this.status = status; + } + + public Integer getStatus() + { + return status; + } + + + public void setSortOrder(Long sortOrder) + { + this.sortOrder = sortOrder; + } + + public Long getSortOrder() + { + return sortOrder; + } + + + public void setViewCount(Long viewCount) + { + this.viewCount = viewCount; + } + + public Long getViewCount() + { + return viewCount; + } + + + public void setAuthor(String author) + { + this.author = author; + } + + public String getAuthor() + { + return author; + } + + + public void setKeywords(String keywords) + { + this.keywords = keywords; + } + + public String getKeywords() + { + return keywords; + } + + + public void setDescription(String description) + { + this.description = description; + } + + public String getDescription() + { + return description; + } + + public void setMultiAttachments(String multiAttachments) + { + this.multiAttachments = multiAttachments; + } + + public String getMultiAttachments() + { + return multiAttachments; + } + + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("formatId", getFormatId()) + .append("title", getTitle()) + .append("content", getContent()) + .append("pageType", getPageType()) + .append("pageUrl", getPageUrl()) + .append("createTime", getCreateTime()) + .append("updateTime", getUpdateTime()) + .append("status", getStatus()) + .append("sortOrder", getSortOrder()) + .append("viewCount", getViewCount()) + .append("author", getAuthor()) + .append("keywords", getKeywords()) + .append("description", getDescription()) + .append("multiAttachments", getMultiAttachments()) + .toString(); + } +} diff --git a/boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/mapper/HtmlPagesMapper.java b/boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/mapper/HtmlPagesMapper.java new file mode 100644 index 0000000..be01095 --- /dev/null +++ b/boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/mapper/HtmlPagesMapper.java @@ -0,0 +1,85 @@ +package com.boyue.hasfj.mapper; + +import java.util.List; +import com.boyue.hasfj.domain.HtmlPages; + +/** + * 司法局法律规定、典型案例、单下载Mapper接口 + * + * @author boyue + * @date 2025-05-28 + */ +public interface HtmlPagesMapper +{ + /** + * 查询司法局法律规定、典型案例、单下载 + * + * @param id 司法局法律规定、典型案例、单下载主键 + * @return 司法局法律规定、典型案例、单下载 + */ + public HtmlPages selectHtmlPagesById(Long id); + + /** + * 根据formatId查询司法局法律规定、典型案例、单下载 + * + * @param formatId 访问ID + * @return 司法局法律规定、典型案例、单下载 + */ + public HtmlPages selectHtmlPagesByFormatId(String formatId); + + /** + * 查询司法局法律规定、典型案例、单下载列表 + * + * @param htmlPages 司法局法律规定、典型案例、单下载 + * @return 司法局法律规定、典型案例、单下载集合 + */ + public List selectHtmlPagesList(HtmlPages htmlPages); + + /** + * 根据页面类型查询列表 + * + * @param pageType 页面类型 + * @return 司法局法律规定、典型案例、单下载集合 + */ + public List selectHtmlPagesListByType(String pageType); + + /** + * 新增司法局法律规定、典型案例、单下载 + * + * @param htmlPages 司法局法律规定、典型案例、单下载 + * @return 结果 + */ + public int insertHtmlPages(HtmlPages htmlPages); + + /** + * 修改司法局法律规定、典型案例、单下载 + * + * @param htmlPages 司法局法律规定、典型案例、单下载 + * @return 结果 + */ + public int updateHtmlPages(HtmlPages htmlPages); + + /** + * 删除司法局法律规定、典型案例、单下载 + * + * @param id 司法局法律规定、典型案例、单下载主键 + * @return 结果 + */ + public int deleteHtmlPagesById(Long id); + + /** + * 批量删除司法局法律规定、典型案例、单下载 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteHtmlPagesByIds(Long[] ids); + + /** + * 更新页面浏览次数(直接在数据库中累加) + * + * @param id 页面ID + * @return 结果 + */ + public int updateHtmlPagesViewCount(Long id); +} diff --git a/boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/service/IHtmlPagesService.java b/boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/service/IHtmlPagesService.java new file mode 100644 index 0000000..6c68bcd --- /dev/null +++ b/boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/service/IHtmlPagesService.java @@ -0,0 +1,111 @@ +package com.boyue.hasfj.service; + +import java.util.List; +import com.boyue.hasfj.domain.HtmlPages; + +/** + * 司法局法律规定、典型案例、单下载Service接口 + * + * @author boyue + * @date 2025-05-28 + */ +public interface IHtmlPagesService +{ + /** + * 查询司法局法律规定、典型案例、单下载 + * + * @param id 司法局法律规定、典型案例、单下载主键 + * @return 司法局法律规定、典型案例、单下载 + */ + public HtmlPages selectHtmlPagesById(Long id); + + /** + * 根据formatId查询司法局法律规定、典型案例、单下载 + * + * @param formatId 访问ID + * @return 司法局法律规定、典型案例、单下载 + */ + public HtmlPages selectHtmlPagesByFormatId(String formatId); + + /** + * 查询司法局法律规定、典型案例、单下载列表 + * + * @param htmlPages 司法局法律规定、典型案例、单下载 + * @return 司法局法律规定、典型案例、单下载集合 + */ + public List selectHtmlPagesList(HtmlPages htmlPages); + + /** + * 根据页面类型查询列表 + * + * @param pageType 页面类型 + * @return 司法局法律规定、典型案例、单下载集合 + */ + public List selectHtmlPagesListByType(String pageType); + + /** + * 新增司法局法律规定、典型案例、单下载 + * + * @param htmlPages 司法局法律规定、典型案例、单下载 + * @return 结果 + */ + public int insertHtmlPages(HtmlPages htmlPages); + + /** + * 修改司法局法律规定、典型案例、单下载 + * + * @param htmlPages 司法局法律规定、典型案例、单下载 + * @return 结果 + */ + public int updateHtmlPages(HtmlPages htmlPages); + + /** + * 批量删除司法局法律规定、典型案例、单下载 + * + * @param ids 需要删除的司法局法律规定、典型案例、单下载主键集合 + * @return 结果 + */ + public int deleteHtmlPagesByIds(Long[] ids); + + /** + * 删除司法局法律规定、典型案例、单下载信息 + * + * @param id 司法局法律规定、典型案例、单下载主键 + * @return 结果 + */ + public int deleteHtmlPagesById(Long id); + + /** + * 导入数据 + * + * @param htmlPagesList 司法局法律规定、典型案例、单下载数据列表 + * @param updateSupport 是否更新已存在的数据 + * @param operName 操作人 + * @return 导入结果消息 + */ + public String importHtmlPages(List htmlPagesList, boolean updateSupport, String operName); + + /** + * 删除附件 + * + * @param attachmentUrl 附件URL + * @return 删除结果 + */ + public boolean deleteAttachment(String attachmentUrl); + + /** + * 根据路径删除文件 + * + * @param path 文件路径 + * @return 删除结果 + */ + public boolean deleteFileByPath(String path); + + /** + * 更新页面浏览次数 + * + * @param id 页面ID + * @return 结果 + */ + public int updateHtmlPagesViewCount(Long id); +} diff --git a/boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/service/impl/HtmlPagesServiceImpl.java b/boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/service/impl/HtmlPagesServiceImpl.java new file mode 100644 index 0000000..2d0c674 --- /dev/null +++ b/boyue-models/boyue-hasfj/src/main/java/com/boyue/hasfj/service/impl/HtmlPagesServiceImpl.java @@ -0,0 +1,403 @@ +package com.boyue.hasfj.service.impl; + +import java.util.List; +import java.io.File; +import com.boyue.common.utils.DateUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import com.boyue.hasfj.mapper.HtmlPagesMapper; +import com.boyue.hasfj.domain.HtmlPages; +import com.boyue.hasfj.service.IHtmlPagesService; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.file.FileUtils; +import com.boyue.common.exception.ServiceException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +/** + * 司法局法律规定、典型案例、单下载Service业务层处理 + * + * @author boyue + * @date 2025-05-28 + */ +@Service +public class HtmlPagesServiceImpl implements IHtmlPagesService +{ + private static final Logger log = LoggerFactory.getLogger(HtmlPagesServiceImpl.class); + + @Autowired + private HtmlPagesMapper htmlPagesMapper; + + @Value("${boyue.profile}") + private String uploadPath; + + /** + * 查询司法局法律规定、典型案例、单下载 + * + * @param id 司法局法律规定、典型案例、单下载主键 + * @return 司法局法律规定、典型案例、单下载 + */ + @Override + public HtmlPages selectHtmlPagesById(Long id) + { + return htmlPagesMapper.selectHtmlPagesById(id); + } + + /** + * 根据formatId查询司法局法律规定、典型案例、单下载 + * + * @param formatId 访问ID + * @return 司法局法律规定、典型案例、单下载 + */ + @Override + public HtmlPages selectHtmlPagesByFormatId(String formatId) + { + if (StringUtils.isEmpty(formatId)) + { + return null; + } + return htmlPagesMapper.selectHtmlPagesByFormatId(formatId); + } + + /** + * 查询司法局法律规定、典型案例、单下载列表 + * + * @param htmlPages 司法局法律规定、典型案例、单下载 + * @return 司法局法律规定、典型案例、单下载 + */ + @Override + public List selectHtmlPagesList(HtmlPages htmlPages) + { + return htmlPagesMapper.selectHtmlPagesList(htmlPages); + } + + /** + * 根据页面类型查询列表 + * + * @param pageType 页面类型 + * @return 司法局法律规定、典型案例、单下载集合 + */ + @Override + public List selectHtmlPagesListByType(String pageType) + { + return htmlPagesMapper.selectHtmlPagesListByType(pageType); + } + + /** + * 新增司法局法律规定、典型案例、单下载 + * + * @param htmlPages 司法局法律规定、典型案例、单下载 + * @return 结果 + */ + @Override + public int insertHtmlPages(HtmlPages htmlPages) + { + // 验证多附件格式 + validateMultiAttachments(htmlPages); + + htmlPages.setCreateTime(DateUtils.getNowDate()); + return htmlPagesMapper.insertHtmlPages(htmlPages); + } + + /** + * 修改司法局法律规定、典型案例、单下载 + * + * @param htmlPages 司法局法律规定、典型案例、单下载 + * @return 结果 + */ + @Override + public int updateHtmlPages(HtmlPages htmlPages) + { + // 验证多附件格式 + validateMultiAttachments(htmlPages); + + htmlPages.setUpdateTime(DateUtils.getNowDate()); + return htmlPagesMapper.updateHtmlPages(htmlPages); + } + + /** + * 更新页面浏览次数(增加专门的方法处理浏览次数更新) + * + * @param id 页面ID + * @return 结果 + */ + @Override + public int updateHtmlPagesViewCount(Long id) + { + if (id == null || id <= 0) { + return 0; + } + return htmlPagesMapper.updateHtmlPagesViewCount(id); + } + + /** + * 批量删除司法局法律规定、典型案例、单下载 + * + * @param ids 需要删除的司法局法律规定、典型案例、单下载主键 + * @return 结果 + */ + @Override + public int deleteHtmlPagesByIds(Long[] ids) + { + return htmlPagesMapper.deleteHtmlPagesByIds(ids); + } + + /** + * 删除司法局法律规定、典型案例、单下载信息 + * + * @param id 司法局法律规定、典型案例、单下载主键 + * @return 结果 + */ + @Override + public int deleteHtmlPagesById(Long id) + { + return htmlPagesMapper.deleteHtmlPagesById(id); + } + + /** + * 验证附件格式 + * + * @param htmlPages 页面对象 + */ + private void validateMultiAttachments(HtmlPages htmlPages) + { + if (htmlPages.getMultiAttachments() != null && !htmlPages.getMultiAttachments().trim().startsWith("[")) + { + log.warn("附件格式不正确,将重置为空数组: {}", htmlPages.getMultiAttachments()); + htmlPages.setMultiAttachments("[]"); + } + } + + /** + * 导入数据 + * + * @param htmlPagesList 司法局法律规定、典型案例、单下载数据列表 + * @param updateSupport 是否更新已存在的数据 + * @param operName 操作人 + * @return 导入结果消息 + */ + @Override + public String importHtmlPages(List htmlPagesList, boolean updateSupport, String operName) + { + if (StringUtils.isNull(htmlPagesList) || htmlPagesList.size() == 0) + { + throw new ServiceException("导入数据不能为空!"); + } + int successNum = 0; + int failureNum = 0; + StringBuilder successMsg = new StringBuilder(); + StringBuilder failureMsg = new StringBuilder(); + for (HtmlPages htmlPages : htmlPagesList) + { + try + { + // 验证是否存在这个用户 + HtmlPages existHtmlPages = selectHtmlPagesByFormatId(htmlPages.getFormatId()); + if (StringUtils.isNull(existHtmlPages)) + { + htmlPages.setCreateBy(operName); + htmlPages.setCreateTime(DateUtils.getNowDate()); + htmlPages.setUpdateBy(operName); + htmlPages.setUpdateTime(DateUtils.getNowDate()); + // 设置默认值 + if (htmlPages.getStatus() == null) + { + htmlPages.setStatus(1); // 默认启用 + } + if (htmlPages.getSortOrder() == null) + { + htmlPages.setSortOrder(0L); // 默认排序值 + } + if (htmlPages.getViewCount() == null) + { + htmlPages.setViewCount(0L); // 默认浏览量为0 + } + if (StringUtils.isEmpty(htmlPages.getMultiAttachments())) + { + htmlPages.setMultiAttachments("[]"); // 默认附件为空数组 + } + this.insertHtmlPages(htmlPages); + successNum++; + successMsg.append("
" + successNum + "、页面标题 " + htmlPages.getTitle() + " 导入成功"); + } + else if (updateSupport) + { + htmlPages.setId(existHtmlPages.getId()); + htmlPages.setUpdateBy(operName); + htmlPages.setUpdateTime(DateUtils.getNowDate()); + this.updateHtmlPages(htmlPages); + successNum++; + successMsg.append("
" + successNum + "、页面标题 " + htmlPages.getTitle() + " 更新成功"); + } + else + { + failureNum++; + failureMsg.append("
" + failureNum + "、页面标题 " + htmlPages.getTitle() + " 已存在"); + } + } + catch (Exception e) + { + failureNum++; + String msg = "
" + failureNum + "、页面标题 " + htmlPages.getTitle() + " 导入失败:"; + failureMsg.append(msg + e.getMessage()); + log.error(msg, e); + } + } + if (failureNum > 0) + { + failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:"); + throw new ServiceException(failureMsg.toString()); + } + else + { + successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:"); + } + return successMsg.toString(); + } + + /** + * 删除附件 + * + * @param attachmentUrl 附件URL + * @return 删除结果 + */ + @Override + public boolean deleteAttachment(String attachmentUrl) { + if (StringUtils.isEmpty(attachmentUrl)) { + return false; + } + + log.info("删除附件: {}", attachmentUrl); + + // 处理URL格式问题 + String processedUrl = attachmentUrl; + + // 处理URL中可能存在的双斜杠问题 + if (processedUrl.contains("://")) { + final int baseUrlPart = processedUrl.indexOf("://") + 3; + processedUrl = processedUrl.substring(0, baseUrlPart) + + processedUrl.substring(baseUrlPart).replace("//", "/"); + } + + // 提取相对路径 + String relativePath = null; + + // 格式1: 包含/profile/的完整URL + if (processedUrl.contains("/profile/")) { + final int pathIndex = processedUrl.indexOf("/profile/"); + if (pathIndex >= 0) { + relativePath = processedUrl.substring(pathIndex + 9); // 9是'/profile/'的长度 + } + } + + // 格式2: 包含年月日格式的路径 + if (relativePath == null) { + java.util.regex.Matcher yearMatch = java.util.regex.Pattern.compile("/(\\d{4}/\\d{2}/\\d{2}/[^/]+)$").matcher(processedUrl); + if (yearMatch.find()) { + relativePath = yearMatch.group(1); + } + } + + // 格式3: files/master/路径格式 + if (relativePath == null && processedUrl.contains("/files/master/")) { + final int pathIndex = processedUrl.indexOf("/files/master/"); + if (pathIndex >= 0) { + relativePath = processedUrl.substring(pathIndex + 1); // 保留files/master/前缀 + } + } + + // 如果提取到了相对路径,进行进一步处理 + if (relativePath != null) { + // 检查是否为年份格式的路径,如果是且不包含files/master前缀,则添加 + if (relativePath.matches("^\\d{4}/\\d{2}/\\d{2}/.*") && !relativePath.contains("files/master")) { + relativePath = "files/master/" + relativePath; + } + + log.info("提取的相对路径: {}", relativePath); + return deleteFileByPath(relativePath); + } + + // 如果以上方法都无法提取路径,则直接尝试删除 + log.info("无法提取相对路径,尝试直接删除: {}", processedUrl); + File file = new File(uploadPath + File.separator + processedUrl); + if (file.exists() && file.isFile()) { + boolean result = file.delete(); + log.info("直接删除文件结果: {}", result ? "成功" : "失败"); + return result; + } + + log.warn("找不到文件: {}", processedUrl); + return false; + } + + /** + * 根据路径删除文件 + * + * @param path 文件路径 + * @return 删除结果 + */ + @Override + public boolean deleteFileByPath(String path) { + if (StringUtils.isEmpty(path)) { + return false; + } + + log.info("根据路径删除文件: {}", path); + + // 处理路径格式,确保正斜杠格式 + String processedPath = path; + if (path.contains("\\")) { + processedPath = path.replace("\\", "/"); + } + + // 确保没有连续的双斜杠(除了http://这种协议部分) + if (processedPath.contains("://")) { + String[] parts = processedPath.split("://", 2); + String protocolPart = parts[0] + "://"; + String pathPart = parts[1].replace("//", "/"); + processedPath = protocolPart + pathPart; + } else { + processedPath = processedPath.replace("//", "/"); + } + + // 构建完整文件路径 + String fullPath = uploadPath + File.separator + processedPath; + log.info("完整文件路径: {}", fullPath); + + // 规范化路径 + fullPath = fullPath.replace("/", File.separator); + + File file = new File(fullPath); + if (file.exists() && file.isFile()) { + boolean result = file.delete(); + log.info("删除文件结果: {}", result ? "成功" : "失败"); + return result; + } + + // 如果找不到文件,可能是因为路径问题,尝试直接在upload目录下查找 + String fileName = new File(processedPath).getName(); + try { + Files.walk(Paths.get(uploadPath)) + .filter(Files::isRegularFile) + .filter(p -> p.getFileName().toString().equals(fileName)) + .forEach(p -> { + try { + Files.deleteIfExists(p); + log.info("通过文件名查找并删除: {}", p); + } catch (IOException e) { + log.error("删除文件失败: {}", p, e); + } + }); + return true; + } catch (IOException e) { + log.error("搜索文件失败", e); + } + + log.warn("找不到文件: {}", fullPath); + return false; + } +} diff --git a/boyue-models/boyue-hasfj/src/main/resources/mapper/hasfj/HtmlPagesMapper.xml b/boyue-models/boyue-hasfj/src/main/resources/mapper/hasfj/HtmlPagesMapper.xml new file mode 100644 index 0000000..556290e --- /dev/null +++ b/boyue-models/boyue-hasfj/src/main/resources/mapper/hasfj/HtmlPagesMapper.xml @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + select + hp.id, + hp.format_id, + hp.title, + hp.content, + hp.page_type, + hp.page_url, + hp.create_time, + hp.update_time, + hp.status, + hp.sort_order, + hp.view_count, + hp.author, + hp.keywords, + hp.description, + hp.multi_attachments + from html_pages hp + + + + + + + + + + + + insert into html_pages + + format_id, + title, + content, + page_type, + page_url, + create_time, + update_time, + status, + sort_order, + view_count, + author, + keywords, + description, + multi_attachments, + + + #{formatId}, + #{title}, + #{content}, + #{pageType}, + #{pageUrl}, + #{createTime}, + #{updateTime}, + #{status}, + #{sortOrder}, + #{viewCount}, + #{author}, + #{keywords}, + #{description}, + #{multiAttachments}, + + + + + update html_pages + + format_id = #{formatId}, + title = #{title}, + content = #{content}, + page_type = #{pageType}, + page_url = #{pageUrl}, + create_time = #{createTime}, + update_time = #{updateTime}, + status = #{status}, + sort_order = #{sortOrder}, + view_count = #{viewCount}, + author = #{author}, + keywords = #{keywords}, + description = #{description}, + multi_attachments = #{multiAttachments}, + + where id = #{id} + + + + delete from html_pages where id = #{id} + + + + delete from html_pages where id in + + #{id} + + + + + + update html_pages + set view_count = IFNULL(view_count, 0) + 1, + update_time = sysdate() + where id = #{id} + + \ No newline at end of file diff --git a/boyue-models/boyue-message/pom.xml b/boyue-models/boyue-message/pom.xml new file mode 100644 index 0000000..92163f8 --- /dev/null +++ b/boyue-models/boyue-message/pom.xml @@ -0,0 +1,45 @@ + + + + boyue-models + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-message + + + message消息模块 + + + + + + + com.boyue + boyue-framework + + + + + com.boyue + boyue-common + + + + + com.boyue + boyue-tfa-phone + + + + + com.boyue + boyue-tfa-email + + + + \ No newline at end of file diff --git a/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/annotation/MessageLog.java b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/annotation/MessageLog.java new file mode 100644 index 0000000..2de35b9 --- /dev/null +++ b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/annotation/MessageLog.java @@ -0,0 +1,36 @@ +package com.boyue.modelMessage.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.boyue.modelMessage.enums.MessageType; + + +//自定义消息系统注解 +@Target({ ElementType.PARAMETER, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface MessageLog { + /** + * 消息操作的描述 + */ + String description() default ""; + + /** + * 消息标题 + */ + String title() default ""; + + /** + * 是否立即记录消息,默认为 true + */ + boolean immediateLog() default true; + + /** + * 操作类型 + */ + MessageType messageType() default MessageType.INFO; +} diff --git a/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/controller/MessageSystemController.java b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/controller/MessageSystemController.java new file mode 100644 index 0000000..2d56e06 --- /dev/null +++ b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/controller/MessageSystemController.java @@ -0,0 +1,214 @@ +package com.boyue.modelMessage.controller; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.domain.entity.SysUser; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.enums.BusinessType; +import com.boyue.common.exception.ServiceException; +import com.boyue.common.utils.DateUtils; +import com.boyue.common.utils.poi.ExcelUtil; +import com.boyue.modelMessage.domain.MessageSystem; +import com.boyue.modelMessage.service.IMessageSystemService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 消息管理Controller + * + * @author boyue + * @date 2024-12-21 + */ +@RestController +@RequestMapping("/modelMessage/messageSystem") +@Tag(name = "【消息管理】管理") +public class MessageSystemController extends BaseController +{ + @Autowired + private IMessageSystemService messageSystemService; + + /** + * 查询消息管理列表 + */ + @Operation(summary = "查询消息管理列表") + //@PreAuthorize("@ss.hasPermi('modelMessage:messageSystem:list')") + @GetMapping("/list") + public TableDataInfo list(MessageSystem messageSystem) + { + startPage(); + messageSystem.setCreateBy(getUsername()); + messageSystem.setMessageRecipient(getUsername()); + List list = messageSystemService.selectMessageSystemList(messageSystem); + return getDataTable(list); + } + + /** + * 导出消息管理列表 + */ + @Operation(summary = "导出消息管理列表") + @PreAuthorize("@ss.hasPermi('modelMessage:messageSystem:export')") + @Log(title = "消息管理", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, MessageSystem messageSystem) + { + List list = messageSystemService.selectMessageSystemList(messageSystem); + ExcelUtil util = new ExcelUtil(MessageSystem.class); + util.exportExcel(response, list, "消息管理数据"); + } + + /** + * 获取消息管理详细信息 + */ + @Operation(summary = "获取消息管理详细信息") + //@PreAuthorize("@ss.hasPermi('modelMessage:messageSystem:query')") + @GetMapping(value = "/{messageId}") + public AjaxResult getInfo(@PathVariable("messageId") Long messageId) + { + return success(messageSystemService.selectMessageSystemByMessageId(messageId)); + } + + /** + * 修改消息管理 + */ + @Operation(summary = "修改消息管理") + //@PreAuthorize("@ss.hasPermi('modelMessage:messageSystem:edit')") + @Log(title = "消息管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody MessageSystem messageSystem) + { + messageSystem.setUpdateBy(getUsername()); + messageSystem.setUpdateTime(DateUtils.getNowDate()); + return toAjax(messageSystemService.updateMessageSystem(messageSystem)); + } + + /** + * 删除消息管理 + */ + @Operation(summary = "删除消息管理") + //@PreAuthorize("@ss.hasPermi('modelMessage:messageSystem:remove')") + @Log(title = "消息管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{messageIds}") + public AjaxResult remove(@PathVariable( name = "messageIds" ) Long[] messageIds) + { + return toAjax(messageSystemService.deleteMessageSystemByMessageIds(messageIds)); + } + + /** + * 批量发送消息 + */ + @Operation(summary = "发送消息") + @Log(title = "发送消息", businessType = BusinessType.INSERT) + @PostMapping + @Transactional + public AjaxResult batchAdd(@RequestBody List messageSystemList) { + try { + messageSystemList.forEach(messageSystemService::processMessageSystem); + messageSystemService.batchInsertMessageSystem(messageSystemList); + return AjaxResult.success("消息发送成功!"); + } catch (Exception e) { + return AjaxResult.error("消息发送失败", e); + } + } + + /** + * 用户点击标题调整信息状态 + */ + @Operation(summary = "用户点击标题调整信息状态") + @PostMapping("/{messageId}") + public AjaxResult update(@PathVariable Long messageId){ + return success(messageSystemService.updateState(messageId)); + } + + /** + * 查询平台用户 sendMode 进行过滤。 + */ + @Operation(summary = "查询平台用户") + @GetMapping("/selectUser") + public AjaxResult selectUser(@RequestParam(required = false) String sendMode) { + try { + // 非空检查并提供默认值 + if (sendMode == null || sendMode.trim().isEmpty()) { + sendMode = "default"; + } + List list; + switch (sendMode) { + case "1": list = messageSystemService.getUsersFilteredBySendMode("phone"); break; // 短信 + case "2": list = messageSystemService.getUsersFilteredBySendMode("email"); break;// 邮件 + default: list = messageSystemService.selectUser(); break; //默认查询全部的用户 + } + return success(list); + } catch (ServiceException e) { + return AjaxResult.error(e.getMessage()); + } + } + + /** + * 查询角色信息 + */ + @Operation(summary = "查询角色") + @GetMapping("/selectRole") + public AjaxResult selectRole() { + return success(messageSystemService.selectRole()); + } + + /** + * 查询所有部门信息 + */ + @Operation(summary = "查询部门") + @GetMapping("/selectDept") + public AjaxResult selectDept() { + return success(messageSystemService.selectDept()); + } + + /** + * 根据角色ID获取所有符合条件的用户信息。 + * + * @param roleId 角色ID + * @return 用户信息列表 + */ + @Operation(summary = "根据角色Id查询所有符合条件的用户信息") + @GetMapping("/getUsersByRole/{roleId}") + public AjaxResult selectUsersByRoleId(@PathVariable Long roleId) { + return success(messageSystemService.selectUsersByRoleId(roleId)); + } + + /** + * 根据部门ID获取所有符合条件的用户信息。 + * + * @param deptId 部门ID + * @return 用户信息列表 + */ + @Operation(summary = "根据部门Id查询所有符合条件的用户信息") + @GetMapping("/getUserNameByDeptId/{deptId}") + public AjaxResult getUserNameByDeptId(@PathVariable Long deptId) { + return success(messageSystemService.getUserNameByDeptId(deptId)); + } + + /** + * 查询模版签名 + * @return 模版信息列表 + */ + @Operation(summary = "查询模版签名") + @GetMapping("/selecTemplates") + public AjaxResult selecTemplates() { + return success(messageSystemService.selecTemplates()); + } +} diff --git a/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/controller/MessageTemplateController.java b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/controller/MessageTemplateController.java new file mode 100644 index 0000000..991a483 --- /dev/null +++ b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/controller/MessageTemplateController.java @@ -0,0 +1,116 @@ +package com.boyue.modelMessage.controller; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.enums.BusinessType; +import com.boyue.common.utils.poi.ExcelUtil; +import com.boyue.modelMessage.domain.MessageTemplate; +import com.boyue.modelMessage.service.IMessageTemplateService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 模版管理Controller + * + * @author boyue + * @date 2024-12-31 + */ +@RestController +@RequestMapping("/modelMessage/template") +@Tag(name = "【模版管理】管理") +public class MessageTemplateController extends BaseController +{ + @Autowired + private IMessageTemplateService messageTemplateService; + + /** + * 查询模版管理列表 + */ + @Operation(summary = "查询模版管理列表") + //@PreAuthorize("@ss.hasPermi('modelMessage:template:list')") + @GetMapping("/list") + public TableDataInfo list(MessageTemplate messageTemplate) + { + startPage(); + List list = messageTemplateService.selectMessageTemplateList(messageTemplate); + return getDataTable(list); + } + + /** + * 导出模版管理列表 + */ + @Operation(summary = "导出模版管理列表") + @PreAuthorize("@ss.hasPermi('modelMessage:template:export')") + @Log(title = "模版管理", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, MessageTemplate messageTemplate) + { + List list = messageTemplateService.selectMessageTemplateList(messageTemplate); + ExcelUtil util = new ExcelUtil(MessageTemplate.class); + util.exportExcel(response, list, "模版管理数据"); + } + + /** + * 获取模版管理详细信息 + */ + @Operation(summary = "获取模版管理详细信息") + //@PreAuthorize("@ss.hasPermi('modelMessage:template:query')") + @GetMapping(value = "/{templateId}") + public AjaxResult getInfo(@PathVariable("templateId") Long templateId) + { + return success(messageTemplateService.selectMessageTemplateByTemplateId(templateId)); + } + + /** + * 新增模版管理 + */ + @Operation(summary = "新增模版管理") + //@PreAuthorize("@ss.hasPermi('modelMessage:template:add')") + @Log(title = "模版管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody MessageTemplate messageTemplate) + { + return toAjax(messageTemplateService.insertMessageTemplate(messageTemplate)); + } + + /** + * 修改模版管理 + */ + @Operation(summary = "修改模版管理") + //@PreAuthorize("@ss.hasPermi('modelMessage:template:edit')") + @Log(title = "模版管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody MessageTemplate messageTemplate) + { + return toAjax(messageTemplateService.updateMessageTemplate(messageTemplate)); + } + + /** + * 删除模版管理 + */ + @Operation(summary = "删除模版管理") + //@PreAuthorize("@ss.hasPermi('modelMessage:template:remove')") + @Log(title = "模版管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{templateIds}") + public AjaxResult remove(@PathVariable( name = "templateIds" ) Long[] templateIds) + { + return toAjax(messageTemplateService.deleteMessageTemplateByTemplateIds(templateIds)); + } +} diff --git a/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/controller/MessageVariableController.java b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/controller/MessageVariableController.java new file mode 100644 index 0000000..40f7c75 --- /dev/null +++ b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/controller/MessageVariableController.java @@ -0,0 +1,139 @@ +package com.boyue.modelMessage.controller; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.enums.BusinessType; +import com.boyue.common.utils.poi.ExcelUtil; +import com.boyue.modelMessage.domain.MessageVariable; +import com.boyue.modelMessage.service.IMessageVariableService; +import com.boyue.modelMessage.utils.MessageSystemUtils; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 变量管理Controller + * + * @author boyue + * @date 2024-12-31 + */ +@RestController +@RequestMapping("/modelMessage/variable") +@Tag(name = "【变量管理】管理") +public class MessageVariableController extends BaseController +{ + @Autowired + private IMessageVariableService messageVariableService; + + @Autowired + private MessageSystemUtils messageVariableUtils; + + /** + * 查询变量管理列表 + */ + @Operation(summary = "查询变量管理列表") + //@PreAuthorize("@ss.hasPermi('modelMessage:variable:list')") + @GetMapping("/list") + public TableDataInfo list(MessageVariable messageVariable) + { + startPage(); + List list = messageVariableService.selectMessageVariableList(messageVariable); + return getDataTable(list); + } + + /** + * 导出变量管理列表 + */ + @Operation(summary = "导出变量管理列表") + @PreAuthorize("@ss.hasPermi('modelMessage:variable:export')") + @Log(title = "变量管理", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, MessageVariable messageVariable) + { + List list = messageVariableService.selectMessageVariableList(messageVariable); + ExcelUtil util = new ExcelUtil(MessageVariable.class); + util.exportExcel(response, list, "变量管理数据"); + } + + /** + * 获取变量管理详细信息 + */ + @Operation(summary = "获取变量管理详细信息") + //@PreAuthorize("@ss.hasPermi('modelMessage:variable:query')") + @GetMapping(value = "/{variableId}") + public AjaxResult getInfo(@PathVariable("variableId") Long variableId) + { + return success(messageVariableService.selectMessageVariableByVariableId(variableId)); + } + + /** + * 新增变量管理 + */ + @Operation(summary = "新增变量管理") + //@PreAuthorize("@ss.hasPermi('modelMessage:variable:add')") + @Log(title = "变量管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody MessageVariable messageVariable) + { + return toAjax(messageVariableService.insertMessageVariable(messageVariable)); + } + + /** + * 修改变量管理 + */ + @Operation(summary = "修改变量管理") + //@PreAuthorize("@ss.hasPermi('modelMessage:variable:edit')") + @Log(title = "变量管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody MessageVariable messageVariable) + { + return toAjax(messageVariableService.updateMessageVariable(messageVariable)); + } + + /** + * 删除变量管理 + */ + @Operation(summary = "删除变量管理") + //@PreAuthorize("@ss.hasPermi('modelMessage:variable:remove')") + @Log(title = "变量管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{variableIds}") + public AjaxResult remove(@PathVariable( name = "variableIds" ) Long[] variableIds) + { + return toAjax(messageVariableService.deleteMessageVariableByVariableIds(variableIds)); + } + + /** + * 查询变量 + */ + @Operation(summary = "查询变量") + @GetMapping("/selectMessageVariable") + public AjaxResult selectMessageVariable() { + return success(messageVariableService.selectMessageVariable()); + } + + /** + * 根据变量类型生成不同的变量内容 + */ + @Operation(summary = "根据内置变量生成不同内容") + @GetMapping("/generate") + public AjaxResult generateVariableContent(@RequestParam String variableContent) { + return success(messageVariableUtils.generateVariableContent(variableContent)); + } +} diff --git a/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/domain/MessageSystem.java b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/domain/MessageSystem.java new file mode 100644 index 0000000..904cbf6 --- /dev/null +++ b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/domain/MessageSystem.java @@ -0,0 +1,159 @@ +package com.boyue.modelMessage.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.boyue.common.annotation.Excel; +import com.boyue.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 消息管理对象 message_system + * + * @author boyue + * @date 2024-12-21 + */ +@Schema(description = "消息管理对象") +public class MessageSystem extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 主键 */ + @Schema(title = "主键") + private Long messageId; + + /** 标题 */ + @Schema(title = "标题") + @Excel(name = "标题") + private String messageTitle; + + /** 消息内容 */ + @Schema(title = "消息内容") + @Excel(name = "消息内容") + private String messageContent; + + /** 消息状态(0未读 1已读) */ + @Schema(title = "消息状态(0未读 1已读)") + @Excel(name = "消息状态(0未读 1已读)") + private String messageStatus; + + /** 消息类型 */ + @Schema(title = "消息类型") + @Excel(name = "消息类型") + private String messageType; + + /** 消息类型 */ + @Schema(title = "接收人") + @Excel(name = "接收人") + private String messageRecipient; + + /** 发送方式(0平台 1手机号 2 邮箱) */ + @Schema(title = "发送方式(0平台 1手机号 2 邮箱)") + @Excel(name = "发送方式(0平台 1手机号 2 邮箱)") + private String sendMode; + + /** 号码 */ + @Schema(title = "号码") + @Excel(name = "号码") + private String code; + + public String getSendMode() { + return sendMode; + } + + public void setSendMode(String sendMode) { + this.sendMode = sendMode; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public void setMessageId(Long messageId) + { + this.messageId = messageId; + } + + public Long getMessageId() + { + return messageId; + } + + + public void setMessageTitle(String messageTitle) + { + this.messageTitle = messageTitle; + } + + public String getMessageTitle() + { + return messageTitle; + } + + + public void setMessageContent(String messageContent) + { + this.messageContent = messageContent; + } + + public String getMessageContent() + { + return messageContent; + } + + + public void setMessageStatus(String messageStatus) + { + this.messageStatus = messageStatus; + } + + public String getMessageStatus() + { + return messageStatus; + } + + + public void setMessageType(String messageType) + { + this.messageType = messageType; + } + + public String getMessageType() + { + return messageType; + } + + public String getMessageRecipient() { + return messageRecipient; + } + + public void setMessageRecipient(String messageRecipient) { + this.messageRecipient = messageRecipient; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("messageId", getMessageId()) + .append("messageTitle", getMessageTitle()) + .append("messageContent", getMessageContent()) + .append("messageStatus", getMessageStatus()) + .append("messageType", getMessageType()) + .append("messageRecipient", getMessageRecipient()) + .append("sendMode", getSendMode()) + .append("code", getCode()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } + + +} diff --git a/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/domain/MessageTemplate.java b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/domain/MessageTemplate.java new file mode 100644 index 0000000..720a9e9 --- /dev/null +++ b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/domain/MessageTemplate.java @@ -0,0 +1,134 @@ +package com.boyue.modelMessage.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.boyue.common.annotation.Excel; +import com.boyue.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 模版管理对象 message_template + * + * @author boyue + * @date 2024-12-31 + */ +@Schema(description = "模版管理对象") +public class MessageTemplate extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + + /** 主键 */ + @Schema(title = "主键") + private Long templateId; + + /** 模版名称 */ + @Schema(title = "模版名称") + @Excel(name = "模版名称") + private String templateName; + + /** 模版CODE */ + @Schema(title = "模版CODE") + @Excel(name = "模版CODE") + private String templateCode; + + /** 模版类型 */ + @Schema(title = "模版类型") + @Excel(name = "模版类型") + private String templateType; + + /** 模版内容 */ + @Schema(title = "模版内容") + @Excel(name = "模版内容") + private String templateContent; + + /** 变量属性 */ + @Schema(title = "变量属性") + @Excel(name = "变量属性") + private String templateVariable; + public void setTemplateId(Long templateId) + { + this.templateId = templateId; + } + + public Long getTemplateId() + { + return templateId; + } + + + public void setTemplateName(String templateName) + { + this.templateName = templateName; + } + + public String getTemplateName() + { + return templateName; + } + + + public void setTemplateCode(String templateCode) + { + this.templateCode = templateCode; + } + + public String getTemplateCode() + { + return templateCode; + } + + + public void setTemplateType(String templateType) + { + this.templateType = templateType; + } + + public String getTemplateType() + { + return templateType; + } + + + public void setTemplateContent(String templateContent) + { + this.templateContent = templateContent; + } + + public String getTemplateContent() + { + return templateContent; + } + + + public void setTemplateVariable(String templateVariable) + { + this.templateVariable = templateVariable; + } + + public String getTemplateVariable() + { + return templateVariable; + } + + + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("templateId", getTemplateId()) + .append("templateName", getTemplateName()) + .append("templateCode", getTemplateCode()) + .append("templateType", getTemplateType()) + .append("templateContent", getTemplateContent()) + .append("templateVariable", getTemplateVariable()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/domain/MessageVariable.java b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/domain/MessageVariable.java new file mode 100644 index 0000000..9543ab2 --- /dev/null +++ b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/domain/MessageVariable.java @@ -0,0 +1,107 @@ +package com.boyue.modelMessage.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.boyue.common.annotation.Excel; +import com.boyue.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 变量管理对象 message_variable + * + * @author boyue + * @date 2024-12-31 + */ +@Schema(description = "变量管理对象") +public class MessageVariable extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + + /** 主键 */ + @Schema(title = "主键") + private Long variableId; + + /** 变量名称 */ + @Schema(title = "变量名称") + @Excel(name = "变量名称") + private String variableName; + + /** 变量类型 */ + @Schema(title = "变量类型") + @Excel(name = "变量类型") + private String variableType; + + /** 变量内容 */ + @Schema(title = "变量内容") + @Excel(name = "变量内容") + private String variableContent; + public void setVariableId(Long variableId) + { + this.variableId = variableId; + } + + public MessageVariable(Long variableId, String variableName, String variableType, String variableContent) { + this.variableId = variableId; + this.variableName = variableName; + this.variableType = variableType; + this.variableContent = variableContent; + } + + public Long getVariableId() + { + return variableId; + } + + + public void setVariableName(String variableName) + { + this.variableName = variableName; + } + + public String getVariableName() + { + return variableName; + } + + + public void setVariableType(String variableLength) + { + this.variableType = variableLength; + } + + public String getVariableType() + { + return variableType; + } + + + public void setVariableContent(String variableContent) + { + this.variableContent = variableContent; + } + + public String getVariableContent() + { + return variableContent; + } + + + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("variableId", getVariableId()) + .append("variableName", getVariableName()) + .append("variableType", getVariableType()) + .append("variableContent", getVariableContent()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/enums/MessageType.java b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/enums/MessageType.java new file mode 100644 index 0000000..3d40f49 --- /dev/null +++ b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/enums/MessageType.java @@ -0,0 +1,25 @@ +package com.boyue.modelMessage.enums; + +public enum MessageType { + INFO("信息", "普通信息记录"), + WARN("警告", "非致命问题警告"), + ERROR("错误", "严重错误信息"), + DEBUG("调试", "调试信息,仅用于开发环境"), + SUCCESS("成功", "操作成功的提示信息"); + + private final String code; + private final String description; + + MessageType(String code, String description) { + this.code = code; + this.description = description; + } + + public String getCode() { + return code; + } + + public String getDescription() { + return description; + } +} diff --git a/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/enums/SendMode.java b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/enums/SendMode.java new file mode 100644 index 0000000..43fe225 --- /dev/null +++ b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/enums/SendMode.java @@ -0,0 +1,32 @@ +package com.boyue.modelMessage.enums; + +public enum SendMode { + PLATFORM("0", "平台"), + PHONE("1", "手机号"), + EMAIL("2", "邮箱"); + + private final String code; + private final String description; + + SendMode(String code, String description) { + this.code = code; + this.description = description; + } + + public String getCode() { + return code; + } + + public String getDescription() { + return description; + } + + public static SendMode fromCode(String code) { + for (SendMode mode : SendMode.values()) { + if (mode.getCode().equals(code)) { + return mode; + } + } + throw new IllegalArgumentException("未知的发送方式: " + code); + } +} diff --git a/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/mapper/MessageSystemMapper.java b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/mapper/MessageSystemMapper.java new file mode 100644 index 0000000..71158e5 --- /dev/null +++ b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/mapper/MessageSystemMapper.java @@ -0,0 +1,121 @@ +package com.boyue.modelMessage.mapper; + +import java.util.List; + +import org.apache.ibatis.annotations.Select; +import org.apache.ibatis.annotations.Update; + +import com.boyue.common.core.domain.entity.SysDept; +import com.boyue.common.core.domain.entity.SysRole; +import com.boyue.common.core.domain.entity.SysUser; +import com.boyue.modelMessage.domain.MessageSystem; +import com.boyue.modelMessage.domain.MessageTemplate; + +/** + * 消息管理Mapper接口 + * + * @author boyue + * @date 2024-12-21 + */ +public interface MessageSystemMapper +{ + /** + * 查询消息管理 + * + * @param messageId 消息管理主键 + * @return 消息管理 + */ + public MessageSystem selectMessageSystemByMessageId(Long messageId); + + /** + * 查询消息管理列表 + * + * @param messageSystem 消息管理 + * @return 消息管理集合 + */ + public List selectMessageSystemList(MessageSystem messageSystem); + + /** + * 新增消息管理 + * + * @param messageSystem 消息管理 + * @return 结果 + */ + public int insertMessageSystem(MessageSystem messageSystem); + + /** + * 修改消息管理 + * + * @param messageSystem 消息管理 + * @return 结果 + */ + public int updateMessageSystem(MessageSystem messageSystem); + + /** + * 删除消息管理 + * + * @param messageId 消息管理主键 + * @return 结果 + */ + public int deleteMessageSystemByMessageId(Long messageId); + + /** + * 批量删除消息管理 + * + * @param messageIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteMessageSystemByMessageIds(Long[] messageIds); + + //查询平台系统资源收件人用户信息 + @Select("SELECT user_id, dept_id, user_name, phonenumber, email FROM `sys_user`") + public List selectUser(); + + //查询角色信息 然后根据角色把消息发给某角色 + @Select("SELECT role_id, role_name FROM `sys_role`") + public List selectRole(); + + //查询部门信息 然后根据部门把消息发给某部门 + @Select("SELECT dept_id, parent_id, dept_name FROM `sys_dept`") + public List selectDept(); + + // 根据发送方式过滤用户 (短信或邮箱) + @Select("") + List selectUserBySendMode(String filterType); + + //将信息状态未读信息变为已读 + @Update("update message_system set message_status = 1 where message_id = #{messageId} and message_recipient = #{userName}") + public int updateState(Long messageId, String userName); + + + /** + * 根据部门ID获取所有符合条件的用户信息。 + * + * @param deptId 部门ID + * @return 用户信息列表 + */ + @Select("SELECT u.user_name FROM sys_user u JOIN sys_dept d ON u.dept_id = d.dept_id WHERE d.dept_id = #{deptId}") + public List getUserNameByDeptId(Long deptId); + + /** + * 根据角色ID查询用户列表。 + * + * @param roleId 角色ID + * @return 用户列表 + */ + @Select("SELECT u.user_name FROM sys_user u JOIN sys_user_role ur ON u.user_id = ur.user_id WHERE ur.role_id = #{roleId}") + public List selectUsersByRoleId(Long roleId); + + //查询模版签名 + @Select("SELECT template_id,template_code FROM `message_template`") + public List selecTemplates(); + + //批量发送信息 + public int batchInsertMessageSystem(List messageSystemList); +} diff --git a/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/mapper/MessageTemplateMapper.java b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/mapper/MessageTemplateMapper.java new file mode 100644 index 0000000..9fcedc3 --- /dev/null +++ b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/mapper/MessageTemplateMapper.java @@ -0,0 +1,73 @@ +package com.boyue.modelMessage.mapper; + +import java.util.List; + +import org.apache.ibatis.annotations.Select; + +import com.boyue.modelMessage.domain.MessageTemplate; + +/** + * 模版管理Mapper接口 + * + * @author boyue + * @date 2024-12-31 + */ +public interface MessageTemplateMapper +{ + /** + * 查询模版管理 + * + * @param templateId 模版管理主键 + * @return 模版管理 + */ + public MessageTemplate selectMessageTemplateByTemplateId(Long templateId); + + /** + * 查询模版管理列表 + * + * @param messageTemplate 模版管理 + * @return 模版管理集合 + */ + public List selectMessageTemplateList(MessageTemplate messageTemplate); + + /** + * 新增模版管理 + * + * @param messageTemplate 模版管理 + * @return 结果 + */ + public int insertMessageTemplate(MessageTemplate messageTemplate); + + /** + * 修改模版管理 + * + * @param messageTemplate 模版管理 + * @return 结果 + */ + public int updateMessageTemplate(MessageTemplate messageTemplate); + + /** + * 删除模版管理 + * + * @param templateId 模版管理主键 + * @return 结果 + */ + public int deleteMessageTemplateByTemplateId(Long templateId); + + /** + * 批量删除模版管理 + * + * @param templateIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteMessageTemplateByTemplateIds(Long[] templateIds); + + /** + * 根据模板templateCode查询模板信息 + * + * @param templateCode 模版编码 + * @return 模版对象 + */ + @Select("SELECT template_code,template_variable,template_content FROM `message_template` WHERE template_code = #{templateCode}") + public MessageTemplate selectMessageTemplateByTemplateCode(String templateCode); +} diff --git a/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/mapper/MessageVariableMapper.java b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/mapper/MessageVariableMapper.java new file mode 100644 index 0000000..6b6e1ba --- /dev/null +++ b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/mapper/MessageVariableMapper.java @@ -0,0 +1,75 @@ +package com.boyue.modelMessage.mapper; + +import java.util.List; + +import org.apache.ibatis.annotations.Select; + +import com.boyue.modelMessage.domain.MessageVariable; + +/** + * 变量管理Mapper接口 + * + * @author boyue + * @date 2024-12-31 + */ +public interface MessageVariableMapper +{ + /** + * 查询变量管理 + * + * @param variableId 变量管理主键 + * @return 变量管理 + */ + public MessageVariable selectMessageVariableByVariableId(Long variableId); + + /** + * 查询变量管理列表 + * + * @param messageVariable 变量管理 + * @return 变量管理集合 + */ + public List selectMessageVariableList(MessageVariable messageVariable); + + /** + * 新增变量管理 + * + * @param messageVariable 变量管理 + * @return 结果 + */ + public int insertMessageVariable(MessageVariable messageVariable); + + /** + * 修改变量管理 + * + * @param messageVariable 变量管理 + * @return 结果 + */ + public int updateMessageVariable(MessageVariable messageVariable); + + /** + * 删除变量管理 + * + * @param variableId 变量管理主键 + * @return 结果 + */ + public int deleteMessageVariableByVariableId(Long variableId); + + /** + * 批量删除变量管理 + * + * @param variableIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteMessageVariableByVariableIds(Long[] variableIds); + + //查询变量 + @Select("SELECT variable_id, variable_name, variable_type, variable_content FROM message_variable") + public List selectMessageVariable(); + + //查询在使用模版签名时用到了那些变量一一赋值 + public List selectMessageVariables(List variableNames); + + //查询模版使用的变量 + @Select("SELECT template_variable FROM message_template") + public List selectAllTemplateVariables(); +} diff --git a/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/service/IMessageSystemService.java b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/service/IMessageSystemService.java new file mode 100644 index 0000000..cb89637 --- /dev/null +++ b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/service/IMessageSystemService.java @@ -0,0 +1,108 @@ +package com.boyue.modelMessage.service; + +import java.util.List; + +import com.boyue.common.core.domain.entity.SysDept; +import com.boyue.common.core.domain.entity.SysRole; +import com.boyue.common.core.domain.entity.SysUser; +import com.boyue.modelMessage.domain.MessageSystem; +import com.boyue.modelMessage.domain.MessageTemplate; + +/** + * 消息管理Service接口 + * + * @author boyue + * @date 2024-12-21 + */ +public interface IMessageSystemService +{ + /** + * 查询消息管理 + * + * @param messageId 消息管理主键 + * @return 消息管理 + */ + public MessageSystem selectMessageSystemByMessageId(Long messageId); + + /** + * 查询消息管理列表 + * + * @param messageSystem 消息管理 + * @return 消息管理集合 + */ + public List selectMessageSystemList(MessageSystem messageSystem); + + /** + * 新增消息管理 + * + * @param messageSystem 消息管理 + * @return 结果 + */ + public int insertMessageSystem(MessageSystem messageSystem); + + /** + * 修改消息管理 + * + * @param messageSystem 消息管理 + * @return 结果 + */ + public int updateMessageSystem(MessageSystem messageSystem); + + /** + * 批量删除消息管理 + * + * @param messageIds 需要删除的消息管理主键集合 + * @return 结果 + */ + public int deleteMessageSystemByMessageIds(Long[] messageIds); + + /** + * 删除消息管理信息 + * + * @param messageId 消息管理主键 + * @return 结果 + */ + public int deleteMessageSystemByMessageId(Long messageId); + + //查询系统资源用户信息 + public List selectUser(); + + //将信息状态未读信息变为已读 + public int updateState(Long messageId); + + //根据发送方式 执行不同操作 + public void processMessageSystem(MessageSystem messageSystem); + + // 根据发送方式过滤用户 (短信或邮箱) + public List getUsersFilteredBySendMode(String filterType); + + //查询角色信息 然后根据角色把消息发给某角色 + public List selectRole(); + + //查询部门信息 然后根据部门把消息发给某部门 + public List selectDept(); + + /** + * 根据角色ID获取用户列表。 + * + * @param roleId 角色ID + * @return + * + * */ + public List selectUsersByRoleId(Long roleId); + + /** + * 根据部门ID获取用户列表。 + * + * @param deptId 部门ID + * @return + * + * */ + public List getUserNameByDeptId(Long deptId); + + // 查询模版签名 + public List selecTemplates(); + + // 批量发送信息 + public int batchInsertMessageSystem(List messageSystemList); +} diff --git a/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/service/IMessageTemplateService.java b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/service/IMessageTemplateService.java new file mode 100644 index 0000000..5ec471b --- /dev/null +++ b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/service/IMessageTemplateService.java @@ -0,0 +1,62 @@ +package com.boyue.modelMessage.service; + +import java.util.List; + +import com.boyue.modelMessage.domain.MessageTemplate; + +/** + * 模版管理Service接口 + * + * @author boyue + * @date 2024-12-31 + */ +public interface IMessageTemplateService +{ + /** + * 查询模版管理 + * + * @param templateId 模版管理主键 + * @return 模版管理 + */ + public MessageTemplate selectMessageTemplateByTemplateId(Long templateId); + + /** + * 查询模版管理列表 + * + * @param messageTemplate 模版管理 + * @return 模版管理集合 + */ + public List selectMessageTemplateList(MessageTemplate messageTemplate); + + /** + * 新增模版管理 + * + * @param messageTemplate 模版管理 + * @return 结果 + */ + public int insertMessageTemplate(MessageTemplate messageTemplate); + + /** + * 修改模版管理 + * + * @param messageTemplate 模版管理 + * @return 结果 + */ + public int updateMessageTemplate(MessageTemplate messageTemplate); + + /** + * 批量删除模版管理 + * + * @param templateIds 需要删除的模版管理主键集合 + * @return 结果 + */ + public int deleteMessageTemplateByTemplateIds(Long[] templateIds); + + /** + * 删除模版管理信息 + * + * @param templateId 模版管理主键 + * @return 结果 + */ + public int deleteMessageTemplateByTemplateId(Long templateId); +} diff --git a/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/service/IMessageVariableService.java b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/service/IMessageVariableService.java new file mode 100644 index 0000000..1d17aee --- /dev/null +++ b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/service/IMessageVariableService.java @@ -0,0 +1,67 @@ +package com.boyue.modelMessage.service; + +import java.util.List; + +import com.boyue.modelMessage.domain.MessageVariable; + +/** + * 变量管理Service接口 + * + * @author boyue + * @date 2024-12-31 + */ +public interface IMessageVariableService +{ + /** + * 查询变量管理 + * + * @param variableId 变量管理主键 + * @return 变量管理 + */ + public MessageVariable selectMessageVariableByVariableId(Long variableId); + + /** + * 查询变量管理列表 + * + * @param messageVariable 变量管理 + * @return 变量管理集合 + */ + public List selectMessageVariableList(MessageVariable messageVariable); + + /** + * 新增变量管理 + * + * @param messageVariable 变量管理 + * @return 结果 + */ + public int insertMessageVariable(MessageVariable messageVariable); + + /** + * 修改变量管理 + * + * @param messageVariable 变量管理 + * @return 结果 + */ + public int updateMessageVariable(MessageVariable messageVariable); + + /** + * 批量删除变量管理 + * + * @param variableIds 需要删除的变量管理主键集合 + * @return 结果 + */ + public int deleteMessageVariableByVariableIds(Long[] variableIds); + + /** + * 删除变量管理信息 + * + * @param variableId 变量管理主键 + * @return 结果 + */ + public int deleteMessageVariableByVariableId(Long variableId); + + /** + * 查询全部变量 + */ + public List selectMessageVariable(); +} diff --git a/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/service/impl/MessageSystemServiceImpl.java b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/service/impl/MessageSystemServiceImpl.java new file mode 100644 index 0000000..c1463e1 --- /dev/null +++ b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/service/impl/MessageSystemServiceImpl.java @@ -0,0 +1,305 @@ +package com.boyue.modelMessage.service.impl; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.alibaba.fastjson2.JSONObject; +import com.boyue.common.core.domain.entity.SysDept; +import com.boyue.common.core.domain.entity.SysRole; +import com.boyue.common.core.domain.entity.SysUser; +import com.boyue.common.exception.ServiceException; +import com.boyue.common.utils.DateUtils; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.modelMessage.domain.MessageSystem; +import com.boyue.modelMessage.domain.MessageTemplate; +import com.boyue.modelMessage.enums.SendMode; +import com.boyue.modelMessage.mapper.MessageSystemMapper; +import com.boyue.modelMessage.service.IMessageSystemService; +import com.boyue.modelMessage.utils.MessageSystemUtils; +import com.boyue.tfa.email.utils.EmailUtil; +import com.boyue.tfa.phone.config.DySmsConfig; +import com.boyue.tfa.phone.domain.DySmsTemplate; +import com.boyue.tfa.phone.utils.DySmsUtil; + +/** + * 消息管理Service业务层处理 + * + */ +@Service +public class MessageSystemServiceImpl implements IMessageSystemService { + + private static final Logger log = LoggerFactory.getLogger(MessageSystemServiceImpl.class); + + @Autowired + private DySmsConfig dySmsConfig; + + @Autowired + private MessageSystemMapper messageSystemMapper; + + @Autowired + private MessageSystemUtils messageSystemUtils; + + /** + * 查询消息管理 + * + * @param messageId 消息管理主键 + * @return 消息管理 + */ + @Override + public MessageSystem selectMessageSystemByMessageId(Long messageId) { + return messageSystemMapper.selectMessageSystemByMessageId(messageId); + } + + /** + * 查询消息管理列表 + * + * @param messageSystem 消息管理 + * @return 消息管理列表 + */ + @Override + public List selectMessageSystemList(MessageSystem messageSystem) { + return messageSystemMapper.selectMessageSystemList(messageSystem); + } + + /** + * 新增消息管理 + * + * @param messageSystem 消息管理 + * @return 结果 + */ + @Override + public int insertMessageSystem(MessageSystem messageSystem) { + messageSystem.setMessageStatus("0"); // 默认发送信息为未读状态 + messageSystem.setCreateBy(SecurityUtils.getUsername()); + messageSystem.setUpdateBy(SecurityUtils.getUsername()); + messageSystem.setCreateTime(DateUtils.getNowDate()); + messageSystem.setUpdateTime(DateUtils.getNowDate()); + return messageSystemMapper.insertMessageSystem(messageSystem); + } + + /** + * 修改消息管理 + * + * @param messageSystem 消息管理 + * @return 结果 + */ + @Override + public int updateMessageSystem(MessageSystem messageSystem) { + messageSystem.setUpdateTime(DateUtils.getNowDate()); + return messageSystemMapper.updateMessageSystem(messageSystem); + } + + /** + * 批量删除消息管理 + * + * @param messageIds 需要删除的消息管理主键 + * @return 结果 + */ + @Override + public int deleteMessageSystemByMessageIds(Long[] messageIds) { + return messageSystemMapper.deleteMessageSystemByMessageIds(messageIds); + } + + /** + * 删除消息管理信息 + * + * @param messageId 消息管理主键 + * @return 结果 + */ + @Override + public int deleteMessageSystemByMessageId(Long messageId) { + return messageSystemMapper.deleteMessageSystemByMessageId(messageId); + } + + // 查询系统资源用户信息 + @Override + public List selectUser() { + return messageSystemMapper.selectUser(); + } + + // 收件人为本人的话点击信息详情的时候然后把状态未读信息修改为已读 + @Override + public int updateState(Long messageId) { + int result = messageSystemMapper.updateState(messageId, SecurityUtils.getUsername()); + return result; + } + + // 根据发送方式过滤用户 (短信或邮箱) + @Override + public List getUsersFilteredBySendMode(String filterType) { + return messageSystemMapper.selectUserBySendMode(filterType); + } + + // 查询角色信息 然后根据角色把消息发给某角色 + @Override + public List selectRole() { + return messageSystemMapper.selectRole(); + } + + // 查询部门信息 然后根据部门把消息发给某部门 + @Override + public List selectDept() { + return messageSystemMapper.selectDept(); + } + + /** + * 根据角色ID获取对应用户信息。 + * + * @param roleId 角色ID + * @return 符合条件的用户列表 + */ + @Override + public List selectUsersByRoleId(Long roleId) { + List roleList = messageSystemMapper.selectUsersByRoleId(roleId); + return roleList; + } + + /** + * 根据角色ID获取用户ID列表。 + * + * @param roleId 角色ID + * @return 用户ID列表 + */ + @Override + public List getUserNameByDeptId(Long deptId) { + List depts = messageSystemMapper.getUserNameByDeptId(deptId); + return depts; + } + + // 查询模版签名 + @Override + public List selecTemplates() { + List templates = messageSystemMapper.selecTemplates(); + return templates; + } + + /** + * 批量发送信息 + */ + @Override + public int batchInsertMessageSystem(List messageSystemList) { + String username = SecurityUtils.getUsername(); + Date nowDate = DateUtils.getNowDate(); + for (MessageSystem messageSystem : messageSystemList) { + messageSystem.setMessageStatus("0"); // 默认发送信息为未读状态 + messageSystem.setCreateBy(username); + messageSystem.setUpdateBy(username); + messageSystem.setCreateTime(nowDate); + messageSystem.setUpdateTime(nowDate); + } + int result = messageSystemMapper.batchInsertMessageSystem(messageSystemList); + if (result <= 0) { + throw new ServiceException("消息发送失败!"); + } + return result; + } + + /** + * 根据 MessageSystem 对象的 sendMode 属性处理消息的发送方式 + */ + @Override + public void processMessageSystem(MessageSystem messageSystem) { + if (messageSystem == null || messageSystem.getSendMode() == null) { + throw new ServiceException("无效的消息系统对象或发送方式!"); + } + String sendModeStr = messageSystem.getSendMode(); + SendMode sendMode; + try { + sendMode = SendMode.fromCode(sendModeStr); + } catch (IllegalArgumentException e) { + throw new ServiceException("类型转换失败: " + sendModeStr); + } + switch (sendMode) { + case PHONE: + sendNotificationMessage(messageSystem); + break; + case EMAIL: + handleEmailNotification(messageSystem); + break; + case PLATFORM: + sendPlatformMessage(messageSystem); + break; + default: + throw new ServiceException("未知的发送方式!"); + } + } + + /** + * 发送平台消息 + */ + public void sendPlatformMessage(MessageSystem messageSystem) { + String notificationContent = messageSystem.getMessageContent(); + try { + String filledMessage = notificationContent.startsWith("SMS_") + ? messageSystemUtils.processTemplateMessage(messageSystem, notificationContent) + : notificationContent; // 是自定义输入,使用用户输入的内容 + log.info("平台内容: {}", filledMessage); + messageSystem.setMessageContent(filledMessage); + } catch (Exception e) { + log.error("发送平台消息时发生异常: ", e); + throw new ServiceException("发送平台消息异常:" + e.getMessage()); + } + } + + /** + * 发送邮件通知 + */ + public void handleEmailNotification(MessageSystem messageSystem) { + String email = messageSystem.getCode(); + if (StringUtils.isEmpty(email)) { + throw new ServiceException("邮箱地址不能为空!"); + } + try { + String filledMessage = messageSystem.getMessageContent().startsWith("SMS_") + ? messageSystemUtils.processTemplateMessage(messageSystem, messageSystem.getMessageContent()) + : messageSystem.getMessageContent(); // 是自定义输入,则直接使用用户提供的内容 + log.info("邮件内容: {}", filledMessage); + messageSystem.setMessageContent(filledMessage); + EmailUtil.sendMessage(email, "通知", filledMessage); + } catch (Exception e) { + log.error("发送邮件时发生异常: ", e); + throw new ServiceException("发送通知信息异常:" + e.getMessage()); + } + } + + /** + * 发送短信通知 + */ + @SuppressWarnings("unchecked") + public void sendNotificationMessage(MessageSystem messageSystem) { + String phone = messageSystem.getCode(); + if (StringUtils.isEmpty(phone)) { + throw new ServiceException("手机号码为空!"); + } + try { + // 解析并处理模板消息 + Map parsedParams = messageSystemUtils.parseInput(messageSystem.getMessageContent()); + String templateCode = (String) parsedParams.get("templateCode"); + DySmsTemplate dySmsTemplate = null; + if (templateCode != null) { + dySmsTemplate = dySmsConfig.getTemplate().get(templateCode); + Map params = (Map) parsedParams.get("params"); + // 提取模板中的变量名并填充内置变量 + List variableNames = (List) parsedParams.get("variableNames"); + messageSystemUtils.fillBuiltInVariables(params, messageSystem, variableNames); + String filledMessage = messageSystemUtils.fillTemplate((String) parsedParams.get("templateContent"), + params); + messageSystem.setMessageContent(filledMessage); + JSONObject templateParamJson = new JSONObject(params); + DySmsUtil.sendSms(phone, dySmsTemplate, templateParamJson); + } else { + DySmsUtil.sendSms(phone, null, null); + } + } catch (Exception e) { + log.error("发送短信时发生异常: ", e); + throw new ServiceException("发送短信异常:" + e.getMessage()); + } + } +} diff --git a/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/service/impl/MessageTemplateServiceImpl.java b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/service/impl/MessageTemplateServiceImpl.java new file mode 100644 index 0000000..5d5c50f --- /dev/null +++ b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/service/impl/MessageTemplateServiceImpl.java @@ -0,0 +1,104 @@ +package com.boyue.modelMessage.service.impl; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.boyue.common.utils.DateUtils; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.modelMessage.domain.MessageTemplate; +import com.boyue.modelMessage.mapper.MessageTemplateMapper; +import com.boyue.modelMessage.service.IMessageTemplateService; + + +/** + * 模版管理Service业务层处理 + * + * @author boyue + * @date 2024-12-31 + */ +@Service +public class MessageTemplateServiceImpl implements IMessageTemplateService +{ + @Autowired + private MessageTemplateMapper messageTemplateMapper; + + /** + * 查询模版管理 + * + * @param templateId 模版管理主键 + * @return 模版管理 + */ + @Override + public MessageTemplate selectMessageTemplateByTemplateId(Long templateId) + { + return messageTemplateMapper.selectMessageTemplateByTemplateId(templateId); + } + + /** + * 查询模版管理列表 + * + * @param messageTemplate 模版管理 + * @return 模版管理 + */ + @Override + public List selectMessageTemplateList(MessageTemplate messageTemplate) + { + return messageTemplateMapper.selectMessageTemplateList(messageTemplate); + } + + /** + * 新增模版管理 + * + * @param messageTemplate 模版管理 + * @return 结果 + */ + @Override + public int insertMessageTemplate(MessageTemplate messageTemplate) + { + messageTemplate.setCreateBy(SecurityUtils.getUsername()); + messageTemplate.setCreateTime(DateUtils.getNowDate()); + messageTemplate.setUpdateBy(SecurityUtils.getUsername()); + messageTemplate.setUpdateTime(DateUtils.getNowDate()); + return messageTemplateMapper.insertMessageTemplate(messageTemplate); + } + + /** + * 修改模版管理 + * + * @param messageTemplate 模版管理 + * @return 结果 + */ + @Override + public int updateMessageTemplate(MessageTemplate messageTemplate) + { + messageTemplate.setUpdateBy(SecurityUtils.getUsername()); + messageTemplate.setUpdateTime(DateUtils.getNowDate()); + return messageTemplateMapper.updateMessageTemplate(messageTemplate); + } + + /** + * 批量删除模版管理 + * + * @param templateIds 需要删除的模版管理主键 + * @return 结果 + */ + @Override + public int deleteMessageTemplateByTemplateIds(Long[] templateIds) + { + return messageTemplateMapper.deleteMessageTemplateByTemplateIds(templateIds); + } + + /** + * 删除模版管理信息 + * + * @param templateId 模版管理主键 + * @return 结果 + */ + @Override + public int deleteMessageTemplateByTemplateId(Long templateId) + { + return messageTemplateMapper.deleteMessageTemplateByTemplateId(templateId); + } +} diff --git a/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/service/impl/MessageVariableServiceImpl.java b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/service/impl/MessageVariableServiceImpl.java new file mode 100644 index 0000000..560389a --- /dev/null +++ b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/service/impl/MessageVariableServiceImpl.java @@ -0,0 +1,129 @@ +package com.boyue.modelMessage.service.impl; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.boyue.common.exception.ServiceException; +import com.boyue.common.utils.DateUtils; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.modelMessage.domain.MessageVariable; +import com.boyue.modelMessage.mapper.MessageVariableMapper; +import com.boyue.modelMessage.service.IMessageVariableService; + +/** + * 变量管理Service业务层处理 + * + * @author boyue + * @date 2024-12-31 + */ +@Service +public class MessageVariableServiceImpl implements IMessageVariableService +{ + + @Autowired + private MessageVariableMapper messageVariableMapper; + + /** + * 查询变量管理 + * + * @param variableId 变量管理主键 + * @return 变量管理 + */ + @Override + public MessageVariable selectMessageVariableByVariableId(Long variableId) + { + return messageVariableMapper.selectMessageVariableByVariableId(variableId); + } + + /** + * 查询变量管理列表 + * + * @param messageVariable 变量管理 + * @return 变量管理 + */ + @Override + public List selectMessageVariableList(MessageVariable messageVariable) + { + return messageVariableMapper.selectMessageVariableList(messageVariable); + } + + /** + * 新增变量管理 + * + * @param messageVariable 变量管理 + * @return 结果 + */ + @Override + public int insertMessageVariable(MessageVariable messageVariable) + { + messageVariable.setCreateBy(SecurityUtils.getUsername()); + messageVariable.setCreateTime(DateUtils.getNowDate()); + messageVariable.setUpdateBy(SecurityUtils.getUsername()); + messageVariable.setUpdateTime(DateUtils.getNowDate()); + return messageVariableMapper.insertMessageVariable(messageVariable); + } + + /** + * 修改变量管理 + * + * @param messageVariable 变量管理 + * @return 结果 + */ + @Override + public int updateMessageVariable(MessageVariable messageVariable) + { + messageVariable.setUpdateBy(SecurityUtils.getUsername()); + messageVariable.setUpdateTime(DateUtils.getNowDate()); + return messageVariableMapper.updateMessageVariable(messageVariable); + } + + /** + * 批量删除变量管理 + * + * @param variableIds 需要删除的变量管理主键 + * @return 结果 + */ + @Override + public int deleteMessageVariableByVariableIds(Long[] variableIds) + { + for (Long variableId : variableIds) { + // 获取变量信息 + MessageVariable variable = messageVariableMapper.selectMessageVariableByVariableId(variableId); + if (variable == null) { + throw new ServiceException("未找到该变量信息!"); + } + // 检查变量是否被模板使用 + String variableName = variable.getVariableName(); + List variables = messageVariableMapper.selectAllTemplateVariables(); + for (String templateVariable : variables) { + String[] templateParts = templateVariable.split("/"); + for (String part : templateParts) { + if (part.equals(variableName)) { + throw new ServiceException("变量为 '" + variableName + "'' 已被模版使用,不能删除!"); + } + } + } + } + return messageVariableMapper.deleteMessageVariableByVariableIds(variableIds); + } + + /** + * 删除变量管理信息 + * + * @param variableId 变量管理主键 + * @return 结果 + */ + @Override + public int deleteMessageVariableByVariableId(Long variableId) + { + return messageVariableMapper.deleteMessageVariableByVariableId(variableId); + } + + //查询变量 + @Override + public List selectMessageVariable() { + return messageVariableMapper.selectMessageVariable(); + } +} diff --git a/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/utils/MessageLogAspect.java b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/utils/MessageLogAspect.java new file mode 100644 index 0000000..ff583af --- /dev/null +++ b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/utils/MessageLogAspect.java @@ -0,0 +1,68 @@ +package com.boyue.modelMessage.utils; + +import java.lang.reflect.Method; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import com.boyue.common.utils.DateUtils; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.modelMessage.annotation.MessageLog; +import com.boyue.modelMessage.domain.MessageSystem; +import com.boyue.modelMessage.enums.MessageType; +import com.boyue.modelMessage.service.IMessageSystemService; + +@Aspect +@Component +@Order(1) //Aspect 的优先级 +public class MessageLogAspect { + + private static final Logger logger = LoggerFactory.getLogger(MessageLogAspect.class); + + @Autowired + private IMessageSystemService messageSystemService; + + @Around("@annotation(messageLog)") + public Object handleMessageLog(ProceedingJoinPoint joinPoint, MessageLog messageLog) throws Throwable { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + // 获取注解信息 + String description = messageLog.description(); + String title = messageLog.title(); + MessageType messageType = messageLog.messageType(); + boolean immediateLog = messageLog.immediateLog(); + try { + Object result = joinPoint.proceed(); + // 如果设置了true立即执行 + if (immediateLog) { + logMessage(title, description, messageType); + } + return result; + } catch (Exception e) { + logger.error("消息记录失败,方法: {}, 描述: {}", method.getName(), description, e); + throw e; + } + } + + private void logMessage(String title, String description, MessageType messageType) { + MessageSystem messageSystem = new MessageSystem(); + messageSystem.setMessageTitle(title); // 标题 + messageSystem.setCreateBy(SecurityUtils.getUsername()); // 发送人 + messageSystem.setCreateTime(DateUtils.getNowDate()); // 发送时间 + messageSystem.setMessageContent(description); // 信息内容 + messageSystem.setMessageStatus("0"); // 默认为未读 0未读 1 已读 + messageSystem.setMessageType(messageType.getCode()); + messageSystem.setUpdateBy(SecurityUtils.getUsername()); // 修改人 + messageSystem.setUpdateTime(DateUtils.getNowDate()); // 修改时间 + messageSystem.setSendMode("0"); // 默认发送方式为平台 + messageSystemService.insertMessageSystem(messageSystem); + logger.info("消息记录成功,标题: {}, 描述: {}, 类型: {}", title, description, messageType); + } +} diff --git a/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/utils/MessageSystemUtils.java b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/utils/MessageSystemUtils.java new file mode 100644 index 0000000..8993261 --- /dev/null +++ b/boyue-models/boyue-message/src/main/java/com/boyue/modelMessage/utils/MessageSystemUtils.java @@ -0,0 +1,224 @@ +package com.boyue.modelMessage.utils; + +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.boyue.common.exception.ServiceException; +import com.boyue.common.utils.DateUtils; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.modelMessage.domain.MessageSystem; +import com.boyue.modelMessage.domain.MessageTemplate; +import com.boyue.modelMessage.domain.MessageVariable; +import com.boyue.modelMessage.mapper.MessageTemplateMapper; +import com.boyue.modelMessage.mapper.MessageVariableMapper; + +@Component +public class MessageSystemUtils { + + private static final String NUMERIC_CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + private static final int CODE_LENGTH = 6; + + @Autowired + private MessageTemplateMapper messageTemplateMapper; + + @Autowired + private MessageVariableMapper messageVariableMapper; + + /** + * 解析输入字符串,提取模板代码和参数 + * + * @param input 输入字符串 + * @return 解析结果 + */ + public Map parseInput(String input) { + if (input == null) { + throw new ServiceException("输入内容不能为空!"); + } + String templateCode = null; + String queryParams = ""; + if (input.startsWith("SMS_")) { + int templateCodeEndIndex = input.indexOf('?'); + if (templateCodeEndIndex != -1) { + templateCode = input.substring(0, templateCodeEndIndex); + queryParams = input.substring(templateCodeEndIndex + 1); + } else { + templateCode = input; + } + } + MessageTemplate templateContent = null; + List variableNames = new ArrayList<>(); + if (templateCode != null) { + templateContent = messageTemplateMapper.selectMessageTemplateByTemplateCode(templateCode); + if (templateContent == null) { + throw new ServiceException("未找到该模版签名! " + templateCode); + } + variableNames = extractVariableNamesFromTemplate(templateContent); + } + + Map params = new HashMap<>(); + if (!queryParams.isEmpty()) { + for (String param : queryParams.split("&")) { + String[] keyValue = param.split("=", 2); + if (keyValue.length != 2) { + throw new ServiceException("无效的参数格式:" + param); + } + params.put(keyValue[0], URLDecoder.decode(keyValue[1], StandardCharsets.UTF_8)); + } + } + if (templateContent != null) { + for (String varName : variableNames) { + if (!params.containsKey(varName)) { + params.put(varName, null); //默认先不传递 等自动填充值 + } + } + } + return Map.of("templateCode", templateCode, "params", params, + "templateContent", templateContent != null ? templateContent.getTemplateContent() : input, + "variableNames", variableNames); + } + + /** + * 提取模板中的变量名 + * + * @param templateContent 模板内容 + * @return 变量名列表 + */ + public List extractVariableNamesFromTemplate(MessageTemplate templateContent) { + List variableNames = new ArrayList<>(); + Pattern pattern = Pattern.compile("\\$\\{(.*?)\\}"); + Matcher matcher = pattern.matcher(templateContent.getTemplateContent()); + while (matcher.find()) { + variableNames.add(matcher.group(1)); + } + return variableNames; + } + + /** + * 填充模板 + * + * @param template 模板 + * @param params 参数 + * @return 填充后的模板 + */ + public String fillTemplate(String template, Map params) { + String filledTemplate = template; + for (Map.Entry entry : params.entrySet()) { + filledTemplate = filledTemplate.replace("${" + entry.getKey() + "}", entry.getValue()); + } + return filledTemplate; + } + + /** + * 清除内置变量的随机数字 + * + * @param params 参数 + */ + public void clearBuiltInVariables(Map params) { + List builtInVariables = messageVariableMapper.selectMessageVariable(); + for (MessageVariable variable : builtInVariables) { + String variableContent = variable.getVariableContent(); + params.remove(variableContent); + } + } + + /** + * 处理模板消息并填充内置变量 + */ + public String processTemplateMessage(MessageSystem messageSystem, String notificationContent) throws Exception { + Map parsedParams = parseInput(notificationContent); + String templateCode = (String) parsedParams.get("templateCode"); + MessageTemplate templateContent = null; + + if (templateCode != null) { + templateContent = messageTemplateMapper.selectMessageTemplateByTemplateCode(templateCode); //查询获取模版内容 + } + + @SuppressWarnings("unchecked") + Map params = (Map) parsedParams.get("params"); + + // 检查参数 + List variableNames = new ArrayList<>(); + if (templateContent != null) { + variableNames = extractVariableNamesFromTemplate(templateContent); + for (String varName : variableNames) { + if (!params.containsKey(varName)) { + throw new ServiceException("缺少参数: " + varName); + } + } + } + clearBuiltInVariables(params); // 清除内置变量 + fillBuiltInVariables(params, messageSystem, variableNames); + // 如果未找到模板,使用原始内容作为模板 + String templateContentStr = templateContent != null ? templateContent.getTemplateContent() : notificationContent; + return fillTemplate(templateContentStr, params); + } + + /** + * 填充内置变量 + * + * @param params 参数 + * @param message 消息 + * @param variableNames 变量名列表 + */ + public void fillBuiltInVariables(Map params, MessageSystem message, List variableNames) { + List builtInVariables = messageVariableMapper.selectMessageVariables(variableNames); + for (MessageVariable variable : builtInVariables) { + String variableContent = variable.getVariableContent(); + String variableValue = "recipients".equals(variableContent) ? message.getMessageRecipient() : generateVariableContent(variableContent); + params.putIfAbsent(variableContent, variableValue); + } + } + + /** + * 生成变量内容 + * + * @param variableContent 变量内容 + * @return 生成的变量内容 + */ + public String generateVariableContent(String variableContent) { + switch (variableContent) { + case "time": + return DateUtils.dateTimeNow("HH:mm:ss"); //发送时间 + case "date": + return DateUtils.dateTimeNow("yyyy-MM-dd"); //发送日期 + case "datetime": + return DateUtils.dateTimeNow("yyyy-MM-dd HH:mm:ss"); //发送日期+时间 + case "addresser": + return SecurityUtils.getUsername(); //发件人 + case "code": + case "RandomnDigits": + case "RandomnCharacters": + case "RandomN-digitLetters": + return generateRandomCode(CODE_LENGTH); //随机数字+英文 + default: + throw new ServiceException("不明确的类型 " + variableContent); + } + } + + /** + * 生成指定长度的随机数字字符串 数字+英文 + * + * @param length 长度 + * @return 随机数字字符串 + */ + public String generateRandomCode(int length) { + Random random = new Random(); + StringBuilder codeBuilder = new StringBuilder(length); + for (int i = 0; i < length; i++) { + int index = random.nextInt(NUMERIC_CHARACTERS.length()); + char randomChar = NUMERIC_CHARACTERS.charAt(index); + codeBuilder.append(randomChar); + } + return codeBuilder.toString(); + } +} diff --git a/boyue-models/boyue-message/src/main/resources/mapper/modelMessage/MessageSystemMapper.xml b/boyue-models/boyue-message/src/main/resources/mapper/modelMessage/MessageSystemMapper.xml new file mode 100644 index 0000000..9b789ad --- /dev/null +++ b/boyue-models/boyue-message/src/main/resources/mapper/modelMessage/MessageSystemMapper.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + select message_id, message_title, message_content, message_status, message_type, create_by, create_time, update_by, update_time, remark, message_recipient, send_mode, `code` from message_system + + + + + + + + insert into message_system + + message_title, + message_content, + message_status, + message_type, + create_by, + create_time, + update_by, + update_time, + remark, + message_recipient, + send_mode, + code, + + + #{messageTitle}, + #{messageContent}, + #{messageStatus}, + #{messageType}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + #{messageRecipient}, + #{sendMode}, + #{code}, + + + + + update message_system + + message_title = #{messageTitle}, + message_content = #{messageContent}, + message_status = #{messageStatus}, + message_type = #{messageType}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + message_recipient = #{messageRecipient}, + send_mode = #{sendMode}, + code = #{code}, + + where message_system.message_id = #{messageId} + + + + delete from message_system where message_id = #{messageId} + + + + delete from message_system where message_id in + + #{messageId} + + + + + + INSERT INTO message_system (message_title, message_content, message_status, message_type, message_recipient, + send_mode, `code`, create_by, create_time, update_by, update_time, remark) VALUES + + ( #{item.messageTitle}, #{item.messageContent}, #{item.messageStatus}, #{item.messageType}, + #{item.messageRecipient}, #{item.sendMode}, #{item.code}, #{item.createBy}, NOW(), #{item.updateBy}, + NOW(), #{item.remark} ) + + + + \ No newline at end of file diff --git a/boyue-models/boyue-message/src/main/resources/mapper/modelMessage/MessageTemplateMapper.xml b/boyue-models/boyue-message/src/main/resources/mapper/modelMessage/MessageTemplateMapper.xml new file mode 100644 index 0000000..78553e5 --- /dev/null +++ b/boyue-models/boyue-message/src/main/resources/mapper/modelMessage/MessageTemplateMapper.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + select template_id, template_name, template_code, template_type, template_content, template_variable, create_by, create_time, update_by, update_time, remark from message_template + + + + + + + + insert into message_template + + template_name, + template_code, + template_type, + template_content, + template_variable, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{templateName}, + #{templateCode}, + #{templateType}, + #{templateContent}, + #{templateVariable}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update message_template + + template_name = #{templateName}, + template_code = #{templateCode}, + template_type = #{templateType}, + template_content = #{templateContent}, + template_variable = #{templateVariable}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where message_template.template_id = #{templateId} + + + + delete from message_template where template_id = #{templateId} + + + + delete from message_template where template_id in + + #{templateId} + + + \ No newline at end of file diff --git a/boyue-models/boyue-message/src/main/resources/mapper/modelMessage/MessageVariableMapper.xml b/boyue-models/boyue-message/src/main/resources/mapper/modelMessage/MessageVariableMapper.xml new file mode 100644 index 0000000..76c56a2 --- /dev/null +++ b/boyue-models/boyue-message/src/main/resources/mapper/modelMessage/MessageVariableMapper.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + select variable_id, variable_name, variable_type, variable_content, create_by, create_time, update_by, update_time, remark from message_variable + + + + + + + + insert into message_variable + + variable_name, + variable_type, + variable_content, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{variableName}, + #{variableType}, + #{variableContent}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update message_variable + + variable_name = #{variableName}, + variable_type = #{variableType}, + variable_content = #{variableContent}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where message_variable.variable_id = #{variableId} + + + + delete from message_variable where variable_id = #{variableId} + + + + delete from message_variable where variable_id in + + #{variableId} + + + + + + \ No newline at end of file diff --git a/boyue-models/boyue-models-starter/pom.xml b/boyue-models/boyue-models-starter/pom.xml new file mode 100644 index 0000000..ed6a2f2 --- /dev/null +++ b/boyue-models/boyue-models-starter/pom.xml @@ -0,0 +1,57 @@ + + + + boyue-models + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-models-starter + + + 中间件 + + + + + + + com.boyue + boyue-quartz + + + + + com.boyue + boyue-generator + + + + + com.boyue + boyue-online + + + + + com.boyue + boyue-message + + + + + com.boyue + boyue-form + + + + + com.boyue + boyue-hasfj + + + + + diff --git a/boyue-models/boyue-online/pom.xml b/boyue-models/boyue-online/pom.xml new file mode 100644 index 0000000..e169097 --- /dev/null +++ b/boyue-models/boyue-online/pom.xml @@ -0,0 +1,26 @@ + + + + boyue-models + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-online + + + online系统模块 + + + + + + com.boyue + boyue-common + + + + \ No newline at end of file diff --git a/boyue-models/boyue-online/src/main/java/com/boyue/online/controller/OnLineController.java b/boyue-models/boyue-online/src/main/java/com/boyue/online/controller/OnLineController.java new file mode 100644 index 0000000..969d807 --- /dev/null +++ b/boyue-models/boyue-online/src/main/java/com/boyue/online/controller/OnLineController.java @@ -0,0 +1,141 @@ +package com.boyue.online.controller; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.annotation.Anonymous; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.security.service.IPermissionService; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.online.domain.OnlineMb; +import com.boyue.online.service.IOnlineMbService; +import com.boyue.online.utils.SqlMapper; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 在线接口 + * + * @author Dftre + * @date 2024-01-26 + */ +@RestController +@Anonymous +@RequestMapping("/online") +public class OnLineController extends BaseController { + @Autowired + private IOnlineMbService onlineMbService; + + @Autowired + private IPermissionService permissionService; + + @Autowired + private SqlSessionFactory sqlSessionFactory; + + @SuppressWarnings("unchecked") + public Map getParams(HashMap params, HashMap data) { + Map object = new HashMap<>(); + HashMap object_params = new HashMap(); + String keyregex = "params\\[(.*?)\\]"; + Pattern pattern = Pattern.compile(keyregex); + if (params != null) { + params.keySet().forEach(key -> { + Matcher matcher = pattern.matcher(key); + if (matcher.find()) { + object_params.put(matcher.group(1), params.get(key)); + } else { + object.put(key, params.get(key)); + } + }); + } + if (data != null) { + if (data.containsKey("params")) { + object_params.putAll((HashMap) data.get("params")); + data.remove("params"); + } + object.putAll(data); + } + object.put("params", object_params); + return object; + } + + public Boolean checkPermission(String permissionType, String permissionValue) { + if (permissionType == null) { + return true; + } + return switch (permissionType) { + case "hasPermi" -> permissionService.hasPermi(permissionValue); + case "lacksPermi" -> permissionService.lacksPermi(permissionValue); + case "hasAnyPermi" -> permissionService.hasAnyPermi(permissionValue); + case "hasRole" -> permissionService.hasRole(permissionValue); + case "lacksRole" -> permissionService.lacksRole(permissionValue); + case "hasAnyRoles" -> permissionService.hasAnyRoles(permissionValue); + default -> true; + }; + } + + public Object processingMapper(String sqlContext, String actuatot, Map params) { + String sql = ""; + SqlSession sqlSession = sqlSessionFactory.openSession(); + try { + SqlMapper sqlMapper = new SqlMapper(sqlSession); + Object res = null; + res = switch (actuatot) { + case "selectList" -> getDataTable(sqlMapper.selectList(sql, params)); + case "insert" -> toAjax(sqlMapper.insert(sql, params)); + case "selectOne" -> success(sqlMapper.selectOne(sql, params)); + case "update" -> toAjax(sqlMapper.update(sql, params)); + case "delete" -> toAjax(sqlMapper.delete(sql, params)); + default -> AjaxResult.error(500, "系统错误,执行器错误"); + }; + return res; + } finally { + sqlSession.close(); + } + + } + + @RequestMapping("/api/**") + public Object api(@RequestParam(required = false) HashMap params, + @RequestBody(required = false) HashMap data, HttpServletRequest request, + HttpServletResponse response) { + OnlineMb selectOnlineMb = new OnlineMb(); + selectOnlineMb.setPath(request.getRequestURI().replace("/online/api", "")); + selectOnlineMb.setMethod(request.getMethod()); + + Map object = getParams(params, data); + + List selectOnlineMbList = onlineMbService.selectOnlineMbList(selectOnlineMb); + if (selectOnlineMbList.size() == 0) { + return AjaxResult.error("没有资源" + selectOnlineMb.getPath()); + } else if (selectOnlineMbList.size() > 1) { + return AjaxResult.error(500, "系统错误,在线接口重复"); + } else { + OnlineMb onlineMb = selectOnlineMbList.get(0); + if (!checkPermission(onlineMb.getPermissionType(), onlineMb.getPermissionValue())) { + return AjaxResult.error(403, "没有权限,请联系管理员授权"); + } + if (onlineMb.getDeptId() != null && onlineMb.getDeptId().equals("1")) { + object.put("deptId", SecurityUtils.getDeptId()); + } + if (onlineMb.getUserId() != null && onlineMb.getUserId().equals("1")) { + object.put("userId", SecurityUtils.getUserId()); + } + return processingMapper(onlineMb.getSqlText(), onlineMb.getActuator(), object); + } + } + +} diff --git a/boyue-models/boyue-online/src/main/java/com/boyue/online/controller/OnlineDbController.java b/boyue-models/boyue-online/src/main/java/com/boyue/online/controller/OnlineDbController.java new file mode 100644 index 0000000..49dc22e --- /dev/null +++ b/boyue-models/boyue-online/src/main/java/com/boyue/online/controller/OnlineDbController.java @@ -0,0 +1,39 @@ +package com.boyue.online.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.annotation.Anonymous; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.BaseEntity; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.online.mapper.OnlineDbMapper; + + +/** + * mysql数据库Controller接口 + * + * @author Dftre + * @date 2024-01-26 + */ +@RestController +@RequestMapping("/online/db") +@Anonymous +public class OnlineDbController extends BaseController { + + @Autowired + private OnlineDbMapper onlineDbMapper; + + @GetMapping("/table/list") + public TableDataInfo selectDbTableList(BaseEntity baseEntity){ + startPage(); + return getDataTable(onlineDbMapper.selectDbTableList(baseEntity)); + } + + @GetMapping("/column/list") + public TableDataInfo selectDbColumnsListByTableName(String tableName){ + return getDataTable(onlineDbMapper.selectDbColumnsListByTableName(tableName)); + } +} diff --git a/boyue-models/boyue-online/src/main/java/com/boyue/online/controller/OnlineMbController.java b/boyue-models/boyue-online/src/main/java/com/boyue/online/controller/OnlineMbController.java new file mode 100644 index 0000000..6e678b5 --- /dev/null +++ b/boyue-models/boyue-online/src/main/java/com/boyue/online/controller/OnlineMbController.java @@ -0,0 +1,116 @@ +package com.boyue.online.controller; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.enums.BusinessType; +import com.boyue.common.utils.poi.ExcelUtil; +import com.boyue.online.domain.OnlineMb; +import com.boyue.online.service.IOnlineMbService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * mybatis在线接口Controller + * + * @author Dftre + * @date 2024-01-26 + */ +@RestController +@RequestMapping("/online/mb") +@Tag(name = "【mybatis在线接口】管理") +public class OnlineMbController extends BaseController +{ + @Autowired + private IOnlineMbService onlineMbService; + + /** + * 查询mybatis在线接口列表 + */ + @Operation(summary = "查询mybatis在线接口列表") + @PreAuthorize("@ss.hasPermi('online:mb:list')") + @GetMapping("/list") + public TableDataInfo list(OnlineMb onlineMb) + { + startPage(); + List list = onlineMbService.selectOnlineMbList(onlineMb); + return getDataTable(list); + } + + /** + * 导出mybatis在线接口列表 + */ + @Operation(summary = "导出mybatis在线接口列表") + @PreAuthorize("@ss.hasPermi('online:mb:export')") + @Log(title = "mybatis在线接口", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, OnlineMb onlineMb) + { + List list = onlineMbService.selectOnlineMbList(onlineMb); + ExcelUtil util = new ExcelUtil(OnlineMb.class); + util.exportExcel(response, list, "mybatis在线接口数据"); + } + + /** + * 获取mybatis在线接口详细信息 + */ + @Operation(summary = "获取mybatis在线接口详细信息") + @PreAuthorize("@ss.hasPermi('online:mb:query')") + @GetMapping(value = "/{mbId}") + public AjaxResult getInfo(@PathVariable("mbId") Long mbId) + { + return success(onlineMbService.selectOnlineMbByMbId(mbId)); + } + + /** + * 新增mybatis在线接口 + */ + @Operation(summary = "新增mybatis在线接口") + @PreAuthorize("@ss.hasPermi('online:mb:add')") + @Log(title = "mybatis在线接口", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody OnlineMb onlineMb) + { + return toAjax(onlineMbService.insertOnlineMb(onlineMb)); + } + + /** + * 修改mybatis在线接口 + */ + @Operation(summary = "修改mybatis在线接口") + @PreAuthorize("@ss.hasPermi('online:mb:edit')") + @Log(title = "mybatis在线接口", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody OnlineMb onlineMb) + { + return toAjax(onlineMbService.updateOnlineMb(onlineMb)); + } + + /** + * 删除mybatis在线接口 + */ + @Operation(summary = "删除mybatis在线接口") + @PreAuthorize("@ss.hasPermi('online:mb:remove')") + @Log(title = "mybatis在线接口", businessType = BusinessType.DELETE) + @DeleteMapping("/{mbIds}") + public AjaxResult remove(@PathVariable( name = "mbIds" ) Long[] mbIds) + { + return toAjax(onlineMbService.deleteOnlineMbByMbIds(mbIds)); + } +} diff --git a/boyue-models/boyue-online/src/main/java/com/boyue/online/domain/OnlineMb.java b/boyue-models/boyue-online/src/main/java/com/boyue/online/domain/OnlineMb.java new file mode 100644 index 0000000..118c077 --- /dev/null +++ b/boyue-models/boyue-online/src/main/java/com/boyue/online/domain/OnlineMb.java @@ -0,0 +1,221 @@ +package com.boyue.online.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.boyue.common.annotation.Excel; +import com.boyue.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * mybatis在线接口对象 online_mb + * + * @author Dftre + * @date 2024-01-26 + */ +@Schema(description = "mybatis在线接口对象") +public class OnlineMb extends BaseEntity { + private static final long serialVersionUID = 1L; + + /** 主键 */ + @Schema(title = "主键") + private Long mbId; + + /** 标签名 */ + @Schema(title = "标签名") + @Excel(name = "标签名") + private String tag; + + /** 标签id */ + @Schema(title = "标签id") + @Excel(name = "标签id") + private String tagId; + + /** 参数类型 */ + @Schema(title = "参数类型") + @Excel(name = "参数类型") + private String parameterType; + + /** 结果类型 */ + @Schema(title = "结果类型") + @Excel(name = "结果类型") + private String resultMap; + + /** sql语句 */ + @Schema(title = "sql语句") + @Excel(name = "sql语句") + private String sqlText; + + /** 请求路径 */ + @Schema(title = "请求路径") + @Excel(name = "请求路径") + private String path; + + /** 请求方式 */ + @Schema(title = "请求方式") + @Excel(name = "请求方式") + private String method; + + /** 响应类型 */ + @Schema(title = "响应类型") + @Excel(name = "响应类型") + private String resultType; + + /** 执行器 */ + @Schema(title = "执行器") + @Excel(name = "执行器") + private String actuator; + + /** 是否需要userId */ + @Schema(title = "是否需要userId") + @Excel(name = "是否需要userId") + private String userId; + + /** 是否需要deptId */ + @Schema(title = "是否需要deptId") + @Excel(name = "是否需要deptId") + private String deptId; + + /** 许可类型 */ + @Schema(title = "许可类型") + @Excel(name = "许可类型") + private String permissionType; + + /** 许可值 */ + @Schema(title = "许可值") + @Excel(name = "许可值") + private String permissionValue; + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getPermissionType() { + return permissionType; + } + + public void setPermissionType(String permissionType) { + this.permissionType = permissionType; + } + + public String getPermissionValue() { + return permissionValue; + } + + public void setPermissionValue(String permissionValue) { + this.permissionValue = permissionValue; + } + + public void setMbId(Long mbId) { + this.mbId = mbId; + } + + public Long getMbId() { + return mbId; + } + + public void setTag(String tag) { + this.tag = tag; + } + + public String getTag() { + return tag; + } + + public void setTagId(String tagId) { + this.tagId = tagId; + } + + public String getTagId() { + return tagId; + } + + public void setParameterType(String parameterType) { + this.parameterType = parameterType; + } + + public String getParameterType() { + return parameterType; + } + + public void setResultMap(String resultMap) { + this.resultMap = resultMap; + } + + public String getResultMap() { + return resultMap; + } + + public void setSqlText(String sqlText) { + this.sqlText = sqlText; + } + + public String getSqlText() { + return sqlText; + } + + public void setPath(String path) { + this.path = path; + } + + public String getPath() { + return path; + } + + public void setMethod(String method) { + this.method = method; + } + + public String getMethod() { + return method; + } + + public void setResultType(String resultType) { + this.resultType = resultType; + } + + public String getResultType() { + return resultType; + } + + public void setActuator(String actuator) { + this.actuator = actuator; + } + + public String getActuator() { + return actuator; + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("mbId", getMbId()) + .append("tag", getTag()) + .append("tagId", getTagId()) + .append("parameterType", getParameterType()) + .append("resultMap", getResultMap()) + .append("sqlText", getSqlText()) + .append("path", getPath()) + .append("method", getMethod()) + .append("resultType", getResultType()) + .append("actuator", getActuator()) + .append("userId",getUserId()) + .append("deptId",getDeptId()) + .append("permissionType",getPermissionType()) + .append("permissionValue",getPermissionValue()) + .toString(); + } +} diff --git a/boyue-models/boyue-online/src/main/java/com/boyue/online/mapper/OnlineDbMapper.java b/boyue-models/boyue-online/src/main/java/com/boyue/online/mapper/OnlineDbMapper.java new file mode 100644 index 0000000..83cd2f6 --- /dev/null +++ b/boyue-models/boyue-online/src/main/java/com/boyue/online/mapper/OnlineDbMapper.java @@ -0,0 +1,17 @@ +package com.boyue.online.mapper; + +import java.util.List; +import java.util.Map; + +import com.boyue.common.core.domain.BaseEntity; + +/** + * mysql数据库Mapper接口 + * + * @author Dftre + * @date 2024-01-26 + */ +public interface OnlineDbMapper { + public List> selectDbTableList(BaseEntity baseEntity); + public List> selectDbColumnsListByTableName(String tableName); +} diff --git a/boyue-models/boyue-online/src/main/java/com/boyue/online/mapper/OnlineMbMapper.java b/boyue-models/boyue-online/src/main/java/com/boyue/online/mapper/OnlineMbMapper.java new file mode 100644 index 0000000..74d7136 --- /dev/null +++ b/boyue-models/boyue-online/src/main/java/com/boyue/online/mapper/OnlineMbMapper.java @@ -0,0 +1,62 @@ +package com.boyue.online.mapper; + +import java.util.List; + +import com.boyue.online.domain.OnlineMb; + +/** + * mybatis在线接口Mapper接口 + * + * @author Dftre + * @date 2024-01-26 + */ +public interface OnlineMbMapper +{ + /** + * 查询mybatis在线接口 + * + * @param mbId mybatis在线接口主键 + * @return mybatis在线接口 + */ + public OnlineMb selectOnlineMbByMbId(Long mbId); + + /** + * 查询mybatis在线接口列表 + * + * @param onlineMb mybatis在线接口 + * @return mybatis在线接口集合 + */ + public List selectOnlineMbList(OnlineMb onlineMb); + + /** + * 新增mybatis在线接口 + * + * @param onlineMb mybatis在线接口 + * @return 结果 + */ + public int insertOnlineMb(OnlineMb onlineMb); + + /** + * 修改mybatis在线接口 + * + * @param onlineMb mybatis在线接口 + * @return 结果 + */ + public int updateOnlineMb(OnlineMb onlineMb); + + /** + * 删除mybatis在线接口 + * + * @param mbId mybatis在线接口主键 + * @return 结果 + */ + public int deleteOnlineMbByMbId(Long mbId); + + /** + * 批量删除mybatis在线接口 + * + * @param mbIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteOnlineMbByMbIds(Long[] mbIds); +} diff --git a/boyue-models/boyue-online/src/main/java/com/boyue/online/service/IOnlineMbService.java b/boyue-models/boyue-online/src/main/java/com/boyue/online/service/IOnlineMbService.java new file mode 100644 index 0000000..2735dbb --- /dev/null +++ b/boyue-models/boyue-online/src/main/java/com/boyue/online/service/IOnlineMbService.java @@ -0,0 +1,62 @@ +package com.boyue.online.service; + +import java.util.List; + +import com.boyue.online.domain.OnlineMb; + +/** + * mybatis在线接口Service接口 + * + * @author Dftre + * @date 2024-01-26 + */ +public interface IOnlineMbService +{ + /** + * 查询mybatis在线接口 + * + * @param mbId mybatis在线接口主键 + * @return mybatis在线接口 + */ + public OnlineMb selectOnlineMbByMbId(Long mbId); + + /** + * 查询mybatis在线接口列表 + * + * @param onlineMb mybatis在线接口 + * @return mybatis在线接口集合 + */ + public List selectOnlineMbList(OnlineMb onlineMb); + + /** + * 新增mybatis在线接口 + * + * @param onlineMb mybatis在线接口 + * @return 结果 + */ + public int insertOnlineMb(OnlineMb onlineMb); + + /** + * 修改mybatis在线接口 + * + * @param onlineMb mybatis在线接口 + * @return 结果 + */ + public int updateOnlineMb(OnlineMb onlineMb); + + /** + * 批量删除mybatis在线接口 + * + * @param mbIds 需要删除的mybatis在线接口主键集合 + * @return 结果 + */ + public int deleteOnlineMbByMbIds(Long[] mbIds); + + /** + * 删除mybatis在线接口信息 + * + * @param mbId mybatis在线接口主键 + * @return 结果 + */ + public int deleteOnlineMbByMbId(Long mbId); +} diff --git a/boyue-models/boyue-online/src/main/java/com/boyue/online/service/impl/OnlineMbServiceImpl.java b/boyue-models/boyue-online/src/main/java/com/boyue/online/service/impl/OnlineMbServiceImpl.java new file mode 100644 index 0000000..be1850b --- /dev/null +++ b/boyue-models/boyue-online/src/main/java/com/boyue/online/service/impl/OnlineMbServiceImpl.java @@ -0,0 +1,95 @@ +package com.boyue.online.service.impl; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.boyue.online.domain.OnlineMb; +import com.boyue.online.mapper.OnlineMbMapper; +import com.boyue.online.service.IOnlineMbService; + +/** + * mybatis在线接口Service业务层处理 + * + * @author Dftre + * @date 2024-01-26 + */ +@Service +public class OnlineMbServiceImpl implements IOnlineMbService +{ + @Autowired + private OnlineMbMapper onlineMbMapper; + + /** + * 查询mybatis在线接口 + * + * @param mbId mybatis在线接口主键 + * @return mybatis在线接口 + */ + @Override + public OnlineMb selectOnlineMbByMbId(Long mbId) + { + return onlineMbMapper.selectOnlineMbByMbId(mbId); + } + + /** + * 查询mybatis在线接口列表 + * + * @param onlineMb mybatis在线接口 + * @return mybatis在线接口 + */ + @Override + public List selectOnlineMbList(OnlineMb onlineMb) + { + return onlineMbMapper.selectOnlineMbList(onlineMb); + } + + /** + * 新增mybatis在线接口 + * + * @param onlineMb mybatis在线接口 + * @return 结果 + */ + @Override + public int insertOnlineMb(OnlineMb onlineMb) + { + return onlineMbMapper.insertOnlineMb(onlineMb); + } + + /** + * 修改mybatis在线接口 + * + * @param onlineMb mybatis在线接口 + * @return 结果 + */ + @Override + public int updateOnlineMb(OnlineMb onlineMb) + { + return onlineMbMapper.updateOnlineMb(onlineMb); + } + + /** + * 批量删除mybatis在线接口 + * + * @param mbIds 需要删除的mybatis在线接口主键 + * @return 结果 + */ + @Override + public int deleteOnlineMbByMbIds(Long[] mbIds) + { + return onlineMbMapper.deleteOnlineMbByMbIds(mbIds); + } + + /** + * 删除mybatis在线接口信息 + * + * @param mbId mybatis在线接口主键 + * @return 结果 + */ + @Override + public int deleteOnlineMbByMbId(Long mbId) + { + return onlineMbMapper.deleteOnlineMbByMbId(mbId); + } +} diff --git a/boyue-models/boyue-online/src/main/java/com/boyue/online/utils/SqlMapper.java b/boyue-models/boyue-online/src/main/java/com/boyue/online/utils/SqlMapper.java new file mode 100644 index 0000000..d5df049 --- /dev/null +++ b/boyue-models/boyue-online/src/main/java/com/boyue/online/utils/SqlMapper.java @@ -0,0 +1,411 @@ +package com.boyue.online.utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.ibatis.builder.StaticSqlSource; +import org.apache.ibatis.exceptions.TooManyResultsException; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.ResultMap; +import org.apache.ibatis.mapping.ResultMapping; +import org.apache.ibatis.mapping.SqlCommandType; +import org.apache.ibatis.mapping.SqlSource; +import org.apache.ibatis.scripting.LanguageDriver; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.SqlSession; + +/** + * MyBatis执行sql工具,在写SQL的时候建议使用参数形式的可以是${}或#{} + * + * 不建议将参数直接拼到字符串中,当大量这么使用的时候由于缓存MappedStatement而占用更多的内存 + * + * @author liuzh + * @since 2015-03-10 + */ +public class SqlMapper { + private final MSUtils msUtils; + private final SqlSession sqlSession; + + /** + * 构造方法,默认缓存MappedStatement + * + * @param sqlSession + */ + public SqlMapper(SqlSession sqlSession) { + this.sqlSession = sqlSession; + this.msUtils = new MSUtils(sqlSession.getConfiguration()); + } + + /** + * 获取List中最多只有一个的数据 + * + * @param list List结果 + * @param 泛型类型 + * @return + */ + private T getOne(List list) { + if (list.size() == 1) { + return list.get(0); + } else if (list.size() > 1) { + throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); + } else { + return null; + } + } + + /** + * 查询返回一个结果,多个结果时抛出异常 + * + * @param sql 执行的sql + * @return + */ + public Map selectOne(String sql) { + List> list = selectList(sql); + return getOne(list); + } + + /** + * 查询返回一个结果,多个结果时抛出异常 + * + * @param sql 执行的sql + * @param value 参数 + * @return + */ + public Map selectOne(String sql, Object value) { + List> list = selectList(sql, value); + return getOne(list); + } + + /** + * 查询返回一个结果,多个结果时抛出异常 + * + * @param sql 执行的sql + * @param resultType 返回的结果类型 + * @param 泛型类型 + * @return + */ + public T selectOne(String sql, Class resultType) { + List list = selectList(sql, resultType); + return getOne(list); + } + + /** + * 查询返回一个结果,多个结果时抛出异常 + * + * @param sql 执行的sql + * @param value 参数 + * @param resultType 返回的结果类型 + * @param 泛型类型 + * @return + */ + public T selectOne(String sql, Object value, Class resultType) { + List list = selectList(sql, value, resultType); + return getOne(list); + } + + /** + * 查询返回List> + * + * @param sql 执行的sql + * @return + */ + public List> selectList(String sql) { + String msId = msUtils.select(sql); + return sqlSession.selectList(msId); + } + + /** + * 查询返回List> + * + * @param sql 执行的sql + * @param value 参数 + * @return + */ + public List> selectList(String sql, Object value) { + Class parameterType = value != null ? value.getClass() : null; + String msId = msUtils.selectDynamic(sql, parameterType); + return sqlSession.selectList(msId, value); + } + + /** + * 查询返回指定的结果类型 + * + * @param sql 执行的sql + * @param resultType 返回的结果类型 + * @param 泛型类型 + * @return + */ + public List selectList(String sql, Class resultType) { + String msId; + if (resultType == null) { + msId = msUtils.select(sql); + } else { + msId = msUtils.select(sql, resultType); + } + return sqlSession.selectList(msId); + } + + /** + * 查询返回指定的结果类型 + * + * @param sql 执行的sql + * @param value 参数 + * @param resultType 返回的结果类型 + * @param 泛型类型 + * @return + */ + public List selectList(String sql, Object value, Class resultType) { + String msId; + Class parameterType = value != null ? value.getClass() : null; + if (resultType == null) { + msId = msUtils.selectDynamic(sql, parameterType); + } else { + msId = msUtils.selectDynamic(sql, parameterType, resultType); + } + return sqlSession.selectList(msId, value); + } + + /** + * 插入数据 + * + * @param sql 执行的sql + * @return + */ + public int insert(String sql) { + String msId = msUtils.insert(sql); + return sqlSession.insert(msId); + } + + /** + * 插入数据 + * + * @param sql 执行的sql + * @param value 参数 + * @return + */ + public int insert(String sql, Object value) { + Class parameterType = value != null ? value.getClass() : null; + String msId = msUtils.insertDynamic(sql, parameterType); + return sqlSession.insert(msId, value); + } + + /** + * 更新数据 + * + * @param sql 执行的sql + * @return + */ + public int update(String sql) { + String msId = msUtils.update(sql); + return sqlSession.update(msId); + } + + /** + * 更新数据 + * + * @param sql 执行的sql + * @param value 参数 + * @return + */ + public int update(String sql, Object value) { + Class parameterType = value != null ? value.getClass() : null; + String msId = msUtils.updateDynamic(sql, parameterType); + return sqlSession.update(msId, value); + } + + /** + * 删除数据 + * + * @param sql 执行的sql + * @return + */ + public int delete(String sql) { + String msId = msUtils.delete(sql); + return sqlSession.delete(msId); + } + + /** + * 删除数据 + * + * @param sql 执行的sql + * @param value 参数 + * @return + */ + public int delete(String sql, Object value) { + Class parameterType = value != null ? value.getClass() : null; + String msId = msUtils.deleteDynamic(sql, parameterType); + return sqlSession.delete(msId, value); + } + + private class MSUtils { + private Configuration configuration; + private LanguageDriver languageDriver; + + private MSUtils(Configuration configuration) { + this.configuration = configuration; + languageDriver = configuration.getDefaultScriptingLanguageInstance(); + } + + /** + * 创建MSID + * + * @param sql 执行的sql + * @param sql 执行的sqlCommandType + * @return + */ + private String newMsId(String sql, SqlCommandType sqlCommandType) { + StringBuilder msIdBuilder = new StringBuilder(sqlCommandType.toString()); + msIdBuilder.append(".").append(sql.hashCode()); + return msIdBuilder.toString(); + } + + /** + * 是否已经存在该ID + * + * @param msId + * @return + */ + private boolean hasMappedStatement(String msId) { + return configuration.hasStatement(msId, false); + } + + /** + * 创建一个查询的MS + * + * @param msId + * @param sqlSource 执行的sqlSource + * @param resultType 返回的结果类型 + */ + private void newSelectMappedStatement(String msId, SqlSource sqlSource, final Class resultType) { + MappedStatement ms = new MappedStatement.Builder(configuration, msId, sqlSource, SqlCommandType.SELECT) + .resultMaps(new ArrayList() { + { + add(new ResultMap.Builder(configuration, "defaultResultMap", resultType, new ArrayList(0)).build()); + } + }) + .build(); + //缓存 + configuration.addMappedStatement(ms); + } + + /** + * 创建一个简单的MS + * + * @param msId + * @param sqlSource 执行的sqlSource + * @param sqlCommandType 执行的sqlCommandType + */ + private void newUpdateMappedStatement(String msId, SqlSource sqlSource, SqlCommandType sqlCommandType) { + MappedStatement ms = new MappedStatement.Builder(configuration, msId, sqlSource, sqlCommandType) + .resultMaps(new ArrayList() { + { + add(new ResultMap.Builder(configuration, "defaultResultMap", int.class, new ArrayList(0)).build()); + } + }) + .build(); + //缓存 + configuration.addMappedStatement(ms); + } + + private String select(String sql) { + String msId = newMsId(sql, SqlCommandType.SELECT); + if (hasMappedStatement(msId)) { + return msId; + } + StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql); + newSelectMappedStatement(msId, sqlSource, Map.class); + return msId; + } + + private String selectDynamic(String sql, Class parameterType) { + String msId = newMsId(sql + parameterType, SqlCommandType.SELECT); + if (hasMappedStatement(msId)) { + return msId; + } + SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType); + newSelectMappedStatement(msId, sqlSource, Map.class); + return msId; + } + + private String select(String sql, Class resultType) { + String msId = newMsId(resultType + sql, SqlCommandType.SELECT); + if (hasMappedStatement(msId)) { + return msId; + } + StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql); + newSelectMappedStatement(msId, sqlSource, resultType); + return msId; + } + + private String selectDynamic(String sql, Class parameterType, Class resultType) { + String msId = newMsId(resultType + sql + parameterType, SqlCommandType.SELECT); + if (hasMappedStatement(msId)) { + return msId; + } + SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType); + newSelectMappedStatement(msId, sqlSource, resultType); + return msId; + } + + private String insert(String sql) { + String msId = newMsId(sql, SqlCommandType.INSERT); + if (hasMappedStatement(msId)) { + return msId; + } + StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql); + newUpdateMappedStatement(msId, sqlSource, SqlCommandType.INSERT); + return msId; + } + + private String insertDynamic(String sql, Class parameterType) { + String msId = newMsId(sql + parameterType, SqlCommandType.INSERT); + if (hasMappedStatement(msId)) { + return msId; + } + SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType); + newUpdateMappedStatement(msId, sqlSource, SqlCommandType.INSERT); + return msId; + } + + private String update(String sql) { + String msId = newMsId(sql, SqlCommandType.UPDATE); + if (hasMappedStatement(msId)) { + return msId; + } + StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql); + newUpdateMappedStatement(msId, sqlSource, SqlCommandType.UPDATE); + return msId; + } + + private String updateDynamic(String sql, Class parameterType) { + String msId = newMsId(sql + parameterType, SqlCommandType.UPDATE); + if (hasMappedStatement(msId)) { + return msId; + } + SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType); + newUpdateMappedStatement(msId, sqlSource, SqlCommandType.UPDATE); + return msId; + } + + private String delete(String sql) { + String msId = newMsId(sql, SqlCommandType.DELETE); + if (hasMappedStatement(msId)) { + return msId; + } + StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql); + newUpdateMappedStatement(msId, sqlSource, SqlCommandType.DELETE); + return msId; + } + + private String deleteDynamic(String sql, Class parameterType) { + String msId = newMsId(sql + parameterType, SqlCommandType.DELETE); + if (hasMappedStatement(msId)) { + return msId; + } + SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType); + newUpdateMappedStatement(msId, sqlSource, SqlCommandType.DELETE); + return msId; + } + } +} \ No newline at end of file diff --git a/boyue-models/boyue-online/src/main/resources/mapper/online/OnlineDbMapper.xml b/boyue-models/boyue-online/src/main/resources/mapper/online/OnlineDbMapper.xml new file mode 100644 index 0000000..366d6a6 --- /dev/null +++ b/boyue-models/boyue-online/src/main/resources/mapper/online/OnlineDbMapper.xml @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/boyue-models/boyue-online/src/main/resources/mapper/online/OnlineMbMapper.xml b/boyue-models/boyue-online/src/main/resources/mapper/online/OnlineMbMapper.xml new file mode 100644 index 0000000..80c589f --- /dev/null +++ b/boyue-models/boyue-online/src/main/resources/mapper/online/OnlineMbMapper.xml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + select mb_id, tag, tag_id, parameter_type, result_map, sql_text, online_mb.path, method, result_type, actuator,user_id,dept_id,permission_type,permission_value from online_mb + + + + + + + + insert into online_mb + + mb_id, + tag, + tag_id, + parameter_type, + result_map, + sql_text, + path, + method, + result_type, + actuator, + user_id, + dept_id, + permission_type, + permission_value, + + + #{mbId}, + #{tag}, + #{tagId}, + #{parameterType}, + #{resultMap}, + #{sqlText}, + #{path}, + #{method}, + #{resultType}, + #{actuator}, + #{userId}, + #{deptId}, + #{permissionType}, + #{permissionValue}, + + + + + update online_mb + + tag = #{tag}, + tag_id = #{tagId}, + parameter_type = #{parameterType}, + result_map = #{result_map}, + sql_text = #{sqlText}, + path = #{path}, + method = #{method}, + result_type = #{resultType}, + actuator = #{actuator}, + user_id = #{userId}, + dept_id = #{deptId}, + permission_type = #{permissionType}, + permission_value = #{permissionValue}, + + where online_mb.mb_id = #{mbId} and online_mb.del_flag != '1' + + + + update online_mb set del_flag = '1' where mb_id = #{mbId} + + + + update online_mb set del_flag = '1' where mb_id in + + #{mbId} + + + \ No newline at end of file diff --git a/boyue-models/boyue-quartz/pom.xml b/boyue-models/boyue-quartz/pom.xml new file mode 100644 index 0000000..3cede9a --- /dev/null +++ b/boyue-models/boyue-quartz/pom.xml @@ -0,0 +1,39 @@ + + + + boyue-models + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-quartz + + + quartz定时任务 + + + + + + + org.quartz-scheduler + quartz + + + com.mchange + c3p0 + + + + + + + com.boyue + boyue-common + + + + + diff --git a/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/config/ScheduleConfig.java b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/config/ScheduleConfig.java new file mode 100644 index 0000000..00013dd --- /dev/null +++ b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/config/ScheduleConfig.java @@ -0,0 +1,57 @@ +//package com.boyue.quartz.config; +// +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.scheduling.quartz.SchedulerFactoryBean; +//import javax.sql.DataSource; +//import java.util.Properties; +// +///** +// * 定时任务配置(单机部署建议删除此类和qrtz数据库表,默认走内存会最高效) +// * +// * @author boyue +// */ +//@Configuration +//public class ScheduleConfig +//{ +// @Bean +// public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) +// { +// SchedulerFactoryBean factory = new SchedulerFactoryBean(); +// factory.setDataSource(dataSource); +// +// // quartz参数 +// Properties prop = new Properties(); +// prop.put("org.quartz.scheduler.instanceName", "boyueScheduler"); +// prop.put("org.quartz.scheduler.instanceId", "AUTO"); +// // 线程池配置 +// prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool"); +// prop.put("org.quartz.threadPool.threadCount", "20"); +// prop.put("org.quartz.threadPool.threadPriority", "5"); +// // JobStore配置 +// prop.put("org.quartz.jobStore.class", "org.springframework.scheduling.quartz.LocalDataSourceJobStore"); +// // 集群配置 +// prop.put("org.quartz.jobStore.isClustered", "true"); +// prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000"); +// prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1"); +// prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true"); +// +// // sqlserver 启用 +// // prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?"); +// prop.put("org.quartz.jobStore.misfireThreshold", "12000"); +// prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_"); +// factory.setQuartzProperties(prop); +// +// factory.setSchedulerName("boyueScheduler"); +// // 延时启动 +// factory.setStartupDelay(1); +// factory.setApplicationContextSchedulerContextKey("applicationContextKey"); +// // 可选,QuartzScheduler +// // 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了 +// factory.setOverwriteExistingJobs(true); +// // 设置自动启动,默认为true +// factory.setAutoStartup(true); +// +// return factory; +// } +//} diff --git a/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/controller/SysJobController.java b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/controller/SysJobController.java new file mode 100644 index 0000000..659f26d --- /dev/null +++ b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/controller/SysJobController.java @@ -0,0 +1,188 @@ +package com.boyue.quartz.controller; + +import java.util.List; + +import org.quartz.SchedulerException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.annotation.Log; +import com.boyue.common.constant.Constants; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.enums.BusinessType; +import com.boyue.common.exception.job.TaskException; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.poi.ExcelUtil; +import com.boyue.quartz.domain.SysJob; +import com.boyue.quartz.service.ISysJobService; +import com.boyue.quartz.util.CronUtils; +import com.boyue.quartz.util.ScheduleUtils; + +import jakarta.servlet.http.HttpServletResponse; + +/** + * 调度任务信息操作处理 + * + * @author boyue + */ +@RestController +@RequestMapping("/monitor/job") +public class SysJobController extends BaseController +{ + @Autowired + private ISysJobService jobService; + + /** + * 查询定时任务列表 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:list')") + @GetMapping("/list") + public TableDataInfo list(SysJob sysJob) + { + startPage(); + List list = jobService.selectJobList(sysJob); + return getDataTable(list); + } + + /** + * 导出定时任务列表 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:export')") + @Log(title = "定时任务", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, SysJob sysJob) + { + List list = jobService.selectJobList(sysJob); + ExcelUtil util = new ExcelUtil(SysJob.class); + util.exportExcel(response, list, "定时任务"); + } + + /** + * 获取定时任务详细信息 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:query')") + @GetMapping(value = "/{jobId}") + public AjaxResult getInfo(@PathVariable("jobId") Long jobId) + { + return success(jobService.selectJobById(jobId)); + } + + /** + * 新增定时任务 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:add')") + @Log(title = "定时任务", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody SysJob job) throws SchedulerException, TaskException + { + if (!CronUtils.isValid(job.getCronExpression())) + { + return error("新增任务'" + job.getJobName() + "'失败,Cron表达式不正确"); + } + else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI)) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS })) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS })) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR)) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串存在违规"); + } + else if (!ScheduleUtils.whiteList(job.getInvokeTarget())) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不在白名单内"); + } + job.setCreateBy(getUsername()); + return toAjax(jobService.insertJob(job)); + } + + /** + * 修改定时任务 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:edit')") + @Log(title = "定时任务", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody SysJob job) throws SchedulerException, TaskException + { + if (!CronUtils.isValid(job.getCronExpression())) + { + return error("修改任务'" + job.getJobName() + "'失败,Cron表达式不正确"); + } + else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI)) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS })) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS })) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR)) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串存在违规"); + } + else if (!ScheduleUtils.whiteList(job.getInvokeTarget())) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不在白名单内"); + } + job.setUpdateBy(getUsername()); + return toAjax(jobService.updateJob(job)); + } + + /** + * 定时任务状态修改 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:changeStatus')") + @Log(title = "定时任务", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public AjaxResult changeStatus(@RequestBody SysJob job) throws SchedulerException + { + SysJob newJob = jobService.selectJobById(job.getJobId()); + newJob.setStatus(job.getStatus()); + return toAjax(jobService.changeStatus(newJob)); + } + + /** + * 定时任务立即执行一次 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:changeStatus')") + @Log(title = "定时任务", businessType = BusinessType.UPDATE) + @PutMapping("/run") + public AjaxResult run(@RequestBody SysJob job) throws SchedulerException + { + boolean result = jobService.run(job); + return result ? success() : error("任务不存在或已过期!"); + } + + /** + * 删除定时任务 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:remove')") + @Log(title = "定时任务", businessType = BusinessType.DELETE) + @DeleteMapping("/{jobIds}") + public AjaxResult remove(@PathVariable( name = "jobIds" ) Long[] jobIds) throws SchedulerException, TaskException + { + jobService.deleteJobByIds(jobIds); + return success(); + } +} diff --git a/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/controller/SysJobLogController.java b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/controller/SysJobLogController.java new file mode 100644 index 0000000..d915e09 --- /dev/null +++ b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/controller/SysJobLogController.java @@ -0,0 +1,95 @@ +package com.boyue.quartz.controller; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.enums.BusinessType; +import com.boyue.common.utils.poi.ExcelUtil; +import com.boyue.quartz.domain.SysJobLog; +import com.boyue.quartz.service.ISysJobLogService; + +import jakarta.servlet.http.HttpServletResponse; + +/** + * 调度日志操作处理 + * + * @author boyue + */ +@RestController +@RequestMapping("/monitor/jobLog") +public class SysJobLogController extends BaseController +{ + @Autowired + private ISysJobLogService jobLogService; + + /** + * 查询定时任务调度日志列表 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:list')") + @GetMapping("/list") + public TableDataInfo list(SysJobLog sysJobLog) + { + startPage(); + List list = jobLogService.selectJobLogList(sysJobLog); + return getDataTable(list); + } + + /** + * 导出定时任务调度日志列表 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:export')") + @Log(title = "任务调度日志", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, SysJobLog sysJobLog) + { + List list = jobLogService.selectJobLogList(sysJobLog); + ExcelUtil util = new ExcelUtil(SysJobLog.class); + util.exportExcel(response, list, "调度日志"); + } + + /** + * 根据调度编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:query')") + @GetMapping(value = "/{jobLogId}") + public AjaxResult getInfo(@PathVariable( name = "jobLogId" ) Long jobLogId) + { + return success(jobLogService.selectJobLogById(jobLogId)); + } + + + /** + * 删除定时任务调度日志 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:remove')") + @Log(title = "定时任务调度日志", businessType = BusinessType.DELETE) + @DeleteMapping("/{jobLogIds}") + public AjaxResult remove(@PathVariable( name = "jobLogIds" ) Long[] jobLogIds) + { + return toAjax(jobLogService.deleteJobLogByIds(jobLogIds)); + } + + /** + * 清空定时任务调度日志 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:remove')") + @Log(title = "调度日志", businessType = BusinessType.CLEAN) + @DeleteMapping("/clean") + public AjaxResult clean() + { + jobLogService.cleanJobLog(); + return success(); + } +} diff --git a/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/domain/SysJob.java b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/domain/SysJob.java new file mode 100644 index 0000000..006fba9 --- /dev/null +++ b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/domain/SysJob.java @@ -0,0 +1,171 @@ +package com.boyue.quartz.domain; + +import java.util.Date; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.boyue.common.annotation.Excel; +import com.boyue.common.annotation.Excel.ColumnType; +import com.boyue.common.constant.ScheduleConstants; +import com.boyue.common.core.domain.BaseEntity; +import com.boyue.common.utils.StringUtils; +import com.boyue.quartz.util.CronUtils; + +/** + * 定时任务调度表 sys_job + * + * @author boyue + */ +public class SysJob extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 任务ID */ + @Excel(name = "任务序号", cellType = ColumnType.NUMERIC) + private Long jobId; + + /** 任务名称 */ + @Excel(name = "任务名称") + private String jobName; + + /** 任务组名 */ + @Excel(name = "任务组名") + private String jobGroup; + + /** 调用目标字符串 */ + @Excel(name = "调用目标字符串") + private String invokeTarget; + + /** cron执行表达式 */ + @Excel(name = "执行表达式 ") + private String cronExpression; + + /** cron计划策略 */ + @Excel(name = "计划策略 ", readConverterExp = "0=默认,1=立即触发执行,2=触发一次执行,3=不触发立即执行") + private String misfirePolicy = ScheduleConstants.MISFIRE_DEFAULT; + + /** 是否并发执行(0允许 1禁止) */ + @Excel(name = "并发执行", readConverterExp = "0=允许,1=禁止") + private String concurrent; + + /** 任务状态(0正常 1暂停) */ + @Excel(name = "任务状态", readConverterExp = "0=正常,1=暂停") + private String status; + + public Long getJobId() + { + return jobId; + } + + public void setJobId(Long jobId) + { + this.jobId = jobId; + } + + @NotBlank(message = "任务名称不能为空") + @Size(min = 0, max = 64, message = "任务名称不能超过64个字符") + public String getJobName() + { + return jobName; + } + + public void setJobName(String jobName) + { + this.jobName = jobName; + } + + public String getJobGroup() + { + return jobGroup; + } + + public void setJobGroup(String jobGroup) + { + this.jobGroup = jobGroup; + } + + @NotBlank(message = "调用目标字符串不能为空") + @Size(min = 0, max = 500, message = "调用目标字符串长度不能超过500个字符") + public String getInvokeTarget() + { + return invokeTarget; + } + + public void setInvokeTarget(String invokeTarget) + { + this.invokeTarget = invokeTarget; + } + + @NotBlank(message = "Cron执行表达式不能为空") + @Size(min = 0, max = 255, message = "Cron执行表达式不能超过255个字符") + public String getCronExpression() + { + return cronExpression; + } + + public void setCronExpression(String cronExpression) + { + this.cronExpression = cronExpression; + } + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + public Date getNextValidTime() + { + if (StringUtils.isNotEmpty(cronExpression)) + { + return CronUtils.getNextExecution(cronExpression); + } + return null; + } + + public String getMisfirePolicy() + { + return misfirePolicy; + } + + public void setMisfirePolicy(String misfirePolicy) + { + this.misfirePolicy = misfirePolicy; + } + + public String getConcurrent() + { + return concurrent; + } + + public void setConcurrent(String concurrent) + { + this.concurrent = concurrent; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("jobId", getJobId()) + .append("jobName", getJobName()) + .append("jobGroup", getJobGroup()) + .append("cronExpression", getCronExpression()) + .append("nextValidTime", getNextValidTime()) + .append("misfirePolicy", getMisfirePolicy()) + .append("concurrent", getConcurrent()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/domain/SysJobLog.java b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/domain/SysJobLog.java new file mode 100644 index 0000000..28ca728 --- /dev/null +++ b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/domain/SysJobLog.java @@ -0,0 +1,155 @@ +package com.boyue.quartz.domain; + +import java.util.Date; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.boyue.common.annotation.Excel; +import com.boyue.common.core.domain.BaseEntity; + +/** + * 定时任务调度日志表 sys_job_log + * + * @author boyue + */ +public class SysJobLog extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** ID */ + @Excel(name = "日志序号") + private Long jobLogId; + + /** 任务名称 */ + @Excel(name = "任务名称") + private String jobName; + + /** 任务组名 */ + @Excel(name = "任务组名") + private String jobGroup; + + /** 调用目标字符串 */ + @Excel(name = "调用目标字符串") + private String invokeTarget; + + /** 日志信息 */ + @Excel(name = "日志信息") + private String jobMessage; + + /** 执行状态(0正常 1失败) */ + @Excel(name = "执行状态", readConverterExp = "0=正常,1=失败") + private String status; + + /** 异常信息 */ + @Excel(name = "异常信息") + private String exceptionInfo; + + /** 开始时间 */ + private Date startTime; + + /** 停止时间 */ + private Date stopTime; + + public Long getJobLogId() + { + return jobLogId; + } + + public void setJobLogId(Long jobLogId) + { + this.jobLogId = jobLogId; + } + + public String getJobName() + { + return jobName; + } + + public void setJobName(String jobName) + { + this.jobName = jobName; + } + + public String getJobGroup() + { + return jobGroup; + } + + public void setJobGroup(String jobGroup) + { + this.jobGroup = jobGroup; + } + + public String getInvokeTarget() + { + return invokeTarget; + } + + public void setInvokeTarget(String invokeTarget) + { + this.invokeTarget = invokeTarget; + } + + public String getJobMessage() + { + return jobMessage; + } + + public void setJobMessage(String jobMessage) + { + this.jobMessage = jobMessage; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getExceptionInfo() + { + return exceptionInfo; + } + + public void setExceptionInfo(String exceptionInfo) + { + this.exceptionInfo = exceptionInfo; + } + + public Date getStartTime() + { + return startTime; + } + + public void setStartTime(Date startTime) + { + this.startTime = startTime; + } + + public Date getStopTime() + { + return stopTime; + } + + public void setStopTime(Date stopTime) + { + this.stopTime = stopTime; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("jobLogId", getJobLogId()) + .append("jobName", getJobName()) + .append("jobGroup", getJobGroup()) + .append("jobMessage", getJobMessage()) + .append("status", getStatus()) + .append("exceptionInfo", getExceptionInfo()) + .append("startTime", getStartTime()) + .append("stopTime", getStopTime()) + .toString(); + } +} diff --git a/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/mapper/SysJobLogMapper.java b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/mapper/SysJobLogMapper.java new file mode 100644 index 0000000..7f52a23 --- /dev/null +++ b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/mapper/SysJobLogMapper.java @@ -0,0 +1,64 @@ +package com.boyue.quartz.mapper; + +import java.util.List; +import com.boyue.quartz.domain.SysJobLog; + +/** + * 调度任务日志信息 数据层 + * + * @author boyue + */ +public interface SysJobLogMapper +{ + /** + * 获取quartz调度器日志的计划任务 + * + * @param jobLog 调度日志信息 + * @return 调度任务日志集合 + */ + public List selectJobLogList(SysJobLog jobLog); + + /** + * 查询所有调度任务日志 + * + * @return 调度任务日志列表 + */ + public List selectJobLogAll(); + + /** + * 通过调度任务日志ID查询调度信息 + * + * @param jobLogId 调度任务日志ID + * @return 调度任务日志对象信息 + */ + public SysJobLog selectJobLogById(Long jobLogId); + + /** + * 新增任务日志 + * + * @param jobLog 调度日志信息 + * @return 结果 + */ + public int insertJobLog(SysJobLog jobLog); + + /** + * 批量删除调度日志信息 + * + * @param logIds 需要删除的数据ID + * @return 结果 + */ + public int deleteJobLogByIds(Long[] logIds); + + /** + * 删除任务日志 + * + * @param jobId 调度日志ID + * @return 结果 + */ + public int deleteJobLogById(Long jobId); + + /** + * 清空任务日志 + */ + public void cleanJobLog(); +} diff --git a/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/mapper/SysJobMapper.java b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/mapper/SysJobMapper.java new file mode 100644 index 0000000..eb92dca --- /dev/null +++ b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/mapper/SysJobMapper.java @@ -0,0 +1,67 @@ +package com.boyue.quartz.mapper; + +import java.util.List; +import com.boyue.quartz.domain.SysJob; + +/** + * 调度任务信息 数据层 + * + * @author boyue + */ +public interface SysJobMapper +{ + /** + * 查询调度任务日志集合 + * + * @param job 调度信息 + * @return 操作日志集合 + */ + public List selectJobList(SysJob job); + + /** + * 查询所有调度任务 + * + * @return 调度任务列表 + */ + public List selectJobAll(); + + /** + * 通过调度ID查询调度任务信息 + * + * @param jobId 调度ID + * @return 角色对象信息 + */ + public SysJob selectJobById(Long jobId); + + /** + * 通过调度ID删除调度任务信息 + * + * @param jobId 调度ID + * @return 结果 + */ + public int deleteJobById(Long jobId); + + /** + * 批量删除调度任务信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteJobByIds(Long[] ids); + + /** + * 修改调度任务信息 + * + * @param job 调度任务信息 + * @return 结果 + */ + public int updateJob(SysJob job); + + /** + * 新增调度任务信息 + * + * @param job 调度任务信息 + * @return 结果 + */ + public int insertJob(SysJob job); +} diff --git a/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/service/ISysJobLogService.java b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/service/ISysJobLogService.java new file mode 100644 index 0000000..e4431dc --- /dev/null +++ b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/service/ISysJobLogService.java @@ -0,0 +1,56 @@ +package com.boyue.quartz.service; + +import java.util.List; +import com.boyue.quartz.domain.SysJobLog; + +/** + * 定时任务调度日志信息信息 服务层 + * + * @author boyue + */ +public interface ISysJobLogService +{ + /** + * 获取quartz调度器日志的计划任务 + * + * @param jobLog 调度日志信息 + * @return 调度任务日志集合 + */ + public List selectJobLogList(SysJobLog jobLog); + + /** + * 通过调度任务日志ID查询调度信息 + * + * @param jobLogId 调度任务日志ID + * @return 调度任务日志对象信息 + */ + public SysJobLog selectJobLogById(Long jobLogId); + + /** + * 新增任务日志 + * + * @param jobLog 调度日志信息 + */ + public void addJobLog(SysJobLog jobLog); + + /** + * 批量删除调度日志信息 + * + * @param logIds 需要删除的日志ID + * @return 结果 + */ + public int deleteJobLogByIds(Long[] logIds); + + /** + * 删除任务日志 + * + * @param jobId 调度日志ID + * @return 结果 + */ + public int deleteJobLogById(Long jobId); + + /** + * 清空任务日志 + */ + public void cleanJobLog(); +} diff --git a/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/service/ISysJobService.java b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/service/ISysJobService.java new file mode 100644 index 0000000..769efbf --- /dev/null +++ b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/service/ISysJobService.java @@ -0,0 +1,102 @@ +package com.boyue.quartz.service; + +import java.util.List; +import org.quartz.SchedulerException; +import com.boyue.common.exception.job.TaskException; +import com.boyue.quartz.domain.SysJob; + +/** + * 定时任务调度信息信息 服务层 + * + * @author boyue + */ +public interface ISysJobService +{ + /** + * 获取quartz调度器的计划任务 + * + * @param job 调度信息 + * @return 调度任务集合 + */ + public List selectJobList(SysJob job); + + /** + * 通过调度任务ID查询调度信息 + * + * @param jobId 调度任务ID + * @return 调度任务对象信息 + */ + public SysJob selectJobById(Long jobId); + + /** + * 暂停任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int pauseJob(SysJob job) throws SchedulerException; + + /** + * 恢复任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int resumeJob(SysJob job) throws SchedulerException; + + /** + * 删除任务后,所对应的trigger也将被删除 + * + * @param job 调度信息 + * @return 结果 + */ + public int deleteJob(SysJob job) throws SchedulerException; + + /** + * 批量删除调度信息 + * + * @param jobIds 需要删除的任务ID + * @return 结果 + */ + public void deleteJobByIds(Long[] jobIds) throws SchedulerException; + + /** + * 任务调度状态修改 + * + * @param job 调度信息 + * @return 结果 + */ + public int changeStatus(SysJob job) throws SchedulerException; + + /** + * 立即运行任务 + * + * @param job 调度信息 + * @return 结果 + */ + public boolean run(SysJob job) throws SchedulerException; + + /** + * 新增任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int insertJob(SysJob job) throws SchedulerException, TaskException; + + /** + * 更新任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int updateJob(SysJob job) throws SchedulerException, TaskException; + + /** + * 校验cron表达式是否有效 + * + * @param cronExpression 表达式 + * @return 结果 + */ + public boolean checkCronExpressionIsValid(String cronExpression); +} diff --git a/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/service/impl/SysJobLogServiceImpl.java b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/service/impl/SysJobLogServiceImpl.java new file mode 100644 index 0000000..e3b8c13 --- /dev/null +++ b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/service/impl/SysJobLogServiceImpl.java @@ -0,0 +1,87 @@ +package com.boyue.quartz.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.boyue.quartz.domain.SysJobLog; +import com.boyue.quartz.mapper.SysJobLogMapper; +import com.boyue.quartz.service.ISysJobLogService; + +/** + * 定时任务调度日志信息 服务层 + * + * @author boyue + */ +@Service +public class SysJobLogServiceImpl implements ISysJobLogService +{ + @Autowired + private SysJobLogMapper jobLogMapper; + + /** + * 获取quartz调度器日志的计划任务 + * + * @param jobLog 调度日志信息 + * @return 调度任务日志集合 + */ + @Override + public List selectJobLogList(SysJobLog jobLog) + { + return jobLogMapper.selectJobLogList(jobLog); + } + + /** + * 通过调度任务日志ID查询调度信息 + * + * @param jobLogId 调度任务日志ID + * @return 调度任务日志对象信息 + */ + @Override + public SysJobLog selectJobLogById(Long jobLogId) + { + return jobLogMapper.selectJobLogById(jobLogId); + } + + /** + * 新增任务日志 + * + * @param jobLog 调度日志信息 + */ + @Override + public void addJobLog(SysJobLog jobLog) + { + jobLogMapper.insertJobLog(jobLog); + } + + /** + * 批量删除调度日志信息 + * + * @param logIds 需要删除的数据ID + * @return 结果 + */ + @Override + public int deleteJobLogByIds(Long[] logIds) + { + return jobLogMapper.deleteJobLogByIds(logIds); + } + + /** + * 删除任务日志 + * + * @param jobId 调度日志ID + */ + @Override + public int deleteJobLogById(Long jobId) + { + return jobLogMapper.deleteJobLogById(jobId); + } + + /** + * 清空任务日志 + */ + @Override + public void cleanJobLog() + { + jobLogMapper.cleanJobLog(); + } +} diff --git a/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/service/impl/SysJobServiceImpl.java b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/service/impl/SysJobServiceImpl.java new file mode 100644 index 0000000..7c5df63 --- /dev/null +++ b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/service/impl/SysJobServiceImpl.java @@ -0,0 +1,261 @@ +package com.boyue.quartz.service.impl; + +import java.util.List; +import jakarta.annotation.PostConstruct; +import org.quartz.JobDataMap; +import org.quartz.JobKey; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.boyue.common.constant.ScheduleConstants; +import com.boyue.common.exception.job.TaskException; +import com.boyue.quartz.domain.SysJob; +import com.boyue.quartz.mapper.SysJobMapper; +import com.boyue.quartz.service.ISysJobService; +import com.boyue.quartz.util.CronUtils; +import com.boyue.quartz.util.ScheduleUtils; + +/** + * 定时任务调度信息 服务层 + * + * @author boyue + */ +@Service +public class SysJobServiceImpl implements ISysJobService +{ + @Autowired + private Scheduler scheduler; + + @Autowired + private SysJobMapper jobMapper; + + /** + * 项目启动时,初始化定时器 主要是防止手动修改数据库导致未同步到定时任务处理(注:不能手动修改数据库ID和任务组名,否则会导致脏数据) + */ + @PostConstruct + public void init() throws SchedulerException, TaskException + { + scheduler.clear(); + List jobList = jobMapper.selectJobAll(); + for (SysJob job : jobList) + { + ScheduleUtils.createScheduleJob(scheduler, job); + } + } + + /** + * 获取quartz调度器的计划任务列表 + * + * @param job 调度信息 + * @return + */ + @Override + public List selectJobList(SysJob job) + { + return jobMapper.selectJobList(job); + } + + /** + * 通过调度任务ID查询调度信息 + * + * @param jobId 调度任务ID + * @return 调度任务对象信息 + */ + @Override + public SysJob selectJobById(Long jobId) + { + return jobMapper.selectJobById(jobId); + } + + /** + * 暂停任务 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int pauseJob(SysJob job) throws SchedulerException + { + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + job.setStatus(ScheduleConstants.Status.PAUSE.getValue()); + int rows = jobMapper.updateJob(job); + if (rows > 0) + { + scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + return rows; + } + + /** + * 恢复任务 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int resumeJob(SysJob job) throws SchedulerException + { + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + job.setStatus(ScheduleConstants.Status.NORMAL.getValue()); + int rows = jobMapper.updateJob(job); + if (rows > 0) + { + scheduler.resumeJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + return rows; + } + + /** + * 删除任务后,所对应的trigger也将被删除 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int deleteJob(SysJob job) throws SchedulerException + { + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + int rows = jobMapper.deleteJobById(jobId); + if (rows > 0) + { + scheduler.deleteJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + return rows; + } + + /** + * 批量删除调度信息 + * + * @param jobIds 需要删除的任务ID + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteJobByIds(Long[] jobIds) throws SchedulerException + { + for (Long jobId : jobIds) + { + SysJob job = jobMapper.selectJobById(jobId); + deleteJob(job); + } + } + + /** + * 任务调度状态修改 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int changeStatus(SysJob job) throws SchedulerException + { + int rows = 0; + String status = job.getStatus(); + if (ScheduleConstants.Status.NORMAL.getValue().equals(status)) + { + rows = resumeJob(job); + } + else if (ScheduleConstants.Status.PAUSE.getValue().equals(status)) + { + rows = pauseJob(job); + } + return rows; + } + + /** + * 立即运行任务 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean run(SysJob job) throws SchedulerException + { + boolean result = false; + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + SysJob properties = selectJobById(job.getJobId()); + // 参数 + JobDataMap dataMap = new JobDataMap(); + dataMap.put(ScheduleConstants.TASK_PROPERTIES, properties); + JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup); + if (scheduler.checkExists(jobKey)) + { + result = true; + scheduler.triggerJob(jobKey, dataMap); + } + return result; + } + + /** + * 新增任务 + * + * @param job 调度信息 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int insertJob(SysJob job) throws SchedulerException, TaskException + { + job.setStatus(ScheduleConstants.Status.PAUSE.getValue()); + int rows = jobMapper.insertJob(job); + if (rows > 0) + { + ScheduleUtils.createScheduleJob(scheduler, job); + } + return rows; + } + + /** + * 更新任务的时间表达式 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int updateJob(SysJob job) throws SchedulerException, TaskException + { + SysJob properties = selectJobById(job.getJobId()); + int rows = jobMapper.updateJob(job); + if (rows > 0) + { + updateSchedulerJob(job, properties.getJobGroup()); + } + return rows; + } + + /** + * 更新任务 + * + * @param job 任务对象 + * @param jobGroup 任务组名 + */ + public void updateSchedulerJob(SysJob job, String jobGroup) throws SchedulerException, TaskException + { + Long jobId = job.getJobId(); + // 判断是否存在 + JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup); + if (scheduler.checkExists(jobKey)) + { + // 防止创建时存在数据问题 先移除,然后在执行创建操作 + scheduler.deleteJob(jobKey); + } + ScheduleUtils.createScheduleJob(scheduler, job); + } + + /** + * 校验cron表达式是否有效 + * + * @param cronExpression 表达式 + * @return 结果 + */ + @Override + public boolean checkCronExpressionIsValid(String cronExpression) + { + return CronUtils.isValid(cronExpression); + } +} diff --git a/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/task/RyTask.java b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/task/RyTask.java new file mode 100644 index 0000000..f874312 --- /dev/null +++ b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/task/RyTask.java @@ -0,0 +1,28 @@ +package com.boyue.quartz.task; + +import org.springframework.stereotype.Component; +import com.boyue.common.utils.StringUtils; + +/** + * 定时任务调度测试 + * + * @author boyue + */ +@Component("ryTask") +public class RyTask +{ + public void ryMultipleParams(String s, Boolean b, Long l, Double d, Integer i) + { + System.out.println(StringUtils.format("执行多参方法: 字符串类型{},布尔类型{},长整型{},浮点型{},整形{}", s, b, l, d, i)); + } + + public void ryParams(String params) + { + System.out.println("执行有参方法:" + params); + } + + public void ryNoParams() + { + System.out.println("执行无参方法"); + } +} diff --git a/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/util/AbstractQuartzJob.java b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/util/AbstractQuartzJob.java new file mode 100644 index 0000000..6d8fd05 --- /dev/null +++ b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/util/AbstractQuartzJob.java @@ -0,0 +1,107 @@ +package com.boyue.quartz.util; + +import java.util.Date; +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.boyue.common.constant.Constants; +import com.boyue.common.constant.ScheduleConstants; +import com.boyue.common.utils.ExceptionUtil; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.bean.BeanUtils; +import com.boyue.common.utils.spring.SpringUtils; +import com.boyue.quartz.domain.SysJob; +import com.boyue.quartz.domain.SysJobLog; +import com.boyue.quartz.service.ISysJobLogService; + +/** + * 抽象quartz调用 + * + * @author boyue + */ +public abstract class AbstractQuartzJob implements Job +{ + private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class); + + /** + * 线程本地变量 + */ + private static ThreadLocal threadLocal = new ThreadLocal<>(); + + @Override + public void execute(JobExecutionContext context) throws JobExecutionException + { + SysJob sysJob = new SysJob(); + BeanUtils.copyBeanProp(sysJob, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES)); + try + { + before(context, sysJob); + if (sysJob != null) + { + doExecute(context, sysJob); + } + after(context, sysJob, null); + } + catch (Exception e) + { + log.error("任务执行异常 - :", e); + after(context, sysJob, e); + } + } + + /** + * 执行前 + * + * @param context 工作执行上下文对象 + * @param sysJob 系统计划任务 + */ + protected void before(JobExecutionContext context, SysJob sysJob) + { + threadLocal.set(new Date()); + } + + /** + * 执行后 + * + * @param context 工作执行上下文对象 + * @param sysJob 系统计划任务 + */ + protected void after(JobExecutionContext context, SysJob sysJob, Exception e) + { + Date startTime = threadLocal.get(); + threadLocal.remove(); + + final SysJobLog sysJobLog = new SysJobLog(); + sysJobLog.setJobName(sysJob.getJobName()); + sysJobLog.setJobGroup(sysJob.getJobGroup()); + sysJobLog.setInvokeTarget(sysJob.getInvokeTarget()); + sysJobLog.setStartTime(startTime); + sysJobLog.setStopTime(new Date()); + long runMs = sysJobLog.getStopTime().getTime() - sysJobLog.getStartTime().getTime(); + sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时:" + runMs + "毫秒"); + if (e != null) + { + sysJobLog.setStatus(Constants.FAIL); + String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000); + sysJobLog.setExceptionInfo(errorMsg); + } + else + { + sysJobLog.setStatus(Constants.SUCCESS); + } + + // 写入数据库当中 + SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog); + } + + /** + * 执行方法,由子类重载 + * + * @param context 工作执行上下文对象 + * @param sysJob 系统计划任务 + * @throws Exception 执行过程中的异常 + */ + protected abstract void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception; +} diff --git a/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/util/CronUtils.java b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/util/CronUtils.java new file mode 100644 index 0000000..71f6bc4 --- /dev/null +++ b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/util/CronUtils.java @@ -0,0 +1,63 @@ +package com.boyue.quartz.util; + +import java.text.ParseException; +import java.util.Date; +import org.quartz.CronExpression; + +/** + * cron表达式工具类 + * + * @author boyue + * + */ +public class CronUtils +{ + /** + * 返回一个布尔值代表一个给定的Cron表达式的有效性 + * + * @param cronExpression Cron表达式 + * @return boolean 表达式是否有效 + */ + public static boolean isValid(String cronExpression) + { + return CronExpression.isValidExpression(cronExpression); + } + + /** + * 返回一个字符串值,表示该消息无效Cron表达式给出有效性 + * + * @param cronExpression Cron表达式 + * @return String 无效时返回表达式错误描述,如果有效返回null + */ + public static String getInvalidMessage(String cronExpression) + { + try + { + new CronExpression(cronExpression); + return null; + } + catch (ParseException pe) + { + return pe.getMessage(); + } + } + + /** + * 返回下一个执行时间根据给定的Cron表达式 + * + * @param cronExpression Cron表达式 + * @return Date 下次Cron表达式执行时间 + */ + public static Date getNextExecution(String cronExpression) + { + try + { + CronExpression cron = new CronExpression(cronExpression); + return cron.getNextValidTimeAfter(new Date(System.currentTimeMillis())); + } + catch (ParseException e) + { + throw new IllegalArgumentException(e.getMessage()); + } + } +} diff --git a/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/util/JobInvokeUtil.java b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/util/JobInvokeUtil.java new file mode 100644 index 0000000..e26c3b5 --- /dev/null +++ b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/util/JobInvokeUtil.java @@ -0,0 +1,182 @@ +package com.boyue.quartz.util; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.LinkedList; +import java.util.List; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.spring.SpringUtils; +import com.boyue.quartz.domain.SysJob; + +/** + * 任务执行工具 + * + * @author boyue + */ +public class JobInvokeUtil +{ + /** + * 执行方法 + * + * @param sysJob 系统任务 + */ + public static void invokeMethod(SysJob sysJob) throws Exception + { + String invokeTarget = sysJob.getInvokeTarget(); + String beanName = getBeanName(invokeTarget); + String methodName = getMethodName(invokeTarget); + List methodParams = getMethodParams(invokeTarget); + + if (!isValidClassName(beanName)) + { + Object bean = SpringUtils.getBean(beanName); + invokeMethod(bean, methodName, methodParams); + } + else + { + Object bean = Class.forName(beanName).getDeclaredConstructor().newInstance(); + invokeMethod(bean, methodName, methodParams); + } + } + + /** + * 调用任务方法 + * + * @param bean 目标对象 + * @param methodName 方法名称 + * @param methodParams 方法参数 + */ + private static void invokeMethod(Object bean, String methodName, List methodParams) + throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, + InvocationTargetException + { + if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0) + { + Method method = bean.getClass().getMethod(methodName, getMethodParamsType(methodParams)); + method.invoke(bean, getMethodParamsValue(methodParams)); + } + else + { + Method method = bean.getClass().getMethod(methodName); + method.invoke(bean); + } + } + + /** + * 校验是否为为class包名 + * + * @param invokeTarget 名称 + * @return true是 false否 + */ + public static boolean isValidClassName(String invokeTarget) + { + return StringUtils.countMatches(invokeTarget, ".") > 1; + } + + /** + * 获取bean名称 + * + * @param invokeTarget 目标字符串 + * @return bean名称 + */ + public static String getBeanName(String invokeTarget) + { + String beanName = StringUtils.substringBefore(invokeTarget, "("); + return StringUtils.substringBeforeLast(beanName, "."); + } + + /** + * 获取bean方法 + * + * @param invokeTarget 目标字符串 + * @return method方法 + */ + public static String getMethodName(String invokeTarget) + { + String methodName = StringUtils.substringBefore(invokeTarget, "("); + return StringUtils.substringAfterLast(methodName, "."); + } + + /** + * 获取method方法参数相关列表 + * + * @param invokeTarget 目标字符串 + * @return method方法相关参数列表 + */ + public static List getMethodParams(String invokeTarget) + { + String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")"); + if (StringUtils.isEmpty(methodStr)) + { + return null; + } + String[] methodParams = methodStr.split(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)"); + List classs = new LinkedList<>(); + for (int i = 0; i < methodParams.length; i++) + { + String str = StringUtils.trimToEmpty(methodParams[i]); + // String字符串类型,以'或"开头 + if (StringUtils.startsWithAny(str, "'", "\"")) + { + classs.add(new Object[] { StringUtils.substring(str, 1, str.length() - 1), String.class }); + } + // boolean布尔类型,等于true或者false + else if ("true".equalsIgnoreCase(str) || "false".equalsIgnoreCase(str)) + { + classs.add(new Object[] { Boolean.valueOf(str), Boolean.class }); + } + // long长整形,以L结尾 + else if (StringUtils.endsWith(str, "L")) + { + classs.add(new Object[] { Long.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Long.class }); + } + // double浮点类型,以D结尾 + else if (StringUtils.endsWith(str, "D")) + { + classs.add(new Object[] { Double.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Double.class }); + } + // 其他类型归类为整形 + else + { + classs.add(new Object[] { Integer.valueOf(str), Integer.class }); + } + } + return classs; + } + + /** + * 获取参数类型 + * + * @param methodParams 参数相关列表 + * @return 参数类型列表 + */ + public static Class[] getMethodParamsType(List methodParams) + { + Class[] classs = new Class[methodParams.size()]; + int index = 0; + for (Object[] os : methodParams) + { + classs[index] = (Class) os[1]; + index++; + } + return classs; + } + + /** + * 获取参数值 + * + * @param methodParams 参数相关列表 + * @return 参数值列表 + */ + public static Object[] getMethodParamsValue(List methodParams) + { + Object[] classs = new Object[methodParams.size()]; + int index = 0; + for (Object[] os : methodParams) + { + classs[index] = (Object) os[0]; + index++; + } + return classs; + } +} diff --git a/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/util/QuartzDisallowConcurrentExecution.java b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/util/QuartzDisallowConcurrentExecution.java new file mode 100644 index 0000000..b3db63d --- /dev/null +++ b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/util/QuartzDisallowConcurrentExecution.java @@ -0,0 +1,21 @@ +package com.boyue.quartz.util; + +import org.quartz.DisallowConcurrentExecution; +import org.quartz.JobExecutionContext; +import com.boyue.quartz.domain.SysJob; + +/** + * 定时任务处理(禁止并发执行) + * + * @author boyue + * + */ +@DisallowConcurrentExecution +public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob +{ + @Override + protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception + { + JobInvokeUtil.invokeMethod(sysJob); + } +} diff --git a/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/util/QuartzJobExecution.java b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/util/QuartzJobExecution.java new file mode 100644 index 0000000..9c4e7ed --- /dev/null +++ b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/util/QuartzJobExecution.java @@ -0,0 +1,19 @@ +package com.boyue.quartz.util; + +import org.quartz.JobExecutionContext; +import com.boyue.quartz.domain.SysJob; + +/** + * 定时任务处理(允许并发执行) + * + * @author boyue + * + */ +public class QuartzJobExecution extends AbstractQuartzJob +{ + @Override + protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception + { + JobInvokeUtil.invokeMethod(sysJob); + } +} diff --git a/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/util/ScheduleUtils.java b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/util/ScheduleUtils.java new file mode 100644 index 0000000..f52c9b9 --- /dev/null +++ b/boyue-models/boyue-quartz/src/main/java/com/boyue/quartz/util/ScheduleUtils.java @@ -0,0 +1,141 @@ +package com.boyue.quartz.util; + +import org.quartz.CronScheduleBuilder; +import org.quartz.CronTrigger; +import org.quartz.Job; +import org.quartz.JobBuilder; +import org.quartz.JobDetail; +import org.quartz.JobKey; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.quartz.TriggerBuilder; +import org.quartz.TriggerKey; +import com.boyue.common.constant.Constants; +import com.boyue.common.constant.ScheduleConstants; +import com.boyue.common.exception.job.TaskException; +import com.boyue.common.exception.job.TaskException.Code; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.spring.SpringUtils; +import com.boyue.quartz.domain.SysJob; + +/** + * 定时任务工具类 + * + * @author boyue + * + */ +public class ScheduleUtils +{ + /** + * 得到quartz任务类 + * + * @param sysJob 执行计划 + * @return 具体执行任务类 + */ + private static Class getQuartzJobClass(SysJob sysJob) + { + boolean isConcurrent = "0".equals(sysJob.getConcurrent()); + return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class; + } + + /** + * 构建任务触发对象 + */ + public static TriggerKey getTriggerKey(Long jobId, String jobGroup) + { + return TriggerKey.triggerKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup); + } + + /** + * 构建任务键对象 + */ + public static JobKey getJobKey(Long jobId, String jobGroup) + { + return JobKey.jobKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup); + } + + /** + * 创建定时任务 + */ + public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException + { + Class jobClass = getQuartzJobClass(job); + // 构建job信息 + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build(); + + // 表达式调度构建器 + CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression()); + cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder); + + // 按新的cronExpression表达式构建一个新的trigger + CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup)) + .withSchedule(cronScheduleBuilder).build(); + + // 放入参数,运行时的方法可以获取 + jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job); + + // 判断是否存在 + if (scheduler.checkExists(getJobKey(jobId, jobGroup))) + { + // 防止创建时存在数据问题 先移除,然后在执行创建操作 + scheduler.deleteJob(getJobKey(jobId, jobGroup)); + } + + // 判断任务是否过期 + if (StringUtils.isNotNull(CronUtils.getNextExecution(job.getCronExpression()))) + { + // 执行调度任务 + scheduler.scheduleJob(jobDetail, trigger); + } + + // 暂停任务 + if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue())) + { + scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + } + + /** + * 设置定时任务策略 + */ + public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob job, CronScheduleBuilder cb) + throws TaskException + { + switch (job.getMisfirePolicy()) + { + case ScheduleConstants.MISFIRE_DEFAULT: + return cb; + case ScheduleConstants.MISFIRE_IGNORE_MISFIRES: + return cb.withMisfireHandlingInstructionIgnoreMisfires(); + case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED: + return cb.withMisfireHandlingInstructionFireAndProceed(); + case ScheduleConstants.MISFIRE_DO_NOTHING: + return cb.withMisfireHandlingInstructionDoNothing(); + default: + throw new TaskException("The task misfire policy '" + job.getMisfirePolicy() + + "' cannot be used in cron schedule tasks", Code.CONFIG_ERROR); + } + } + + /** + * 检查包名是否为白名单配置 + * + * @param invokeTarget 目标字符串 + * @return 结果 + */ + public static boolean whiteList(String invokeTarget) + { + String packageName = StringUtils.substringBefore(invokeTarget, "("); + int count = StringUtils.countMatches(packageName, "."); + if (count > 1) + { + return StringUtils.containsAnyIgnoreCase(invokeTarget, Constants.JOB_WHITELIST_STR); + } + Object obj = SpringUtils.getBean(StringUtils.split(invokeTarget, ".")[0]); + String beanPackageName = obj.getClass().getPackage().getName(); + return StringUtils.containsAnyIgnoreCase(beanPackageName, Constants.JOB_WHITELIST_STR) + && !StringUtils.containsAnyIgnoreCase(beanPackageName, Constants.JOB_ERROR_STR); + } +} diff --git a/boyue-models/boyue-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml b/boyue-models/boyue-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml new file mode 100644 index 0000000..859d777 --- /dev/null +++ b/boyue-models/boyue-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + select job_log_id, job_name, job_group, invoke_target, job_message, status, exception_info, create_time + from sys_job_log + + + + + + + + + + delete from sys_job_log where job_log_id = #{jobLogId} + + + + delete from sys_job_log where job_log_id in + + #{jobLogId} + + + + + truncate table sys_job_log + + + + insert into sys_job_log( + job_log_id, + job_name, + job_group, + invoke_target, + job_message, + status, + exception_info, + create_time + )values( + #{jobLogId}, + #{jobName}, + #{jobGroup}, + #{invokeTarget}, + #{jobMessage}, + #{status}, + #{exceptionInfo}, + sysdate() + ) + + + \ No newline at end of file diff --git a/boyue-models/boyue-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml b/boyue-models/boyue-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml new file mode 100644 index 0000000..2870a77 --- /dev/null +++ b/boyue-models/boyue-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + select job_id, job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, remark + from sys_job + + + + + + + + + + delete from sys_job where job_id = #{jobId} + + + + delete from sys_job where job_id in + + #{jobId} + + + + + update sys_job + + job_name = #{jobName}, + job_group = #{jobGroup}, + invoke_target = #{invokeTarget}, + cron_expression = #{cronExpression}, + misfire_policy = #{misfirePolicy}, + concurrent = #{concurrent}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where job_id = #{jobId} + + + + insert into sys_job( + job_id, + job_name, + job_group, + invoke_target, + cron_expression, + misfire_policy, + concurrent, + status, + remark, + create_by, + create_time + )values( + #{jobId}, + #{jobName}, + #{jobGroup}, + #{invokeTarget}, + #{cronExpression}, + #{misfirePolicy}, + #{concurrent}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + \ No newline at end of file diff --git a/boyue-models/pom.xml b/boyue-models/pom.xml new file mode 100644 index 0000000..4cea8d1 --- /dev/null +++ b/boyue-models/pom.xml @@ -0,0 +1,97 @@ + + + + boyuehasfj-java + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-models + + + 中间件 + + + + + + + com.boyue + boyue-quartz + ${boyue.version} + + + + + com.boyue + boyue-generator + ${boyue.version} + + + + + com.boyue + boyue-online + ${boyue.version} + + + + + com.boyue + boyue-message + ${boyue.version} + + + + + com.boyue + boyue-form + ${boyue.version} + + + + + com.boyue + boyue-flowable + ${boyue.version} + + + + + com.boyue + boyue-tfa-phone + ${boyue.version} + + + + + com.boyue + boyue-tfa-email + ${boyue.version} + + + + + com.boyue + boyue-hasfj + ${boyue.version} + + + + + + + + boyue-models-starter + boyue-generator + boyue-quartz + boyue-online + boyue-message + boyue-form + boyue-flowable + boyue-hasfj + + pom + \ No newline at end of file diff --git a/boyue-pay/boyue-pay-alipay/pom.xml b/boyue-pay/boyue-pay-alipay/pom.xml new file mode 100644 index 0000000..b08c6cb --- /dev/null +++ b/boyue-pay/boyue-pay-alipay/pom.xml @@ -0,0 +1,31 @@ + + + + boyue-pay + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-pay-alipay + + + 支付宝支付模块 + + + + + + com.boyue + boyue-pay-common + + + + com.alipay.sdk + alipay-easysdk + + + + + \ No newline at end of file diff --git a/boyue-pay/boyue-pay-alipay/src/main/java/com/boyue/pay/alipay/config/AliPayConfig.java b/boyue-pay/boyue-pay-alipay/src/main/java/com/boyue/pay/alipay/config/AliPayConfig.java new file mode 100644 index 0000000..d2a7f80 --- /dev/null +++ b/boyue-pay/boyue-pay-alipay/src/main/java/com/boyue/pay/alipay/config/AliPayConfig.java @@ -0,0 +1,80 @@ +package com.boyue.pay.alipay.config; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; + +import com.alipay.easysdk.factory.Factory; +import com.alipay.easysdk.kernel.Config; + +/** + * @author zlh + */ +@Configuration +@ConditionalOnProperty(prefix = "pay.alipay", name = "enabled", havingValue = "true") +public class AliPayConfig { + @Value("${pay.alipay.appId}") + private String appId; + @Value("${pay.alipay.notifyUrl}") + private String notifyUrl; + @Value("${pay.alipay.appPrivateKey}") + private String appPrivateKey; + @Value("${pay.alipay.alipayPublicKey}") + private String alipayPublicKey; + + @Autowired + private ApplicationContext applicationContext; + + private String getAppPrivateKey() throws Exception { + if (appPrivateKey.startsWith("classpath")) { + Resource resource = applicationContext.getResource(appPrivateKey); + InputStream inputStream = resource.getInputStream(); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + String appPrivateKeyValue = bufferedReader.lines().collect(Collectors.joining(System.lineSeparator())); + bufferedReader.close(); + appPrivateKey = appPrivateKeyValue; + } + return appPrivateKey; + } + + private String getAlipayPublicKey() throws Exception { + if (alipayPublicKey.startsWith("classpath")) { + Resource resource = applicationContext.getResource(alipayPublicKey); + InputStream inputStream = resource.getInputStream(); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + String alipayPublicKeyValue = bufferedReader.lines().collect(Collectors.joining(System.lineSeparator())); + bufferedReader.close(); + alipayPublicKey = alipayPublicKeyValue; + } + return alipayPublicKey; + + } + + @Bean + protected Config alipayBaseConfig() throws Exception { + // 设置参数(全局只需设置一次) + Config config = new Config(); + config.protocol = "https"; + config.gatewayHost = "openapi.alipay.com";// openapi-sandbox.dl.alipaydev.com||openapi.alipay.com + config.signType = "RSA2"; + config.appId = this.appId; + config.merchantPrivateKey = getAppPrivateKey(); + config.alipayPublicKey = getAlipayPublicKey(); + System.out.println(getAlipayPublicKey()); + config.notifyUrl = this.notifyUrl; + Factory.setOptions(config); + return config; + } + +} + +// https://openapi-sandbox.dl.alipaydev.com/gateway.do \ No newline at end of file diff --git a/boyue-pay/boyue-pay-alipay/src/main/java/com/boyue/pay/alipay/service/IAliPayService.java b/boyue-pay/boyue-pay-alipay/src/main/java/com/boyue/pay/alipay/service/IAliPayService.java new file mode 100644 index 0000000..5d38682 --- /dev/null +++ b/boyue-pay/boyue-pay-alipay/src/main/java/com/boyue/pay/alipay/service/IAliPayService.java @@ -0,0 +1,9 @@ +package com.boyue.pay.alipay.service; + +import java.util.Map; + +import com.boyue.pay.service.PayService; + +public interface IAliPayService extends PayService { + public void callback(Map params); +} diff --git a/boyue-pay/boyue-pay-alipay/src/main/java/com/boyue/pay/alipay/service/Impl/AliPayService.java b/boyue-pay/boyue-pay-alipay/src/main/java/com/boyue/pay/alipay/service/Impl/AliPayService.java new file mode 100644 index 0000000..396eb89 --- /dev/null +++ b/boyue-pay/boyue-pay-alipay/src/main/java/com/boyue/pay/alipay/service/Impl/AliPayService.java @@ -0,0 +1,126 @@ +package com.boyue.pay.alipay.service.Impl; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; + +import com.alipay.easysdk.factory.Factory; +import com.alipay.easysdk.payment.common.models.AlipayTradeRefundResponse; +import com.alipay.easysdk.payment.page.models.AlipayTradePagePayResponse; +import com.boyue.common.exception.ServiceException; +import com.boyue.pay.alipay.service.IAliPayService; +import com.boyue.pay.domain.PayOrder; +import com.boyue.pay.service.IPayOrderService; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Service("pay:service:alipay") +@ConditionalOnProperty(prefix = "pay.alipay", name = "enabled", havingValue = "true") +public class AliPayService implements IAliPayService { + public void callback(Map params) { + } + + @Autowired + private IPayOrderService payOrderService; + + public String payUrl(PayOrder payOrder) { + + try { + AlipayTradePagePayResponse response = Factory.Payment.Page().pay( + payOrder.getOrderContent(), + payOrder.getOrderNumber(), + payOrder.getActualAmount(), + ""); + return response.getBody(); + } catch (Exception e) { + throw new ServiceException("创建支付宝支付URL失败"); + } + } + + @Override + public String notify(HttpServletRequest request, HttpServletResponse response) { + if (request.getParameter("trade_status").equals("TRADE_SUCCESS")) { + + Map params = new HashMap<>(); + Map requestParams = request.getParameterMap(); + for (String name : requestParams.keySet()) { + params.put(name, request.getParameter(name)); + } + String orderNumber = params.get("out_trade_no"); + try { + if (Factory.Payment.Common().verifyNotify(params)) { + payOrderService.updateStatus(orderNumber, "已支付"); + } + return "success"; + } catch (Exception e) { + e.printStackTrace(); + } + } + return "fail"; + } + + @Override + public PayOrder query(PayOrder payOrder) { + try { + // 使用支付宝SDK查询订单状态 + com.alipay.easysdk.payment.common.models.AlipayTradeQueryResponse response = Factory.Payment.Common() + .query(payOrder.getOrderNumber()); + + // 根据查询结果更新订单状态 + if ("10000".equals(response.code)) { + String tradeStatus = response.tradeStatus; + String orderStatus = ""; + + // 根据支付宝交易状态映射到系统订单状态 + switch (tradeStatus) { + case "TRADE_SUCCESS": + case "TRADE_FINISHED": + orderStatus = "已支付"; + break; + case "WAIT_BUYER_PAY": + orderStatus = "待支付"; + break; + case "TRADE_CLOSED": + orderStatus = "已关闭"; + break; + default: + orderStatus = "未知状态"; + } + + // 更新订单信息 + payOrderService.updateStatus(payOrder.getOrderNumber(), orderStatus); + } else { + throw new ServiceException("查询支付宝订单失败:" + response.subMsg); + } + + return payOrder; + } catch (Exception e) { + throw new ServiceException("查询支付宝订单异常:" + e.getMessage()); + } + } + + @Override + public PayOrder refund(PayOrder payOrder) { + try { + // 使用支付宝SDK进行退款 + AlipayTradeRefundResponse response = Factory.Payment.Common().refund( + payOrder.getOrderNumber(), + payOrder.getActualAmount()); + + // 处理退款结果 + if ("10000".equals(response.code)) { + payOrderService.updateStatus(payOrder.getOrderNumber(), "已退款"); + } else { + throw new ServiceException("支付宝退款失败:" + response.subMsg); + } + + return payOrder; + } catch (Exception e) { + throw new ServiceException("支付宝退款异常:" + e.getMessage()); + } + } +} diff --git a/boyue-pay/boyue-pay-alipay/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/boyue-pay/boyue-pay-alipay/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000..9d3fbe3 --- /dev/null +++ b/boyue-pay/boyue-pay-alipay/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,29 @@ +{ + "properties": [ + { + "name": "pay.alipay.enabled", + "type": "java.lang.Boolean", + "description": "是否启用支付宝支付" + }, + { + "name": "pay.alipay.appId", + "type": "java.lang.String", + "description": "支付宝appid" + }, + { + "name": "pay.alipay.appPrivateKey", + "type": "java.lang.String", + "description": "支付宝应用私钥,可以直接用字符串,也可以是基于classpath的文件路径" + }, + { + "name": "pay.alipay.alipayPublicKey", + "type": "java.lang.String", + "description": "支付宝应用公钥,可以直接用字符串,也可以是基于classpath的文件路径" + }, + { + "name": "pay.alipay.notifyUrl", + "type": "java.lang.String", + "description": "支付宝支付回调地址" + } + ] +} \ No newline at end of file diff --git a/boyue-pay/boyue-pay-common/pom.xml b/boyue-pay/boyue-pay-common/pom.xml new file mode 100644 index 0000000..5dc0f07 --- /dev/null +++ b/boyue-pay/boyue-pay-common/pom.xml @@ -0,0 +1,28 @@ + + + + boyue-pay + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-pay-common + + + 支付基础模块 + + + + + + + com.boyue + boyue-common + + + + + \ No newline at end of file diff --git a/boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/domain/PayInvoice.java b/boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/domain/PayInvoice.java new file mode 100644 index 0000000..87174ff --- /dev/null +++ b/boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/domain/PayInvoice.java @@ -0,0 +1,168 @@ +package com.boyue.pay.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.boyue.common.annotation.Excel; +import com.boyue.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 发票对象 pay_invoice + * + * @author boyue + * @date 2024-06-11 + */ +@Schema(description = "发票对象") +public class PayInvoice extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + + /** 发票id */ + @Schema(title = "发票id") + private Long invoiceId; + + /** 订单号 */ + @Schema(title = "订单号") + @Excel(name = "订单号") + private String orderNumber; + + /** 发票类型 */ + @Schema(title = "发票类型") + @Excel(name = "发票类型") + private String invoiceType; + + /** 发票抬头 */ + @Schema(title = "发票抬头") + @Excel(name = "发票抬头") + private String invoiceHeader; + + /** 纳税人识别号 */ + @Schema(title = "纳税人识别号") + @Excel(name = "纳税人识别号") + private String invoiceNumber; + + /** 收票人手机号 */ + @Schema(title = "收票人手机号") + @Excel(name = "收票人手机号") + private String invoicePhone; + + /** 收票人邮箱 */ + @Schema(title = "收票人邮箱") + @Excel(name = "收票人邮箱") + private String invoiceEmail; + + /** 发票备注 */ + @Schema(title = "发票备注") + @Excel(name = "发票备注") + private String invoiceRemark; + public void setInvoiceId(Long invoiceId) + { + this.invoiceId = invoiceId; + } + + public Long getInvoiceId() + { + return invoiceId; + } + + + public void setOrderNumber(String orderNumber) + { + this.orderNumber = orderNumber; + } + + public String getOrderNumber() + { + return orderNumber; + } + + + public void setInvoiceType(String invoiceType) + { + this.invoiceType = invoiceType; + } + + public String getInvoiceType() + { + return invoiceType; + } + + + public void setInvoiceHeader(String invoiceHeader) + { + this.invoiceHeader = invoiceHeader; + } + + public String getInvoiceHeader() + { + return invoiceHeader; + } + + + public void setInvoiceNumber(String invoiceNumber) + { + this.invoiceNumber = invoiceNumber; + } + + public String getInvoiceNumber() + { + return invoiceNumber; + } + + + public void setInvoicePhone(String invoicePhone) + { + this.invoicePhone = invoicePhone; + } + + public String getInvoicePhone() + { + return invoicePhone; + } + + + public void setInvoiceEmail(String invoiceEmail) + { + this.invoiceEmail = invoiceEmail; + } + + public String getInvoiceEmail() + { + return invoiceEmail; + } + + + public void setInvoiceRemark(String invoiceRemark) + { + this.invoiceRemark = invoiceRemark; + } + + public String getInvoiceRemark() + { + return invoiceRemark; + } + + + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("invoiceId", getInvoiceId()) + .append("orderNumber", getOrderNumber()) + .append("invoiceType", getInvoiceType()) + .append("invoiceHeader", getInvoiceHeader()) + .append("invoiceNumber", getInvoiceNumber()) + .append("invoicePhone", getInvoicePhone()) + .append("invoiceEmail", getInvoiceEmail()) + .append("invoiceRemark", getInvoiceRemark()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/domain/PayOrder.java b/boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/domain/PayOrder.java new file mode 100644 index 0000000..4de2ec4 --- /dev/null +++ b/boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/domain/PayOrder.java @@ -0,0 +1,141 @@ +package com.boyue.pay.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.boyue.common.annotation.Excel; +import com.boyue.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 订单对象 pay_order + * + * @author boyue + * @date 2024-06-11 + */ +@Schema(description = "订单对象") +public class PayOrder extends BaseEntity { + private static final long serialVersionUID = 1L; + + /** 订单ID */ + @Schema(title = "订单ID") + private Long orderId; + + /** 订单号 */ + @Schema(title = "商户订单号") + @Excel(name = "商户订单号") + private String orderNumber; + + /** 第三方订单号 */ + @Schema(title = "第三方订单号") + @Excel(name = "第三方订单号") + private String thirdNumber; + + /** 订单状态 */ + @Schema(title = "订单状态") + @Excel(name = "订单状态") + private String orderStatus; + + /** 订单总金额 */ + @Schema(title = "订单总金额") + @Excel(name = "订单总金额") + private String totalAmount; + + /** 实际支付金额 */ + @Schema(title = "实际支付金额") + @Excel(name = "实际支付金额") + private String actualAmount; + + /** 订单内容 */ + @Schema(title = "订单内容") + @Excel(name = "订单内容") + private String orderContent; + + /** 负载信息 */ + @Schema(title = "负载信息") + @Excel(name = "负载信息") + private String orderMessage; + + public void setOrderId(Long orderId) { + this.orderId = orderId; + } + + public Long getOrderId() { + return orderId; + } + + public void setOrderNumber(String orderNumber) { + this.orderNumber = orderNumber; + } + + public String getOrderNumber() { + return orderNumber; + } + + public void setThirdNumber(String thirdNumber) { + this.thirdNumber = thirdNumber; + } + + public String getThirdNumber() { + return thirdNumber; + } + + public void setOrderStatus(String orderStatus) { + this.orderStatus = orderStatus; + } + + public String getOrderStatus() { + return orderStatus; + } + + public void setTotalAmount(String totalAmount) { + this.totalAmount = totalAmount; + } + + public String getTotalAmount() { + return totalAmount; + } + + public void setActualAmount(String actualAmount) { + this.actualAmount = actualAmount; + } + + public String getActualAmount() { + return actualAmount; + } + + public void setOrderContent(String orderContent) { + this.orderContent = orderContent; + } + + public String getOrderContent() { + return orderContent; + } + + public void setOrderMessage(String orderMessage) { + this.orderMessage = orderMessage; + } + + public String getOrderMessage() { + return orderMessage; + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("orderId", getOrderId()) + .append("orderNumber", getOrderNumber()) + .append("orderStatus", getOrderStatus()) + .append("totalAmount", getTotalAmount()) + .append("actualAmount", getActualAmount()) + .append("orderContent", getOrderContent()) + .append("orderMessage", getOrderMessage()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/mapper/PayInvoiceMapper.java b/boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/mapper/PayInvoiceMapper.java new file mode 100644 index 0000000..af9db39 --- /dev/null +++ b/boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/mapper/PayInvoiceMapper.java @@ -0,0 +1,62 @@ +package com.boyue.pay.mapper; + +import java.util.List; + +import com.boyue.pay.domain.PayInvoice; + +/** + * 发票Mapper接口 + * + * @author boyue + * @date 2024-06-11 + */ +public interface PayInvoiceMapper +{ + /** + * 查询发票 + * + * @param invoiceId 发票主键 + * @return 发票 + */ + public PayInvoice selectPayInvoiceByInvoiceId(Long invoiceId); + + /** + * 查询发票列表 + * + * @param payInvoice 发票 + * @return 发票集合 + */ + public List selectPayInvoiceList(PayInvoice payInvoice); + + /** + * 新增发票 + * + * @param payInvoice 发票 + * @return 结果 + */ + public int insertPayInvoice(PayInvoice payInvoice); + + /** + * 修改发票 + * + * @param payInvoice 发票 + * @return 结果 + */ + public int updatePayInvoice(PayInvoice payInvoice); + + /** + * 删除发票 + * + * @param invoiceId 发票主键 + * @return 结果 + */ + public int deletePayInvoiceByInvoiceId(Long invoiceId); + + /** + * 批量删除发票 + * + * @param invoiceIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deletePayInvoiceByInvoiceIds(Long[] invoiceIds); +} diff --git a/boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/mapper/PayOrderMapper.java b/boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/mapper/PayOrderMapper.java new file mode 100644 index 0000000..a649a44 --- /dev/null +++ b/boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/mapper/PayOrderMapper.java @@ -0,0 +1,73 @@ +package com.boyue.pay.mapper; + +import java.util.List; + +import com.boyue.pay.domain.PayOrder; + +/** + * 订单Mapper接口 + * + * @author Dftre + * @date 2024-02-15 + */ +public interface PayOrderMapper +{ + /** + * 查询订单 + * + * @param orderId 订单主键 + * @return 订单 + */ + public PayOrder selectPayOrderByOrderId(Long orderId); + + /** + * 查询订单 + * + * @param orderNumber 订单号 + * @return 订单集合 + */ + public PayOrder selectPayOrderByOrderNumber(String orderNumber); + + /** + * 查询订单列表 + * + * @param payOrder 订单 + * @return 订单集合 + */ + public List selectPayOrderList(PayOrder payOrder); + + /** + * 新增订单 + * + * @param payOrder 订单 + * @return 结果 + */ + public int insertPayOrder(PayOrder payOrder); + + /** + * 修改订单 + * + * @param payOrder 订单 + * @return 结果 + */ + public int updatePayOrder(PayOrder payOrder); + + /** + * 删除订单 + * + * @param orderId 订单主键 + * @return 结果 + */ + public int deletePayOrderByOrderId(Long orderId); + + /** + * 批量删除订单 + * + * @param orderIds 需要删除的数据主键集合 + * @return 结果 + */ + public int deletePayOrderByOrderIds(Long[] orderIds); + + public int deletePayOrderByOrderNumber(String orderNumber); + public int updateStatus(String orderNumber, String orderStatus); +} diff --git a/boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/service/IPayInvoiceService.java b/boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/service/IPayInvoiceService.java new file mode 100644 index 0000000..2fe0c8b --- /dev/null +++ b/boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/service/IPayInvoiceService.java @@ -0,0 +1,62 @@ +package com.boyue.pay.service; + +import java.util.List; + +import com.boyue.pay.domain.PayInvoice; + +/** + * 发票Service接口 + * + * @author boyue + * @date 2024-06-11 + */ +public interface IPayInvoiceService +{ + /** + * 查询发票 + * + * @param invoiceId 发票主键 + * @return 发票 + */ + public PayInvoice selectPayInvoiceByInvoiceId(Long invoiceId); + + /** + * 查询发票列表 + * + * @param payInvoice 发票 + * @return 发票集合 + */ + public List selectPayInvoiceList(PayInvoice payInvoice); + + /** + * 新增发票 + * + * @param payInvoice 发票 + * @return 结果 + */ + public int insertPayInvoice(PayInvoice payInvoice); + + /** + * 修改发票 + * + * @param payInvoice 发票 + * @return 结果 + */ + public int updatePayInvoice(PayInvoice payInvoice); + + /** + * 批量删除发票 + * + * @param invoiceIds 需要删除的发票主键集合 + * @return 结果 + */ + public int deletePayInvoiceByInvoiceIds(Long[] invoiceIds); + + /** + * 删除发票信息 + * + * @param invoiceId 发票主键 + * @return 结果 + */ + public int deletePayInvoiceByInvoiceId(Long invoiceId); +} diff --git a/boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/service/IPayOrderService.java b/boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/service/IPayOrderService.java new file mode 100644 index 0000000..65cb176 --- /dev/null +++ b/boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/service/IPayOrderService.java @@ -0,0 +1,80 @@ +package com.boyue.pay.service; + +import java.util.List; + +import com.boyue.pay.domain.PayOrder; + +/** + * 订单Service接口 + * + * @author boyue + * @date 2024-06-11 + */ +public interface IPayOrderService +{ + /** + * 查询订单 + * + * @param orderId 订单主键 + * @return 订单 + */ + public PayOrder selectPayOrderByOrderId(Long orderId); + + /** + * 查询订单 + * + * @param orderNumber 订单号 + * @return 订单集合 + */ + public PayOrder selectPayOrderByOrderNumber(String orderNumber); + + /** + * 查询订单列表 + * + * @param payOrder 订单 + * @return 订单集合 + */ + public List selectPayOrderList(PayOrder payOrder); + + /** + * 新增订单 + * + * @param payOrder 订单 + * @return 结果 + */ + public int insertPayOrder(PayOrder payOrder); + + /** + * 修改订单 + * + * @param payOrder 订单 + * @return 结果 + */ + public int updatePayOrder(PayOrder payOrder); + + /** + * 批量删除订单 + * + * @param orderIds 需要删除的订单主键集合 + * @return 结果 + */ + public int deletePayOrderByOrderIds(Long[] orderIds); + + + /** + * 删除订单信息 + * + * @param orderId 订单主键 + * @return 结果 + */ + public int deletePayOrderByOrderNumber(String orderNumber); + + /** + * 删除订单信息 + * + * @param orderId 订单主键 + * @return 结果 + */ + public int deletePayOrderByOrderId(Long orderId); + public int updateStatus(String orderNumber, String orderStatus); +} diff --git a/boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/service/PayService.java b/boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/service/PayService.java new file mode 100644 index 0000000..7f532ee --- /dev/null +++ b/boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/service/PayService.java @@ -0,0 +1,16 @@ +package com.boyue.pay.service; + +import com.boyue.pay.domain.PayOrder; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public interface PayService { + String payUrl(PayOrder payOrder); + + String notify(HttpServletRequest servletRequest, HttpServletResponse response); + + PayOrder query(PayOrder payOrder); + + PayOrder refund(PayOrder payOrder); +} diff --git a/boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/service/impl/PayInvoiceServiceImpl.java b/boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/service/impl/PayInvoiceServiceImpl.java new file mode 100644 index 0000000..04ae0d4 --- /dev/null +++ b/boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/service/impl/PayInvoiceServiceImpl.java @@ -0,0 +1,98 @@ +package com.boyue.pay.service.impl; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.boyue.common.utils.DateUtils; +import com.boyue.pay.domain.PayInvoice; +import com.boyue.pay.mapper.PayInvoiceMapper; +import com.boyue.pay.service.IPayInvoiceService; + +/** + * 发票Service业务层处理 + * + * @author boyue + * @date 2024-06-11 + */ +@Service +public class PayInvoiceServiceImpl implements IPayInvoiceService +{ + @Autowired + private PayInvoiceMapper payInvoiceMapper; + + /** + * 查询发票 + * + * @param invoiceId 发票主键 + * @return 发票 + */ + @Override + public PayInvoice selectPayInvoiceByInvoiceId(Long invoiceId) + { + return payInvoiceMapper.selectPayInvoiceByInvoiceId(invoiceId); + } + + /** + * 查询发票列表 + * + * @param payInvoice 发票 + * @return 发票 + */ + @Override + public List selectPayInvoiceList(PayInvoice payInvoice) + { + return payInvoiceMapper.selectPayInvoiceList(payInvoice); + } + + /** + * 新增发票 + * + * @param payInvoice 发票 + * @return 结果 + */ + @Override + public int insertPayInvoice(PayInvoice payInvoice) + { + payInvoice.setCreateTime(DateUtils.getNowDate()); + return payInvoiceMapper.insertPayInvoice(payInvoice); + } + + /** + * 修改发票 + * + * @param payInvoice 发票 + * @return 结果 + */ + @Override + public int updatePayInvoice(PayInvoice payInvoice) + { + payInvoice.setUpdateTime(DateUtils.getNowDate()); + return payInvoiceMapper.updatePayInvoice(payInvoice); + } + + /** + * 批量删除发票 + * + * @param invoiceIds 需要删除的发票主键 + * @return 结果 + */ + @Override + public int deletePayInvoiceByInvoiceIds(Long[] invoiceIds) + { + return payInvoiceMapper.deletePayInvoiceByInvoiceIds(invoiceIds); + } + + /** + * 删除发票信息 + * + * @param invoiceId 发票主键 + * @return 结果 + */ + @Override + public int deletePayInvoiceByInvoiceId(Long invoiceId) + { + return payInvoiceMapper.deletePayInvoiceByInvoiceId(invoiceId); + } +} diff --git a/boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/service/impl/PayOrderServiceImpl.java b/boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/service/impl/PayOrderServiceImpl.java new file mode 100644 index 0000000..7bf5aaf --- /dev/null +++ b/boyue-pay/boyue-pay-common/src/main/java/com/boyue/pay/service/impl/PayOrderServiceImpl.java @@ -0,0 +1,117 @@ +package com.boyue.pay.service.impl; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.boyue.common.utils.DateUtils; +import com.boyue.pay.domain.PayOrder; +import com.boyue.pay.mapper.PayOrderMapper; +import com.boyue.pay.service.IPayOrderService; + +/** + * 订单Service业务层处理 + * + * @author boyue + * @date 2024-06-11 + */ +@Service +public class PayOrderServiceImpl implements IPayOrderService { + @Autowired + private PayOrderMapper payOrderMapper; + + /** + * 查询订单 + * + * @param orderId 订单主键 + * @return 订单 + */ + @Override + public PayOrder selectPayOrderByOrderId(Long orderId) { + return payOrderMapper.selectPayOrderByOrderId(orderId); + } + + /** + * 查询订单 + * + * @param orderNumber 订单号 + * @return 订单集合 + */ + @Override + public PayOrder selectPayOrderByOrderNumber(String orderNumber) { + return payOrderMapper.selectPayOrderByOrderNumber(orderNumber); + } + + /** + * 查询订单列表 + * + * @param payOrder 订单 + * @return 订单 + */ + @Override + public List selectPayOrderList(PayOrder payOrder) { + return payOrderMapper.selectPayOrderList(payOrder); + } + + /** + * 新增订单 + * + * @param payOrder 订单 + * @return 结果 + */ + @Override + public int insertPayOrder(PayOrder payOrder) { + return payOrderMapper.insertPayOrder(payOrder); + } + + /** + * 修改订单 + * + * @param payOrder 订单 + * @return 结果 + */ + @Override + public int updatePayOrder(PayOrder payOrder) { + payOrder.setUpdateTime(DateUtils.getNowDate()); + return payOrderMapper.updatePayOrder(payOrder); + } + + /** + * 批量删除订单 + * + * @param orderIds 需要删除的订单主键 + * @return 结果 + */ + @Override + public int deletePayOrderByOrderIds(Long[] orderIds) { + return payOrderMapper.deletePayOrderByOrderIds(orderIds); + } + + /** + * 删除订单信息 + * + * @param orderId 订单主键 + * @return 结果 + */ + @Override + public int deletePayOrderByOrderId(Long orderId) { + return payOrderMapper.deletePayOrderByOrderId(orderId); + } + + /** + * 删除订单信息 + * + * @param orderIds 需要删除的订单主键 + * @return 结果 + */ + @Override + public int deletePayOrderByOrderNumber(String orderNumber) { + return payOrderMapper.deletePayOrderByOrderNumber(orderNumber); + } + + @Override + public int updateStatus(String orderNumber, String orderStatus) { + return payOrderMapper.updateStatus(orderNumber, orderStatus); + } +} diff --git a/boyue-pay/boyue-pay-common/src/main/resources/mapper/pay/PayInvoiceMapper.xml b/boyue-pay/boyue-pay-common/src/main/resources/mapper/pay/PayInvoiceMapper.xml new file mode 100644 index 0000000..ce52464 --- /dev/null +++ b/boyue-pay/boyue-pay-common/src/main/resources/mapper/pay/PayInvoiceMapper.xml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + select invoice_id, order_number, invoice_type, invoice_header, invoice_number, invoice_phone, invoice_email, invoice_remark, create_by, create_time, update_by, update_time, remark from pay_invoice + + + + + + + + insert into pay_invoice + + invoice_id, + order_number, + invoice_type, + invoice_header, + invoice_number, + invoice_phone, + invoice_email, + invoice_remark, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{invoiceId}, + #{orderNumber}, + #{invoiceType}, + #{invoiceHeader}, + #{invoiceNumber}, + #{invoicePhone}, + #{invoiceEmail}, + #{invoiceRemark}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update pay_invoice + + order_number = #{orderNumber}, + invoice_type = #{invoiceType}, + invoice_header = #{invoiceHeader}, + invoice_number = #{invoiceNumber}, + invoice_phone = #{invoicePhone}, + invoice_email = #{invoiceEmail}, + invoice_remark = #{invoiceRemark}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where pay_invoice.invoice_id = #{invoiceId} + + + + delete from pay_invoice where invoice_id = #{invoiceId} + + + + delete from pay_invoice where invoice_id in + + #{invoiceId} + + + \ No newline at end of file diff --git a/boyue-pay/boyue-pay-common/src/main/resources/mapper/pay/PayOrderMapper.xml b/boyue-pay/boyue-pay-common/src/main/resources/mapper/pay/PayOrderMapper.xml new file mode 100644 index 0000000..128bb9f --- /dev/null +++ b/boyue-pay/boyue-pay-common/src/main/resources/mapper/pay/PayOrderMapper.xml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + select order_id, order_number, order_status, total_amount, actual_amount, order_content, order_message, create_by, create_time, update_by, update_time, remark from pay_order + + + + + + + + + + insert into pay_order + + order_number, + third_number, + order_status, + total_amount, + actual_amount, + order_content, + order_message, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{orderNumber}, + #{thirdNumber}, + #{orderStatus}, + #{totalAmount}, + #{actualAmount}, + #{orderContent}, + #{orderMessage}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update pay_order + + order_number = #{orderNumber}, + third_number = #{thirdNumber}, + order_status = #{orderStatus}, + total_amount = #{totalAmount}, + actual_amount = #{actualAmount}, + order_content = #{orderContent}, + order_message = #{orderMessage}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where pay_order.order_id = #{orderId} + + + + delete from pay_order where order_id = #{orderId} + + + + delete from pay_order where order_number= #{orderNumber} + + + + delete from pay_order where order_id in + + #{orderId} + + + + + update pay_order set order_status = #{orderStatus} where order_number = #{orderNumber} + + \ No newline at end of file diff --git a/boyue-pay/boyue-pay-sqb/pom.xml b/boyue-pay/boyue-pay-sqb/pom.xml new file mode 100644 index 0000000..949f3eb --- /dev/null +++ b/boyue-pay/boyue-pay-sqb/pom.xml @@ -0,0 +1,27 @@ + + + + boyue-pay + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-pay-sqb + + + 收钱吧支付模块 + + + + + + com.boyue + boyue-pay-common + + + + + \ No newline at end of file diff --git a/boyue-pay/boyue-pay-sqb/src/main/java/com/boyue/pay/sqb/config/SqbConfig.java b/boyue-pay/boyue-pay-sqb/src/main/java/com/boyue/pay/sqb/config/SqbConfig.java new file mode 100644 index 0000000..ac5d2cb --- /dev/null +++ b/boyue-pay/boyue-pay-sqb/src/main/java/com/boyue/pay/sqb/config/SqbConfig.java @@ -0,0 +1,116 @@ +package com.boyue.pay.sqb.config; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Component; + +@Component +@ConditionalOnProperty(prefix = "pay.sqb", name = "enabled", havingValue = "true") +public class SqbConfig { + @Value("${pay.sqb.apiDomain}") + private String apiDomain; + + @Value("${pay.sqb.terminalSn}") + private String terminalSn; + + @Value("${pay.sqb.terminalKey}") + private String terminalKey; + + @Value("${pay.sqb.appId}") + private String appId; + + @Value("${pay.sqb.vendorSn}") + private String vendorSn; + + @Value("${pay.sqb.vendorKey}") + private String vendorKey; + + @Value("${pay.sqb.notifyUrl}") + private String notifyUrl; + + @Value("${pay.sqb.publicKey}") + private String publicKey; + + @Autowired + private ApplicationContext applicationContext; + + public String getPublicKey() throws Exception { + if (publicKey.startsWith("classpath")) { + Resource resource = applicationContext.getResource(publicKey); + InputStream inputStream = resource.getInputStream(); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + String alipayPublicKeyValue = bufferedReader.lines().collect(Collectors.joining(System.lineSeparator())); + bufferedReader.close(); + publicKey = alipayPublicKeyValue; + } + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getNotifyUrl() { + return notifyUrl; + } + + public void setNotifyUrl(String notifyUrl) { + this.notifyUrl = notifyUrl; + } + + public String getApiDomain() { + return apiDomain; + } + + public void setApiDomain(String apiDomain) { + this.apiDomain = apiDomain; + } + + public String getTerminalSn() { + return terminalSn; + } + + public void setTerminalSn(String terminalSn) { + this.terminalSn = terminalSn; + } + + public String getTerminalKey() { + return terminalKey; + } + + public void setTerminalKey(String terminalKey) { + this.terminalKey = terminalKey; + } + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getVendorSn() { + return vendorSn; + } + + public void setVendorSn(String vendorSn) { + this.vendorSn = vendorSn; + } + + public String getVendorKey() { + return vendorKey; + } + + public void setVendorKey(String vendorKey) { + this.vendorKey = vendorKey; + } +} diff --git a/boyue-pay/boyue-pay-sqb/src/main/java/com/boyue/pay/sqb/service/ISqbPayService.java b/boyue-pay/boyue-pay-sqb/src/main/java/com/boyue/pay/sqb/service/ISqbPayService.java new file mode 100644 index 0000000..2d97ae0 --- /dev/null +++ b/boyue-pay/boyue-pay-sqb/src/main/java/com/boyue/pay/sqb/service/ISqbPayService.java @@ -0,0 +1,6 @@ +package com.boyue.pay.sqb.service; + +import com.boyue.pay.service.PayService; + +public interface ISqbPayService extends PayService { +} diff --git a/boyue-pay/boyue-pay-sqb/src/main/java/com/boyue/pay/sqb/service/Impl/SQBServiceImpl.java b/boyue-pay/boyue-pay-sqb/src/main/java/com/boyue/pay/sqb/service/Impl/SQBServiceImpl.java new file mode 100644 index 0000000..17e22da --- /dev/null +++ b/boyue-pay/boyue-pay-sqb/src/main/java/com/boyue/pay/sqb/service/Impl/SQBServiceImpl.java @@ -0,0 +1,333 @@ +package com.boyue.pay.sqb.service.Impl; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.security.KeyFactory; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.UnrecoverableKeyException; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; +import org.springframework.util.StreamUtils; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.boyue.common.exception.ServiceException; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.common.utils.http.HttpClientUtil; +import com.boyue.common.utils.sign.Md5Utils; +import com.boyue.pay.domain.PayOrder; +import com.boyue.pay.service.IPayOrderService; +import com.boyue.pay.sqb.config.SqbConfig; +import com.boyue.pay.sqb.service.ISqbPayService; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service("pay:service:sqb") +@ConditionalOnProperty(prefix = "pay.sqb", name = "enabled", havingValue = "true") +public class SQBServiceImpl implements ISqbPayService { + @Autowired + private SqbConfig sqbConfig; + + @Autowired + private IPayOrderService payOrderService; + + /** + * http POST 请求 + * + * @param url:请求地址 + * @param body: body实体字符串 + * @param sign:签名 + * @param sn: 序列号 + * @return + */ + public static String httpPost(String url, Object body, String sign, String sn) + throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + String xmlRes = "{}"; + try { + Map header = new HashMap<>(); + header.put("Authorization", sn + " " + sign); + xmlRes = HttpClientUtil.sendHttpPost(url, body, header); + } catch (Exception e) { + } + return xmlRes; + } + + /** + * 计算字符串的MD5值 + * + * @param signStr:签名字符串 + * @return + */ + private String getSign(String signStr) { + try { + String md5 = Md5Utils.encryptMd5(signStr); + return md5; + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + return null; + } + } + + /** + * 终端激活 + * + * @param code:激活码 + * @return {terminal_sn:"$终端号",terminal_key:"$终端密钥"} + */ + public JSONObject activate(String code, String deviceId, String clientSn, String name) { + String url = sqbConfig.getApiDomain() + "/terminal/activate"; + JSONObject params = new JSONObject(); + try { + params.put("app_id", sqbConfig.getAppId()); // app_id,必填 + params.put("code", code); // 激活码,必填 + params.put("device_id", deviceId); // 客户方收银终端序列号,需保证同一app_id下唯一,必填。为方便识别,建议格式为“品牌名+门店编号+‘POS’+POS编号“ + params.put("client_sn", clientSn); // 客户方终端编号,一般客户方或系统给收银终端的编号,必填 + params.put("name", name); // 客户方终端名称,必填 + String sign = getSign(params.toString() + sqbConfig.getVendorKey()); + String result = httpPost(url, params.toString(), sign, sqbConfig.getVendorSn()); + JSONObject retObj = JSON.parseObject(result); + String resCode = retObj.get("result_code").toString(); + if (!"200".equals(resCode)) { + return null; + } + String responseStr = retObj.get("biz_response").toString(); + JSONObject terminal = JSON.parseObject(responseStr); + if (terminal.get("terminal_sn") == null || terminal.get("terminal_key") == null) { + return null; + } + return terminal; + } catch (Exception e) { + return null; + } + } + + /** + * 终端签到 + * + * @return {terminal_sn:"$终端号",terminal_key:"$终端密钥"} + */ + public JSONObject checkin() { + String url = sqbConfig.getApiDomain() + "/terminal/checkin"; + JSONObject params = new JSONObject(); + try { + params.put("terminal_sn", sqbConfig.getTerminalSn()); + params.put("device_id", "HUISUAN001POS01"); + params.put("os_info", "Mac OS"); + params.put("sdk_version", "Java SDK v1.0"); + String sign = getSign(params.toString() + sqbConfig.getTerminalKey()); + String result = httpPost(url, params.toString(), sign, sqbConfig.getTerminalSn()); + JSONObject retObj = JSON.parseObject(result); + String resCode = retObj.get("result_code").toString(); + if (!"200".equals(resCode)) { + return null; + } + String responseStr = retObj.get("biz_response").toString(); + JSONObject terminal = JSON.parseObject(responseStr); + if (terminal.get("terminal_sn") == null || terminal.get("terminal_key") == null) { + return null; + } + return terminal; + } catch (Exception e) { + return null; + } + } + + /** + * 退款 + * + * @return + */ + public PayOrder refund(PayOrder payOrder) { + String url = sqbConfig.getApiDomain() + "/upay/v2/refund"; + JSONObject params = new JSONObject(); + try { + params.put("terminal_sn", sqbConfig.getTerminalSn()); // 收钱吧终端ID + params.put("client_sn", payOrder.getOrderNumber()); // 商户系统订单号,必须在商户系统内唯一;且长度不超过64字节 + params.put("refund_amount", payOrder.getTotalAmount()); // 退款金额 + params.put("refund_request_no", "1"); // 商户退款所需序列号,表明是第几次退款 + params.put("operator", "kay"); // 门店操作员 + + String sign = getSign(params.toString() + sqbConfig.getTerminalKey()); + String result = httpPost(url, params, sign, sqbConfig.getTerminalSn()); + JSONObject retObj = JSON.parseObject(result); + JSONObject bizResponse = retObj.getJSONObject("biz_response"); + if ("REFUNDED".equals(bizResponse.getString("order_status"))) { + payOrderService.updateStatus(payOrder.getOrderNumber(), "已退款"); + } else { + log.error("退款失败"); + } + return payOrder; + } catch (Exception e) { + return null; + } + } + + /** + * 查询 + * + * @return + */ + @Override + public PayOrder query(PayOrder payOrder) { + String url = sqbConfig.getApiDomain() + "/upay/v2/query"; + JSONObject params = new JSONObject(); + try { + params.put("terminal_sn", sqbConfig.getTerminalSn()); // 终端号 + params.put("client_sn", payOrder.getOrderNumber()); // 商户系统订单号,必须在商户系统内唯一;且长度不超过64字节 + System.out.println(params.toString() + sqbConfig.getTerminalKey()); + String sign = getSign(params.toString() + sqbConfig.getTerminalKey()); + String result = httpPost(url, params, sign, sqbConfig.getTerminalSn()); + JSONObject retObj = JSON.parseObject(result); + String resCode = retObj.get("result_code").toString(); + if (!"200".equals(resCode)) { + throw new ServiceException("查询支付订单失败"); + } else { + JSONObject response = retObj.getJSONObject("biz_response"); + System.out.println(response); + } + return payOrder; + } catch (Exception e) { + return null; + } + } + + @Override + public String payUrl(PayOrder payOrder) { + return payUrl(payOrder, null); + } + + public String payUrl(PayOrder payOrder, String notifyBaseUrl) { + if (payOrder.getRemark() == null) { + payOrder.setRemark("支付"); + } + String orderNotifyUrl = sqbConfig.getNotifyUrl(); + String param = "" + + "client_sn=" + payOrder.getOrderNumber() + + "¬ify_url=" + orderNotifyUrl + + "&operator=" + payOrder.getCreateBy() + + "&return_url=" + "https://www.shouqianba.com/" + + "&subject=" + payOrder.getRemark() + + "&terminal_sn=" + sqbConfig.getTerminalSn() + + "&total_amount=" + Long.valueOf(payOrder.getTotalAmount().toString()); + String urlParam; + try { + urlParam = "" + + "client_sn=" + payOrder.getOrderNumber() + + "¬ify_url=" + URLEncoder.encode(orderNotifyUrl, "UTF-8") + + "&operator=" + URLEncoder.encode(payOrder.getCreateBy(), "UTF-8") + + "&return_url=" + "https://www.shouqianba.com/" + + "&subject=" + URLEncoder.encode(payOrder.getRemark(), "UTF-8") + + "&terminal_sn=" + sqbConfig.getTerminalSn() + + "&total_amount=" + Long.valueOf(payOrder.getTotalAmount().toString()); + + String sign = getSign(param + "&key=" + sqbConfig.getTerminalKey()); + return "https://qr.shouqianba.com/gateway?" + urlParam + "&sign=" + sign.toUpperCase(); + } catch (Exception e) { + throw new ServiceException("生成收钱吧支付链接失败"); + } + } + + /** + * 预下单 + * + * @return + */ + public String precreate(PayOrder payOrder, String sn, String payway) { + String url = sqbConfig.getApiDomain() + "/upay/v2/precreate"; + JSONObject params = new JSONObject(); + try { + params.put("terminal_sn", sqbConfig.getTerminalSn()); // 收钱吧终端ID + params.put("client_sn", payOrder.getOrderNumber()); // 商户系统订单号,必须在商户系统内唯一;且长度不超过32字节 + params.put("total_amount", payOrder.getTotalAmount()); // 交易总金额 + // params.put("payway", payway); // 支付方式 + params.put("subject", "无简介"); // 交易简介 + params.put("operator", SecurityUtils.getUsername()); // 门店操作员 + + String sign = getSign(params.toString() + sqbConfig.getTerminalKey()); + String result = httpPost(url, params.toString(), sign, sqbConfig.getTerminalSn()); + return result; + } catch (Exception e) { + return null; + } + } + + /** + * 自动撤单 + * + * @param terminal_sn:终端号 + * @param terminal_key:终端密钥 + * @return + */ + public String cancel(String terminal_sn, String terminal_key) { + String url = sqbConfig.getApiDomain() + "/upay/v2/cancel"; + JSONObject params = new JSONObject(); + try { + params.put("terminal_sn", terminal_sn); // 终端号 + params.put("client_sn", "18348290098298292838"); // 商户系统订单号,必须在商户系统内唯一;且长度不超过64字节 + + String sign = getSign(params.toString() + terminal_key); + String result = httpPost(url, params.toString(), sign, terminal_sn); + + return result; + } catch (Exception e) { + return null; + } + } + + public boolean validateSign(String data, String sign) { + try { + // 使用SHA256WithRSA算法 + Signature signature = Signature.getInstance("SHA256WithRSA"); + + // 获取公钥 + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PublicKey localPublicKey = keyFactory + .generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(sqbConfig.getPublicKey()))); + // 初始化验证过程 + signature.initVerify(localPublicKey); + signature.update(data.getBytes()); + + // 解码签名 + byte[] bytesSign = Base64.getDecoder().decode(sign); + + // 验证签名 + return signature.verify(bytesSign); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + @Override + public String notify(HttpServletRequest request, HttpServletResponse response) { + try { + String requestBody = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8); + JSONObject jsonObject = JSONObject.parseObject(requestBody); + String sign = request.getHeader("Authorization"); + if (!validateSign(requestBody, sign)) { + throw new ServiceException("收钱吧支付回调验签失败"); + } + System.out.println(jsonObject); + return "success"; + } catch (IOException e) { + e.printStackTrace(); + return "fail"; + } + } + +} diff --git a/boyue-pay/boyue-pay-sqb/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/boyue-pay/boyue-pay-sqb/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000..cf85b11 --- /dev/null +++ b/boyue-pay/boyue-pay-sqb/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,49 @@ +{ + "properties": [ + { + "name": "pay.sqb.enabled", + "type": "java.lang.Boolean", + "description": "启用收钱吧支付" + }, + { + "name": "pay.sqb.appId", + "type": "java.lang.String", + "description": "收钱吧appId" + }, + { + "name": "pay.sqb.apiDomain", + "type": "java.lang.String", + "description": "收钱吧apiDomain" + }, + { + "name": "pay.sqb.terminalSn", + "type": "java.lang.String", + "description": "收钱吧terminalSn" + }, + { + "name": "pay.sqb.terminalKey", + "type": "java.lang.String", + "description": "收钱吧terminalKey" + }, + { + "name": "pay.sqb.vendorSn", + "type": "java.lang.String", + "description": "收钱吧vendorSn" + }, + { + "name": "pay.sqb.vendorKey", + "type": "java.lang.String", + "description": "收钱吧vendorKey" + }, + { + "name": "pay.sqb.publicKey", + "type": "java.lang.String", + "description": "收钱吧公钥,可以直接用字符串,也可以是基于classpath的文件路径" + }, + { + "name": "pay.sqb.notifyUrl", + "type": "java.lang.String", + "description": "收钱吧支付回调地址" + } + ] +} \ No newline at end of file diff --git a/boyue-pay/boyue-pay-starter/pom.xml b/boyue-pay/boyue-pay-starter/pom.xml new file mode 100644 index 0000000..8a2a69c --- /dev/null +++ b/boyue-pay/boyue-pay-starter/pom.xml @@ -0,0 +1,46 @@ + + + + boyue-pay + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-pay-starter + + + 第三方支付模块 + + + + + + com.boyue + boyue-pay-common + + + + + com.boyue + boyue-pay-sqb + + + + + com.boyue + boyue-pay-alipay + + + + + com.boyue + boyue-pay-wx + + + + + + \ No newline at end of file diff --git a/boyue-pay/boyue-pay-starter/src/main/java/com/boyue/pay/controller/PayController.java b/boyue-pay/boyue-pay-starter/src/main/java/com/boyue/pay/controller/PayController.java new file mode 100644 index 0000000..06e3732 --- /dev/null +++ b/boyue-pay/boyue-pay-starter/src/main/java/com/boyue/pay/controller/PayController.java @@ -0,0 +1,98 @@ +package com.boyue.pay.controller; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.annotation.Anonymous; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.pay.domain.PayOrder; +import com.boyue.pay.service.IPayOrderService; +import com.boyue.pay.service.PayService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.PostConstruct; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Tag(name = "支付业务") +@RequestMapping("/pay/{channel}") +@RestController +public class PayController extends BaseController { + + @Autowired(required = false) + private Map payServiceMap; // alipay wechat sqb + + @PostConstruct + public void init() { + if (payServiceMap == null) { + payServiceMap = new HashMap<>(); + logger.warn("请注意,没有加载任何支付服务"); + } else { + payServiceMap.forEach((k, v) -> { + logger.info("已加载支付服务 {}", k); + }); + } + } + + @Autowired + private IPayOrderService payOrderService; + + @Operation(summary = "支付") + @Parameters({ + @Parameter(name = "channel", description = "支付方式", required = true), + @Parameter(name = "orderNumber", description = "订单号", required = true) + }) + @GetMapping("/url/{orderNumber}") + public AjaxResult url(@PathVariable String channel, @PathVariable String orderNumber) throws Exception { + PayService payService = payServiceMap.get("pay:service:" + channel ); + PayOrder payOrder = payOrderService.selectPayOrderByOrderNumber(orderNumber); + return success(payService.payUrl(payOrder)); + } + + @Anonymous + @Operation(summary = "支付查询订单") + @Parameters({ + @Parameter(name = "channel", description = "支付方式", required = true) + }) + @PostMapping("/notify") + public String notify(@PathVariable String channel, HttpServletRequest request, HttpServletResponse response) + throws Exception { + PayService payService = payServiceMap.get("pay:service:" + channel ); + return payService.notify(request, response); + } + + @Operation(summary = "查询支付状态") + @Parameters({ + @Parameter(name = "channel", description = "支付方式", required = true), + @Parameter(name = "orderNumber", description = "订单号", required = true) + }) + @PostMapping("/query/{orderNumber}") + public AjaxResult query(@PathVariable String channel, @PathVariable(name = "orderNumber") String orderNumber) + throws Exception { + PayService payService = payServiceMap.get("pay:service:" + channel ); + PayOrder payOrder = payOrderService.selectPayOrderByOrderNumber(orderNumber); + return success(payService.query(payOrder)); + } + + @Operation(summary = "退款") + @PostMapping("/refund") + @Parameters({ + @Parameter(name = "channel", description = "支付方式", required = true), + }) + public AjaxResult refund(@PathVariable String channel, @RequestBody PayOrder payOrder) { + PayService payService = payServiceMap.get("pay:service:" + channel ); + return success(payService.refund(payOrder)); + } +} diff --git a/boyue-pay/boyue-pay-starter/src/main/java/com/boyue/pay/controller/PayInvoiceController.java b/boyue-pay/boyue-pay-starter/src/main/java/com/boyue/pay/controller/PayInvoiceController.java new file mode 100644 index 0000000..de951bf --- /dev/null +++ b/boyue-pay/boyue-pay-starter/src/main/java/com/boyue/pay/controller/PayInvoiceController.java @@ -0,0 +1,116 @@ +package com.boyue.pay.controller; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.enums.BusinessType; +import com.boyue.common.utils.poi.ExcelUtil; +import com.boyue.pay.domain.PayInvoice; +import com.boyue.pay.service.IPayInvoiceService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 发票Controller + * + * @author boyue + * @date 2024-06-11 + */ +@RestController +@RequestMapping("/pay/invoice") +@Tag(name = "【发票】管理") +public class PayInvoiceController extends BaseController +{ + @Autowired + private IPayInvoiceService payInvoiceService; + + /** + * 查询发票列表 + */ + @Operation(summary = "查询发票列表") + @PreAuthorize("@ss.hasPermi('pay:invoice:list')") + @GetMapping("/list") + public TableDataInfo list(PayInvoice payInvoice) + { + startPage(); + List list = payInvoiceService.selectPayInvoiceList(payInvoice); + return getDataTable(list); + } + + /** + * 导出发票列表 + */ + @Operation(summary = "导出发票列表") + @PreAuthorize("@ss.hasPermi('pay:invoice:export')") + @Log(title = "发票", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, PayInvoice payInvoice) + { + List list = payInvoiceService.selectPayInvoiceList(payInvoice); + ExcelUtil util = new ExcelUtil(PayInvoice.class); + util.exportExcel(response, list, "发票数据"); + } + + /** + * 获取发票详细信息 + */ + @Operation(summary = "获取发票详细信息") + @PreAuthorize("@ss.hasPermi('pay:invoice:query')") + @GetMapping(value = "/{invoiceId}") + public AjaxResult getInfo(@PathVariable("invoiceId") Long invoiceId) + { + return success(payInvoiceService.selectPayInvoiceByInvoiceId(invoiceId)); + } + + /** + * 新增发票 + */ + @Operation(summary = "新增发票") + @PreAuthorize("@ss.hasPermi('pay:invoice:add')") + @Log(title = "发票", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody PayInvoice payInvoice) + { + return toAjax(payInvoiceService.insertPayInvoice(payInvoice)); + } + + /** + * 修改发票 + */ + @Operation(summary = "修改发票") + @PreAuthorize("@ss.hasPermi('pay:invoice:edit')") + @Log(title = "发票", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody PayInvoice payInvoice) + { + return toAjax(payInvoiceService.updatePayInvoice(payInvoice)); + } + + /** + * 删除发票 + */ + @Operation(summary = "删除发票") + @PreAuthorize("@ss.hasPermi('pay:invoice:remove')") + @Log(title = "发票", businessType = BusinessType.DELETE) + @DeleteMapping("/{invoiceIds}") + public AjaxResult remove(@PathVariable( name = "invoiceIds" ) Long[] invoiceIds) + { + return toAjax(payInvoiceService.deletePayInvoiceByInvoiceIds(invoiceIds)); + } +} diff --git a/boyue-pay/boyue-pay-starter/src/main/java/com/boyue/pay/controller/PayOrderController.java b/boyue-pay/boyue-pay-starter/src/main/java/com/boyue/pay/controller/PayOrderController.java new file mode 100644 index 0000000..33286b9 --- /dev/null +++ b/boyue-pay/boyue-pay-starter/src/main/java/com/boyue/pay/controller/PayOrderController.java @@ -0,0 +1,133 @@ +package com.boyue.pay.controller; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.enums.BusinessType; +import com.boyue.common.utils.poi.ExcelUtil; +import com.boyue.common.utils.uuid.Seq; +import com.boyue.pay.domain.PayOrder; +import com.boyue.pay.service.IPayOrderService; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 订单Controller + * + * @author Dftre + * @date 2024-02-15 + */ +@RestController +@RequestMapping("/pay/order") +@Tag(name = "【订单】管理") +public class PayOrderController extends BaseController +{ + @Autowired + private IPayOrderService payOrderService; + + /** + * 查询订单列表 + */ + @Operation(summary = "查询订单列表") + @PreAuthorize("@ss.hasPermi('pay:order:list')") + @GetMapping("/list") + public TableDataInfo list(PayOrder payOrder) + { + startPage(); + List list = payOrderService.selectPayOrderList(payOrder); + return getDataTable(list); + } + + /** + * 导出订单列表 + */ + @Operation(summary = "导出订单列表") + @PreAuthorize("@ss.hasPermi('pay:order:export')") + @Log(title = "订单", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, PayOrder payOrder) + { + List list = payOrderService.selectPayOrderList(payOrder); + ExcelUtil util = new ExcelUtil(PayOrder.class); + util.exportExcel(response, list, "订单数据"); + } + + /** + * 获取订单详细信息 + */ + @Operation(summary = "获取订单详细信息") + @PreAuthorize("@ss.hasPermi('pay:order:query')") + @GetMapping(value = "/{orderId}") + public AjaxResult getInfo(@PathVariable("orderId") Long orderId) + { + return success(payOrderService.selectPayOrderByOrderId(orderId)); + } + + /** + * 新增订单 + */ + @Operation(summary = "新增订单") + @PreAuthorize("@ss.hasPermi('pay:order:add')") + @Log(title = "订单", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody PayOrder payOrder) + { + payOrder.setCreateBy(getUsername()); + payOrder.setOrderNumber(Seq.getId().toString()); + AjaxResult result = toAjax(payOrderService.insertPayOrder(payOrder)); + result.put(AjaxResult.DATA_TAG, payOrder); + return result; + } + + /** + * 修改订单 + */ + @Operation(summary = "修改订单") + @PreAuthorize("@ss.hasPermi('pay:order:edit')") + @Log(title = "订单", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody PayOrder payOrder) + { + return toAjax(payOrderService.updatePayOrder(payOrder)); + } + + /** + * 删除订单 + */ + @Operation(summary = "通过订单号删除订单") + @PreAuthorize("@ss.hasPermi('pay:order:remove')") + @Log(title = "订单", businessType = BusinessType.DELETE) + @DeleteMapping("/orderNumber/{orderNumber}") + public AjaxResult removeByOrderNumber(@PathVariable( name = "orderNumber" ) String orderNumbers) + { + return toAjax(payOrderService.deletePayOrderByOrderNumber(orderNumbers)); + } + + /** + * 删除订单 + */ + @Operation(summary = "删除订单") + @PreAuthorize("@ss.hasPermi('pay:order:remove')") + @Log(title = "订单", businessType = BusinessType.DELETE) + @DeleteMapping("/{orderIds}") + public AjaxResult remove(@PathVariable( name = "orderIds" ) Long[] orderIds) + { + return toAjax(payOrderService.deletePayOrderByOrderIds(orderIds)); + } +} diff --git a/boyue-pay/boyue-pay-wx/pom.xml b/boyue-pay/boyue-pay-wx/pom.xml new file mode 100644 index 0000000..fb36e8e --- /dev/null +++ b/boyue-pay/boyue-pay-wx/pom.xml @@ -0,0 +1,31 @@ + + + + boyue-pay + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-pay-wx + + + 微信支付模块 + + + + + + com.boyue + boyue-pay-common + + + + com.github.wechatpay-apiv3 + wechatpay-java + + + + + \ No newline at end of file diff --git a/boyue-pay/boyue-pay-wx/src/main/java/com/boyue/pay/wx/config/WxPayConfig.java b/boyue-pay/boyue-pay-wx/src/main/java/com/boyue/pay/wx/config/WxPayConfig.java new file mode 100644 index 0000000..b354dbd --- /dev/null +++ b/boyue-pay/boyue-pay-wx/src/main/java/com/boyue/pay/wx/config/WxPayConfig.java @@ -0,0 +1,139 @@ +package com.boyue.pay.wx.config; + +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; + +import com.wechat.pay.java.core.RSAAutoCertificateConfig; +import com.wechat.pay.java.core.notification.NotificationParser; +import com.wechat.pay.java.service.payments.nativepay.NativePayService; +import com.wechat.pay.java.service.refund.RefundService; + +/** + * 配置我们自己的信息 + * + * @author ZlH + */ +@Configuration +@ConditionalOnProperty(prefix = "pay.wechat", name = "enabled", havingValue = "true") +public class WxPayConfig { + + /** 商户号 */ + @Value("${pay.wechat.merchantId}") + private String merchantId; + + /** 商户证书序列号 */ + @Value("${pay.wechat.merchantSerialNumber}") + private String merchantSerialNumber; + + /** 商户APIV3密钥 */ + @Value("${pay.wechat.apiV3Key}") + private String apiV3Key; + + /** 商户API私钥路径 */ + @Value("${pay.wechat.privateKeyPath}") + private String privateKeyPath; + + @Value("${pay.wechat.appId}") + private String appId; + + @Value("${pay.wechat.notifyUrl}") + private String notifyUrl; + + public String getMerchantId() { + return merchantId; + } + + public void setMerchantId(String merchantId) { + this.merchantId = merchantId; + } + + public String getMerchantSerialNumber() { + return merchantSerialNumber; + } + + public void setMerchantSerialNumber(String merchantSerialNumber) { + this.merchantSerialNumber = merchantSerialNumber; + } + + public String getApiV3Key() { + return apiV3Key; + } + + public void setApiV3Key(String apiV3Key) { + this.apiV3Key = apiV3Key; + } + + public void setPrivateKeyPath(String privateKeyPath) { + this.privateKeyPath = privateKeyPath; + } + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getNotifyUrl() { + return notifyUrl; + } + + public void setNotifyUrl(String notifyUrl) { + this.notifyUrl = notifyUrl; + } + + @Bean + public RSAAutoCertificateConfig wxpayBaseConfig() throws Exception { + return new RSAAutoCertificateConfig.Builder() + .merchantId(getMerchantId()) + .privateKeyFromPath(getPrivateKeyPath()) + .merchantSerialNumber(getMerchantSerialNumber()) + .apiV3Key(getApiV3Key()) + .build(); + } + + @Bean + public NativePayService nativePayService() throws Exception { + return new NativePayService.Builder().config(wxpayBaseConfig()).build(); + } + + @Bean + public RefundService refundService() throws Exception { + return new RefundService.Builder().config(wxpayBaseConfig()).build(); + } + + @Bean + public NotificationParser notificationParser() throws Exception { + return new NotificationParser(wxpayBaseConfig()); + } + + @Autowired + private ApplicationContext applicationContext; + + public String getPrivateKeyPath() throws Exception { + if (privateKeyPath.startsWith("classpath:")) { + Resource resource = applicationContext.getResource(privateKeyPath); + String tempFilePath = System.getProperty("java.io.tmpdir") + "/temp_wxcert.pem"; + try (InputStream inputStream = resource.getInputStream()) { + Files.copy(inputStream, Paths.get(tempFilePath), StandardCopyOption.REPLACE_EXISTING); + privateKeyPath = tempFilePath; + } catch (Exception e) { + Files.deleteIfExists(Paths.get(tempFilePath)); + throw new RuntimeException("微信支付证书文件读取失败", e); + } + } + return privateKeyPath; + } + +} diff --git a/boyue-pay/boyue-pay-wx/src/main/java/com/boyue/pay/wx/service/IWxPayService.java b/boyue-pay/boyue-pay-wx/src/main/java/com/boyue/pay/wx/service/IWxPayService.java new file mode 100644 index 0000000..f61b0fb --- /dev/null +++ b/boyue-pay/boyue-pay-wx/src/main/java/com/boyue/pay/wx/service/IWxPayService.java @@ -0,0 +1,6 @@ +package com.boyue.pay.wx.service; + +import com.boyue.pay.service.PayService; + +public interface IWxPayService extends PayService { +} diff --git a/boyue-pay/boyue-pay-wx/src/main/java/com/boyue/pay/wx/service/Impl/WxPayService.java b/boyue-pay/boyue-pay-wx/src/main/java/com/boyue/pay/wx/service/Impl/WxPayService.java new file mode 100644 index 0000000..0ac22bd --- /dev/null +++ b/boyue-pay/boyue-pay-wx/src/main/java/com/boyue/pay/wx/service/Impl/WxPayService.java @@ -0,0 +1,124 @@ +package com.boyue.pay.wx.service.Impl; + +import java.nio.charset.StandardCharsets; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; +import org.springframework.util.StreamUtils; + +import com.boyue.pay.domain.PayOrder; +import com.boyue.pay.service.IPayOrderService; +import com.boyue.pay.wx.config.WxPayConfig; +import com.boyue.pay.wx.service.IWxPayService; +import com.wechat.pay.java.core.exception.ServiceException; +import com.wechat.pay.java.core.notification.NotificationParser; +import com.wechat.pay.java.core.notification.RequestParam; +import com.wechat.pay.java.service.payments.model.Transaction; +import com.wechat.pay.java.service.payments.model.Transaction.TradeStateEnum; +import com.wechat.pay.java.service.payments.nativepay.NativePayService; +import com.wechat.pay.java.service.payments.nativepay.model.Amount; +import com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest; +import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse; +import com.wechat.pay.java.service.payments.nativepay.model.QueryOrderByIdRequest; +import com.wechat.pay.java.service.refund.RefundService; +import com.wechat.pay.java.service.refund.model.CreateRequest; +import com.wechat.pay.java.service.refund.model.Refund; +import com.wechat.pay.java.service.refund.model.Status; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Service("pay:service:wechat") +@ConditionalOnProperty(prefix = "pay.wechat", name = "enabled", havingValue = "true") +public class WxPayService implements IWxPayService { + + @Autowired + private IPayOrderService payOrderService; + + @Autowired + private NativePayService nativePayService; + + @Autowired + private WxPayConfig wxPayAppConfig; + + @Autowired + private NotificationParser notificationParser; + + @Autowired + private RefundService refundService; + + @Override + public String payUrl(PayOrder payOrder) { + PrepayRequest request = new PrepayRequest(); + Amount amount = new Amount(); + amount.setTotal(Integer.parseInt(payOrder.getActualAmount())); + request.setAmount(amount); + request.setAppid(wxPayAppConfig.getAppId()); + request.setMchid(wxPayAppConfig.getMerchantId()); + request.setDescription(payOrder.getOrderContent()); + request.setNotifyUrl(wxPayAppConfig.getNotifyUrl()); + request.setOutTradeNo(payOrder.getOrderNumber()); + PrepayResponse response = nativePayService.prepay(request); + return response.getCodeUrl(); + } + + @Override + public String notify(HttpServletRequest request, HttpServletResponse response) { + String timeStamp = request.getHeader("Wechatpay-Timestamp"); + String nonce = request.getHeader("Wechatpay-Nonce"); + String signature = request.getHeader("Wechatpay-Signature"); + String certSn = request.getHeader("Wechatpay-Serial"); + try { + String requestBody = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8); + RequestParam requestParam = new RequestParam.Builder() + .serialNumber(certSn) + .nonce(nonce) + .signature(signature) + .timestamp(timeStamp) + .body(requestBody) + .build(); + Transaction transaction = notificationParser.parse(requestParam, Transaction.class); + String orderNumber = transaction.getOutTradeNo(); + String otherOrderNumber = transaction.getTransactionId(); + TradeStateEnum orderState = transaction.getTradeState(); + System.out.println("orderNumber: " + orderNumber); + System.out.println("otherOrderNumber: " + otherOrderNumber); + System.out.println("orderState: " + orderState); + return "success"; + } catch (Exception e) { + e.printStackTrace(); + return "fail"; + } + } + + @Override + public PayOrder query(PayOrder payOrder) { + QueryOrderByIdRequest queryRequest = new QueryOrderByIdRequest(); + queryRequest.setMchid(wxPayAppConfig.getMerchantId()); + queryRequest.setTransactionId(payOrder.getOrderNumber()); + try { + Transaction result = nativePayService.queryOrderById(queryRequest); + System.out.println(result.getTradeState()); + } catch (ServiceException e) { + System.out.printf("code=[%s], message=[%s]\n", e.getErrorCode(), e.getErrorMessage()); + System.out.printf("reponse body=[%s]\n", e.getResponseBody()); + } + return payOrder; + } + + @Override + public PayOrder refund(PayOrder payOrder) { + CreateRequest request = new CreateRequest(); + request.setTransactionId(payOrder.getOrderNumber()); + request.setOutRefundNo(payOrder.getOrderNumber()); + request.setOutTradeNo(payOrder.getOrderNumber()); + Refund refund = refundService.create(request); + Status status = refund.getStatus(); + if (status.equals(Status.SUCCESS)) { + payOrderService.updateStatus(payOrder.getOrderNumber(), "已退款"); + } + return payOrder; + } + +} diff --git a/boyue-pay/boyue-pay-wx/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/boyue-pay/boyue-pay-wx/src/main/resources/META-INF/additional-spring-configuration-metadata.json new file mode 100644 index 0000000..edafe82 --- /dev/null +++ b/boyue-pay/boyue-pay-wx/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -0,0 +1,39 @@ +{ + "properties": [ + { + "name": "pay.wechat.enabled", + "type": "java.lang.Boolean", + "description": "是否启用微信支付" + }, + { + "name": "pay.wechat.appId", + "type": "java.lang.String", + "description": "微信支付appid" + }, + { + "name": "pay.wechat.apiV3Key", + "type": "java.lang.String", + "description": "微信支付apiV3Key" + }, + { + "name": "pay.wechat.privateKeyPath", + "type": "java.lang.String", + "description": "微信支付私钥,可以直接用字符串,也可以是基于classpath的文件路径" + }, + { + "name": "pay.wechat.merchantId", + "type": "java.lang.String", + "description": "微信支付merchantId" + }, + { + "name": "pay.wechat.merchantSerialNumber", + "type": "java.lang.String", + "description": "微信支付merchantSerialNumber" + }, + { + "name": "pay.wechat.notifyUrl", + "type": "java.lang.String", + "description": "微信支付回调地址" + } + ] +} \ No newline at end of file diff --git a/boyue-pay/pom.xml b/boyue-pay/pom.xml new file mode 100644 index 0000000..289706c --- /dev/null +++ b/boyue-pay/pom.xml @@ -0,0 +1,75 @@ + + + + boyuehasfj-java + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-pay + + + 0.2.12 + 2.2.0 + + + + 支付模块 + + + + + + com.github.wechatpay-apiv3 + wechatpay-java + ${wechatpay.version} + + + + com.alipay.sdk + alipay-easysdk + ${alipay.version} + + + + + com.boyue + boyue-pay-common + ${boyue.version} + + + + + com.boyue + boyue-pay-sqb + ${boyue.version} + + + + + com.boyue + boyue-pay-alipay + ${boyue.version} + + + + + com.boyue + boyue-pay-wx + ${boyue.version} + + + + + + + boyue-pay-sqb + boyue-pay-alipay + boyue-pay-wx + boyue-pay-common + boyue-pay-starter + + pom + \ No newline at end of file diff --git a/boyue-plugins/boyue-atomikos/pom.xml b/boyue-plugins/boyue-atomikos/pom.xml new file mode 100644 index 0000000..069aea9 --- /dev/null +++ b/boyue-plugins/boyue-atomikos/pom.xml @@ -0,0 +1,33 @@ + + + + boyue-plugins + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-atomikos + + + boyue-atomikos + + + + + + + com.boyue + boyue-common + + + + + com.atomikos + transactions-spring-boot3-starter + + + + + diff --git a/boyue-plugins/boyue-atomikos/src/main/java/com/boyue/atomikos/config/AtomikosConfig.java b/boyue-plugins/boyue-atomikos/src/main/java/com/boyue/atomikos/config/AtomikosConfig.java new file mode 100644 index 0000000..e505b95 --- /dev/null +++ b/boyue-plugins/boyue-atomikos/src/main/java/com/boyue/atomikos/config/AtomikosConfig.java @@ -0,0 +1,65 @@ +package com.boyue.atomikos.config; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.jta.JtaTransactionManager; + +import com.atomikos.icatch.jta.UserTransactionImp; +import com.atomikos.icatch.jta.UserTransactionManager; +import com.atomikos.jdbc.AtomikosDataSourceBean; + +import jakarta.annotation.PreDestroy; +import jakarta.transaction.TransactionManager; +import jakarta.transaction.UserTransaction; + +/** + * JTA 事务配置 + * + * @author boyue + */ +@Configuration +@ConditionalOnProperty(name = "atomikos.enabled", havingValue = "true") +public class AtomikosConfig { + @Bean(name = "userTransaction") + public UserTransaction userTransaction() throws Throwable { + UserTransaction userTransaction = new UserTransactionImp(); + // 设置事务超时时间为10000毫秒 + userTransaction.setTransactionTimeout(10000); + return userTransaction; + } + + @Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close") + public TransactionManager atomikosTransactionManager() throws Throwable { + UserTransactionManager userTransactionManager = new UserTransactionManager(); + // 设置是否强制关闭事务管理器为false + userTransactionManager.setForceShutdown(false); + return userTransactionManager; + } + + @Bean(name = "transactionManager") + @DependsOn({ "userTransaction", "atomikosTransactionManager" }) + public PlatformTransactionManager transactionManager() throws Throwable { + UserTransaction userTransaction = userTransaction(); + TransactionManager atomikosTransactionManager = atomikosTransactionManager(); + return new JtaTransactionManager(userTransaction, atomikosTransactionManager); + } + + private List atomikosDataSourceBeans = new ArrayList<>(); + + public List getAtomikosDataSourceBeans() { + return atomikosDataSourceBeans; + } + + @PreDestroy + public void destroy() { + for (AtomikosDataSourceBean aDataSourceBean : this.atomikosDataSourceBeans) { + aDataSourceBean.close(); + } + } +} \ No newline at end of file diff --git a/boyue-plugins/boyue-atomikos/src/main/java/com/boyue/atomikos/datasource/AtomikosDataSourceCreate.java b/boyue-plugins/boyue-atomikos/src/main/java/com/boyue/atomikos/datasource/AtomikosDataSourceCreate.java new file mode 100644 index 0000000..37d98cd --- /dev/null +++ b/boyue-plugins/boyue-atomikos/src/main/java/com/boyue/atomikos/datasource/AtomikosDataSourceCreate.java @@ -0,0 +1,41 @@ +package com.boyue.atomikos.datasource; + +import java.util.Properties; + +import javax.sql.CommonDataSource; +import javax.sql.DataSource; +import javax.sql.XADataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.DependsOn; +import org.springframework.stereotype.Component; + +import com.atomikos.spring.AtomikosDataSourceBean; +import com.boyue.atomikos.config.AtomikosConfig; +import com.boyue.common.service.datasource.AfterCreateDataSource; + +@Component +@ConditionalOnProperty(name = "atomikos.enabled", havingValue = "true") +@DependsOn({ "transactionManager" }) +public class AtomikosDataSourceCreate implements AfterCreateDataSource { + + @Autowired + private AtomikosConfig atomikosConfig; + + public DataSource afterCreateDataSource(String name, Properties prop, CommonDataSource dataSource) { + AtomikosDataSourceBean ds = new AtomikosDataSourceBean(); + atomikosConfig.getAtomikosDataSourceBeans().add(ds); + ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource"); + ds.setUniqueResourceName(name); + ds.setXaDataSource((XADataSource) dataSource); + ds.setXaProperties(prop); + if (prop.getProperty("minIdle") != null) { + ds.setMinPoolSize(Integer.parseInt(prop.getProperty("minIdle"))); + } + if (prop.getProperty("maxActive") != null) { + ds.setMaxPoolSize(Integer.parseInt(prop.getProperty("maxActive"))); + } + return ds; + } +} diff --git a/boyue-plugins/boyue-ehcache/pom.xml b/boyue-plugins/boyue-ehcache/pom.xml new file mode 100644 index 0000000..c1f4687 --- /dev/null +++ b/boyue-plugins/boyue-ehcache/pom.xml @@ -0,0 +1,33 @@ + + + + boyue-plugins + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-ehcache + + + 中间件 + + + + + + + com.boyue + boyue-common + + + + + org.ehcache + ehcache + + + + + diff --git a/boyue-plugins/boyue-ehcache/src/main/java/com/boyue/ehcache/config/Ehcache3Cache.java b/boyue-plugins/boyue-ehcache/src/main/java/com/boyue/ehcache/config/Ehcache3Cache.java new file mode 100644 index 0000000..41a541f --- /dev/null +++ b/boyue-plugins/boyue-ehcache/src/main/java/com/boyue/ehcache/config/Ehcache3Cache.java @@ -0,0 +1,62 @@ +package com.boyue.ehcache.config; + +import java.lang.reflect.Field; +import java.util.HashSet; +import java.util.Set; + +import org.ehcache.core.EhcacheBase; +import org.ehcache.impl.internal.concurrent.ConcurrentHashMap; +import org.ehcache.impl.internal.store.heap.OnHeapStore; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cache.Cache; +import org.springframework.cache.jcache.JCacheCache; +import org.springframework.cache.jcache.JCacheCacheManager; +import org.springframework.stereotype.Component; + +import com.boyue.common.service.cache.CacheKeys; +import com.boyue.common.service.cache.CacheNoTimeOut; + +@Component +@ConditionalOnProperty(prefix = "spring.cache", name = { "type" }, havingValue = "jcache", matchIfMissing = false) +public class Ehcache3Cache implements CacheNoTimeOut, CacheKeys { + + @Autowired + private JCacheCacheManager jCacheCacheManager; + + @Override + @SuppressWarnings(value = { "unchecked", "rawtypes" }) + public Set getCachekeys(Cache cache) { + Set keyset = new HashSet<>(); + try { + JCacheCache jehcache = (JCacheCache) cache; + // org.ehcache.jsr107.Eh107Cache 不公开 + Object nativeCache = jehcache.getNativeCache(); + Class nativeCacheClass = nativeCache.getClass(); + Field ehCacheField = nativeCacheClass.getDeclaredField("ehCache"); + ehCacheField.setAccessible(true); + EhcacheBase ehcache = (EhcacheBase) ehCacheField.get(nativeCache); + Field storeField = EhcacheBase.class.getDeclaredField("store"); + storeField.setAccessible(true); + OnHeapStore store = (OnHeapStore) storeField.get(ehcache); + Field mapField = OnHeapStore.class.getDeclaredField("map"); + mapField.setAccessible(true); + // org.ehcache.impl.internal.store.heap.Backend 不公开 + Object map = mapField.get(store); + Class mapClass = map.getClass(); + Field realMapField = mapClass.getDeclaredField("realMap"); + realMapField.setAccessible(true); + ConcurrentHashMap realMap = (ConcurrentHashMap) realMapField.get(map); + keyset = realMap.keySet(); + } catch (Exception e) { + } + return keyset; + } + + @Override + public void setCacheObject(String cacheName, String key, T value) { + Cache cache = jCacheCacheManager.getCache(cacheName); + cache.put(cacheName, value); + } + +} diff --git a/boyue-plugins/boyue-ehcache/src/main/java/com/boyue/ehcache/config/Ehcache3Config.java b/boyue-plugins/boyue-ehcache/src/main/java/com/boyue/ehcache/config/Ehcache3Config.java new file mode 100644 index 0000000..00d0ee4 --- /dev/null +++ b/boyue-plugins/boyue-ehcache/src/main/java/com/boyue/ehcache/config/Ehcache3Config.java @@ -0,0 +1,52 @@ +package com.boyue.ehcache.config; + +import java.util.concurrent.TimeUnit; + +import javax.cache.CacheManager; +import javax.cache.Caching; +import javax.cache.configuration.MutableConfiguration; +import javax.cache.expiry.CreatedExpiryPolicy; +import javax.cache.expiry.Duration; + +import org.ehcache.jsr107.EhcacheCachingProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.jcache.JCacheCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableCaching +@ConditionalOnProperty(prefix = "spring.cache", name = { "type" }, havingValue = "jcache", matchIfMissing = false) +public class Ehcache3Config { + + @Bean + public JCacheCacheManager ehcacheManager() { + EhcacheCachingProvider cachingProvider = (EhcacheCachingProvider) Caching.getCachingProvider(); + + CacheManager cacheManager = cachingProvider.getCacheManager(); + MutableConfiguration mutableConfiguration = new MutableConfiguration<>(); + mutableConfiguration.setTypes(String.class, Object.class); + mutableConfiguration.setStoreByValue(false); // 默认值为 true,可根据需求调整 + mutableConfiguration.setManagementEnabled(true); // 启用管理功能(可选) + mutableConfiguration.setStatisticsEnabled(true); // 启用统计功能(可选) + // 设置缓存过期策略(以 timeToIdle 为例,根据实际需求调整) + mutableConfiguration.setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(new Duration(TimeUnit.HOURS, 1))); + + cacheManager.createCache("temp_cache", mutableConfiguration); + cacheManager.createCache("eternal_cache", mutableConfiguration); + cacheManager.createCache("sys_dict", mutableConfiguration); + cacheManager.createCache("sys_config", mutableConfiguration); + cacheManager.createCache("repeat_submit", mutableConfiguration); + cacheManager.createCache("captcha_codes", mutableConfiguration); + cacheManager.createCache("login_tokens", mutableConfiguration); + cacheManager.createCache("ip_err_cnt_key", mutableConfiguration); + cacheManager.createCache("rate_limit", mutableConfiguration); + cacheManager.createCache("pwd_err_cnt", mutableConfiguration); + + JCacheCacheManager jCacheCacheManager = new JCacheCacheManager(cacheManager); + + return jCacheCacheManager; + + } +} diff --git a/boyue-plugins/boyue-mybatis-interceptor/pom.xml b/boyue-plugins/boyue-mybatis-interceptor/pom.xml new file mode 100644 index 0000000..5deb580 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-interceptor/pom.xml @@ -0,0 +1,26 @@ + + + + boyue-plugins + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-mybatis-interceptor + + + + com.boyue + boyue-common + + + + org.springframework.boot + spring-boot-starter-aop + + + + diff --git a/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/annotation/DataSecurity.java b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/annotation/DataSecurity.java new file mode 100644 index 0000000..d1096f5 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/annotation/DataSecurity.java @@ -0,0 +1,16 @@ +package com.boyue.mybatisinterceptor.annotation; + +import com.boyue.mybatisinterceptor.enums.DataSecurityStrategy; + +import java.lang.annotation.*; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DataSecurity { + public DataSecurityStrategy strategy() default DataSecurityStrategy.CREEATE_BY; + + public String table() default ""; + + public String joinTableAlise() default ""; +} diff --git a/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/annotation/MybatisHandlerOrder.java b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/annotation/MybatisHandlerOrder.java new file mode 100644 index 0000000..28c6cfc --- /dev/null +++ b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/annotation/MybatisHandlerOrder.java @@ -0,0 +1,10 @@ +package com.boyue.mybatisinterceptor.annotation; + +import java.lang.annotation.*; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface MybatisHandlerOrder { + public int value() default 0; +} diff --git a/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/aspectj/DataSecurityAspect.java b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/aspectj/DataSecurityAspect.java new file mode 100644 index 0000000..e661f5e --- /dev/null +++ b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/aspectj/DataSecurityAspect.java @@ -0,0 +1,77 @@ +package com.boyue.mybatisinterceptor.aspectj; + +import com.boyue.mybatisinterceptor.annotation.DataSecurity; +import com.boyue.mybatisinterceptor.context.sqlContext.SqlContextHolder; +import com.boyue.mybatisinterceptor.model.JoinTableModel; +import com.boyue.mybatisinterceptor.model.WhereModel; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.After; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.stereotype.Component; + +import com.boyue.common.utils.SecurityUtils; +import com.boyue.common.utils.StringUtils; + +@Aspect +@Component +public class DataSecurityAspect { + + @Before(value = "@annotation(dataSecurity)") + public void doBefore(final JoinPoint point, DataSecurity dataSecurity) throws Throwable { + SqlContextHolder.startDataSecurity(); + switch (dataSecurity.strategy()) { + case CREEATE_BY: + WhereModel createByModel = new WhereModel(); + createByModel.setTable(dataSecurity.table()); + createByModel.setValue("\"" + SecurityUtils.getUsername() + "\""); + createByModel.setWhereColumn("create_by"); + createByModel.setMethod(WhereModel.METHOD_EQUAS); + createByModel.setConnectType(WhereModel.CONNECT_AND); + SqlContextHolder.addWhereParam(createByModel); + break; + case USER_ID: + WhereModel userIdModel = new WhereModel(); + userIdModel.setTable(dataSecurity.table()); + userIdModel.setTable("user_id"); + userIdModel.setValue(SecurityUtils.getUserId()); + userIdModel.setConnectType(WhereModel.CONNECT_AND); + userIdModel.setMethod(WhereModel.METHOD_EQUAS); + SqlContextHolder.addWhereParam(userIdModel); + break; + case JOINTABLE_CREATE_BY: + JoinTableModel createByTableModel = new JoinTableModel(); + createByTableModel.setFromTable(dataSecurity.table()); + createByTableModel.setFromTableAlise(dataSecurity.table()); + createByTableModel.setJoinTable("sys_user"); + if (!StringUtils.isEmpty(dataSecurity.joinTableAlise())) { + createByTableModel.setJoinTableAlise(dataSecurity.joinTableAlise()); + } + createByTableModel.setFromTableColumn("create_by"); + createByTableModel.setJoinTableColumn("user_name"); + SqlContextHolder.addJoinTable(createByTableModel); + break; + case JOINTABLE_USER_ID: + JoinTableModel userIdTableModel = new JoinTableModel(); + userIdTableModel.setFromTable(dataSecurity.table()); + userIdTableModel.setFromTableAlise(dataSecurity.table()); + userIdTableModel.setJoinTable("sys_user"); + if (!StringUtils.isEmpty(dataSecurity.joinTableAlise())) { + userIdTableModel.setJoinTableAlise(dataSecurity.joinTableAlise()); + } + userIdTableModel.setFromTableColumn("user_id"); + userIdTableModel.setJoinTableColumn("user_id"); + SqlContextHolder.addJoinTable(userIdTableModel); + break; + default: + break; + } + + } + + @After(value = " @annotation(dataSecurity)") + public void doAfter(final JoinPoint point, DataSecurity dataSecurity) { + SqlContextHolder.clearCache(); + } +} diff --git a/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/context/page/PageContextHolder.java b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/context/page/PageContextHolder.java new file mode 100644 index 0000000..7502519 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/context/page/PageContextHolder.java @@ -0,0 +1,44 @@ +package com.boyue.mybatisinterceptor.context.page; + +import com.alibaba.fastjson2.JSONObject; +import com.boyue.mybatisinterceptor.context.page.model.PageInfo; + +public class PageContextHolder { + private static final ThreadLocal PAGE_CONTEXT_HOLDER = new ThreadLocal<>(); + + private static final String PAGE_FLAG = "isPage"; + + private static final String PAGE_INFO = "pageInfo"; + + private static final String TOTAL = "total"; + + public static void startPage() { + JSONObject jsonObject = new JSONObject(); + jsonObject.put(PAGE_FLAG, Boolean.TRUE); + PAGE_CONTEXT_HOLDER.set(jsonObject); + } + + public static void setPageInfo() { + PAGE_CONTEXT_HOLDER.get().put(PAGE_INFO, PageInfo.defaultPageInfo()); + } + + public static PageInfo getPageInfo() { + return (PageInfo) PAGE_CONTEXT_HOLDER.get().get(PAGE_INFO); + } + + public static void clear() { + PAGE_CONTEXT_HOLDER.remove(); + } + + public static boolean isPage() { + return PAGE_CONTEXT_HOLDER.get() != null && PAGE_CONTEXT_HOLDER.get().getBooleanValue(PAGE_FLAG); + } + + public static void setTotal(Long total) { + PAGE_CONTEXT_HOLDER.get().put(TOTAL, total); + } + + public static Long getTotal() { + return PAGE_CONTEXT_HOLDER.get().getLong(TOTAL); + } +} diff --git a/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/context/page/model/BoYueTableData.java b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/context/page/model/BoYueTableData.java new file mode 100644 index 0000000..543d12e --- /dev/null +++ b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/context/page/model/BoYueTableData.java @@ -0,0 +1,25 @@ +package com.boyue.mybatisinterceptor.context.page.model; + +import java.util.List; + +public class BoYueTableData { + private Long total; + private List data; + + public Long getTotal() { + return total; + } + + public void setTotal(Long total) { + this.total = total; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + +} diff --git a/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/context/page/model/PageInfo.java b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/context/page/model/PageInfo.java new file mode 100644 index 0000000..ec95da6 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/context/page/model/PageInfo.java @@ -0,0 +1,63 @@ +package com.boyue.mybatisinterceptor.context.page.model; + +import com.boyue.common.core.text.Convert; +import com.boyue.common.utils.ServletUtils; + +public class PageInfo { + + private Long pageNumber; + + private Long pageSize; + + /** + * 当前记录起始索引 + */ + public static final String PAGE_NUM = "pageNum"; + + /** + * 每页显示记录数 + */ + public static final String PAGE_SIZE = "pageSize"; + + /** + * 排序列 + */ + public static final String ORDER_BY_COLUMN = "orderByColumn"; + + /** + * 排序的方向 "desc" 或者 "asc". + */ + public static final String IS_ASC = "isAsc"; + + /** + * 分页参数合理化 + */ + public static final String REASONABLE = "reasonable"; + + public Long getPageNumber() { + return pageNumber; + } + + public void setPageNumber(Long pageNumber) { + this.pageNumber = pageNumber; + } + + public Long getPageSize() { + return pageSize; + } + + public void setPageSize(Long pageSize) { + this.pageSize = pageSize; + } + + public static PageInfo defaultPageInfo() { + PageInfo pageInfo = new PageInfo(); + pageInfo.setPageNumber(Long.valueOf(Convert.toInt(ServletUtils.getParameter(PAGE_NUM), 1))); + pageInfo.setPageSize(Long.valueOf(Convert.toInt(ServletUtils.getParameter(PAGE_SIZE), 10))); + return pageInfo; + } + + public Long getOffeset() { + return (pageNumber.longValue() - 1L) * pageSize; + } +} diff --git a/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/context/page/model/TableInfo.java b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/context/page/model/TableInfo.java new file mode 100644 index 0000000..e12c12d --- /dev/null +++ b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/context/page/model/TableInfo.java @@ -0,0 +1,22 @@ +package com.boyue.mybatisinterceptor.context.page.model; + +import java.util.ArrayList; +import java.util.List; + +public class TableInfo extends ArrayList { + + private Long total; + + public TableInfo(List list) { + super(list); + } + + public Long getTotal() { + return total; + } + + public void setTotal(Long total) { + this.total = total; + } + +} diff --git a/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/context/sqlContext/SqlContextHolder.java b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/context/sqlContext/SqlContextHolder.java new file mode 100644 index 0000000..346cac2 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/context/sqlContext/SqlContextHolder.java @@ -0,0 +1,55 @@ +package com.boyue.mybatisinterceptor.context.sqlContext; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.boyue.mybatisinterceptor.enums.SqlType; +import com.boyue.mybatisinterceptor.model.JoinTableModel; +import com.boyue.mybatisinterceptor.model.WhereModel; + +public class SqlContextHolder { + private static final ThreadLocal SQL_CONTEXT_HOLDER = new ThreadLocal<>(); + + public static void startDataSecurity() { + SQL_CONTEXT_HOLDER.get().put("isSecurity", Boolean.TRUE); + } + + public static void startLogicSelect() { + SQL_CONTEXT_HOLDER.get().put("isLogic", Boolean.TRUE); + } + + public static void addWhereParam(WhereModel whereModel) { + SQL_CONTEXT_HOLDER.get().getJSONArray(SqlType.WHERE.getSqlType()).add(whereModel); + } + + public static void clearCache() { + SQL_CONTEXT_HOLDER.remove(); + } + + public static boolean isSecurity() { + return SQL_CONTEXT_HOLDER.get() != null + && SQL_CONTEXT_HOLDER.get().getBooleanValue("isSecurity"); + } + + public static JSONArray getWhere() { + return SQL_CONTEXT_HOLDER.get().getJSONArray(SqlType.WHERE.getSqlType()); + } + + public static void addJoinTable(JoinTableModel joinTableModel) { + SQL_CONTEXT_HOLDER.get().getJSONArray(SqlType.JOIN.getSqlType()).add(joinTableModel); + } + + public static JSONArray getJoinTables() { + return SQL_CONTEXT_HOLDER.get().getJSONArray(SqlType.JOIN.getSqlType()); + } + + public static void startInterceptor() { + JSONObject jsonObject = SQL_CONTEXT_HOLDER.get(); + if (jsonObject != null) { + return; + } + JSONObject object = new JSONObject(); + object.put(SqlType.JOIN.getSqlType(), new JSONArray()); + object.put(SqlType.WHERE.getSqlType(), new JSONArray()); + SQL_CONTEXT_HOLDER.set(object); + } +} diff --git a/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/enums/DataSecurityStrategy.java b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/enums/DataSecurityStrategy.java new file mode 100644 index 0000000..f1338f8 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/enums/DataSecurityStrategy.java @@ -0,0 +1,12 @@ +package com.boyue.mybatisinterceptor.enums; + +public enum DataSecurityStrategy { + /** 通过创建人字段关联表 */ + JOINTABLE_CREATE_BY, + /** 通过用户ID字段关联表 */ + JOINTABLE_USER_ID, + /** 通过创建人字段 */ + CREEATE_BY, + /** 通过用户ID字段 */ + USER_ID; +} diff --git a/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/enums/SqlType.java b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/enums/SqlType.java new file mode 100644 index 0000000..8844f5b --- /dev/null +++ b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/enums/SqlType.java @@ -0,0 +1,22 @@ +package com.boyue.mybatisinterceptor.enums; + +public enum SqlType { + /** where */ + WHERE("where"), + /** join */ + JOIN("join"), + /** select */ + SELECT("select"), + /** limit */ + LIMIT("limit"); + + private String sqlType; + + public String getSqlType() { + return sqlType; + } + + private SqlType(String sqlType) { + this.sqlType = sqlType; + } +} diff --git a/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/handler/MybatisAfterHandler.java b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/handler/MybatisAfterHandler.java new file mode 100644 index 0000000..d1090ab --- /dev/null +++ b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/handler/MybatisAfterHandler.java @@ -0,0 +1,7 @@ +package com.boyue.mybatisinterceptor.handler; + +public interface MybatisAfterHandler { + + Object handleObject(Object object) throws Throwable; + +} diff --git a/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/handler/MybatisPreHandler.java b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/handler/MybatisPreHandler.java new file mode 100644 index 0000000..33c1150 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/handler/MybatisPreHandler.java @@ -0,0 +1,15 @@ +package com.boyue.mybatisinterceptor.handler; + +import org.apache.ibatis.cache.CacheKey; +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.session.RowBounds; + +public interface MybatisPreHandler { + + void preHandle(Executor executor, MappedStatement mappedStatement, Object params, + RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) + throws Throwable; +} diff --git a/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/handler/dataSecurity/DataSecurityPreHandler.java b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/handler/dataSecurity/DataSecurityPreHandler.java new file mode 100644 index 0000000..d61a7d2 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/handler/dataSecurity/DataSecurityPreHandler.java @@ -0,0 +1,133 @@ +package com.boyue.mybatisinterceptor.handler.dataSecurity; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import org.apache.ibatis.cache.CacheKey; +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.session.RowBounds; +import org.springframework.stereotype.Component; +import org.springframework.util.ReflectionUtils; + +import com.alibaba.fastjson2.JSONArray; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.sql.SqlUtil; +import com.boyue.mybatisinterceptor.annotation.MybatisHandlerOrder; +import com.boyue.mybatisinterceptor.context.sqlContext.SqlContextHolder; +import com.boyue.mybatisinterceptor.handler.MybatisPreHandler; +import com.boyue.mybatisinterceptor.model.JoinTableModel; +import com.boyue.mybatisinterceptor.model.WhereModel; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.Alias; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.operators.relational.EqualsTo; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.delete.Delete; +import net.sf.jsqlparser.statement.select.Join; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.Select; +import net.sf.jsqlparser.statement.update.Update; + +@MybatisHandlerOrder(1) +@Component +public class DataSecurityPreHandler implements MybatisPreHandler { + + private static final Field sqlFiled = ReflectionUtils.findField(BoundSql.class, "sql"); + static { + sqlFiled.setAccessible(true); + } + + @Override + public void preHandle(Executor executor, MappedStatement mappedStatement, Object params, RowBounds rowBounds, + ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws Throwable { + if (SqlContextHolder.isSecurity()) { + Statement sql = parseSql(SqlUtil.parseSql(boundSql.getSql())); + sqlFiled.set(boundSql, sql.toString()); + } + } + + private static Statement parseSql(Statement statement) throws JSQLParserException { + if (statement instanceof Select) { + Select select = (Select) statement; + // plain.setWhere(CCJSqlParserUtil.parseCondExpression(handleWhere(expWhere))); + handleWhere(select); + handleJoin(select); + return select; + } else { + return statement; + } + } + + private static void handleWhere(Statement statement) throws JSQLParserException { + if (statement instanceof Select) { + Select select = (Select) statement; + PlainSelect plainSelect = select.getPlainSelect(); + plainSelect.setWhere(getConfigedWhereExpression(plainSelect.getWhere())); + } else if (statement instanceof Update) { + Update update = (Update) statement; + update.setWhere(getConfigedWhereExpression(update.getWhere())); + } else if (statement instanceof Delete) { + Delete delete = (Delete) statement; + delete.setWhere(getConfigedWhereExpression(delete.getWhere())); + } + + } + + private static Expression getConfigedWhereExpression(Expression expWhere) throws JSQLParserException { + StringBuilder whereParam = new StringBuilder(" "); + String where = expWhere != null ? expWhere.toString() : null; + if (SqlContextHolder.getWhere() == null || SqlContextHolder.getWhere().size() <= 0) { + return expWhere; + } + JSONArray wehreArray = SqlContextHolder.getWhere(); + wehreArray.forEach(item -> { + whereParam.append(((WhereModel) item).getSqlString()); + }); + WhereModel whereModel = (WhereModel) wehreArray.get(0); + where = StringUtils.isEmpty(where) + ? whereParam.toString().substring(whereModel.getConnectType().length() + 2, whereParam.length()) + : where + " " + whereParam.toString(); + return CCJSqlParserUtil.parseCondExpression(where); + } + + private static void handleJoin(Statement statement) { + if (SqlContextHolder.getJoinTables() == null || SqlContextHolder.getJoinTables().size() <= 0) { + return; + } + if (statement instanceof Select) { + Select select = (Select) statement; + select.getPlainSelect().addJoins(getConfigedJoinExpression()); + } else if (statement instanceof Update) { + Update update = (Update) statement; + update.addJoins(getConfigedJoinExpression()); + } else if (statement instanceof Delete) { + Delete delete = (Delete) statement; + delete.addJoins(getConfigedJoinExpression()); + } + } + + private static List getConfigedJoinExpression() { + List joins = new ArrayList<>(); + SqlContextHolder.getJoinTables().forEach(item -> { + JoinTableModel tableModel = (JoinTableModel) item; + Table table = new Table(tableModel.getJoinTable()); + table.setAlias(new Alias(tableModel.getJoinTableAlise())); + Join join = new Join(); + join.setRightItem(table); + join.setInner(true); + Expression onExpression = new EqualsTo(new Column(tableModel.getFromTableColumnString()), + new Column(tableModel.getJoinTableColumnString())); + join.setOnExpressions(List.of(onExpression)); + joins.add(join); + }); + return joins; + } +} diff --git a/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/handler/page/PageAfterHandler.java b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/handler/page/PageAfterHandler.java new file mode 100644 index 0000000..81185b2 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/handler/page/PageAfterHandler.java @@ -0,0 +1,30 @@ +package com.boyue.mybatisinterceptor.handler.page; + +import java.util.List; + +import org.springframework.stereotype.Component; + +import com.boyue.mybatisinterceptor.annotation.MybatisHandlerOrder; +import com.boyue.mybatisinterceptor.context.page.PageContextHolder; +import com.boyue.mybatisinterceptor.context.page.model.TableInfo; +import com.boyue.mybatisinterceptor.handler.MybatisAfterHandler; + +@MybatisHandlerOrder(1) +@Component +public class PageAfterHandler implements MybatisAfterHandler { + + @Override + public Object handleObject(Object object) throws Throwable { + if (PageContextHolder.isPage()) { + if (object instanceof List) { + TableInfo tableInfo = new TableInfo((List) object); + tableInfo.setTotal(PageContextHolder.getTotal()); + PageContextHolder.clear(); + return tableInfo; + } + return object; + } + return object; + } + +} diff --git a/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/handler/page/PagePreHandler.java b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/handler/page/PagePreHandler.java new file mode 100644 index 0000000..206fe90 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/handler/page/PagePreHandler.java @@ -0,0 +1,144 @@ +package com.boyue.mybatisinterceptor.handler.page; + +import java.lang.reflect.Field; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.ibatis.cache.CacheKey; +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.ResultMap; +import org.apache.ibatis.mapping.ResultMapping; +import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.session.RowBounds; +import org.springframework.stereotype.Component; +import org.springframework.util.ReflectionUtils; + +import com.boyue.common.utils.sql.SqlUtil; +import com.boyue.mybatisinterceptor.annotation.MybatisHandlerOrder; +import com.boyue.mybatisinterceptor.context.page.PageContextHolder; +import com.boyue.mybatisinterceptor.context.page.model.PageInfo; +import com.boyue.mybatisinterceptor.handler.MybatisPreHandler; + +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.select.Limit; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.Select; +import net.sf.jsqlparser.statement.select.SelectItem; + +@Component +@MybatisHandlerOrder(2) +public class PagePreHandler implements MybatisPreHandler { + + private static final List EMPTY_RESULTMAPPING = new ArrayList(0); + + private static final String SELECT_COUNT_SUFIX = "_SELECT_COUNT"; + private static final Field sqlFiled = ReflectionUtils.findField(BoundSql.class, "sql"); + static { + sqlFiled.setAccessible(true); + } + + @Override + public void preHandle(Executor executor, MappedStatement mappedStatement, Object params, RowBounds rowBounds, + ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws Throwable { + if (PageContextHolder.isPage()) { + String originSql = boundSql.getSql(); + Statement sql = SqlUtil.parseSql(originSql); + if (sql instanceof Select) { + PageInfo pageInfo = PageContextHolder.getPageInfo(); + Statement handleLimit = handleLimit((Select) sql, pageInfo); + Statement countSql = getCountSql((Select) sql); + Long count = getCount(executor, mappedStatement, params, boundSql, rowBounds, resultHandler, + countSql.toString()); + PageContextHolder.setTotal(count); + sqlFiled.set(boundSql, handleLimit.toString()); + cacheKey = executor.createCacheKey(mappedStatement, params, rowBounds, boundSql); + } + } + + } + + private static MappedStatement createCountMappedStatement(MappedStatement ms, String newMsId) { + MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), newMsId, + ms.getSqlSource(), + ms.getSqlCommandType()); + builder.resource(ms.getResource()); + builder.fetchSize(ms.getFetchSize()); + builder.statementType(ms.getStatementType()); + builder.keyGenerator(ms.getKeyGenerator()); + if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) { + StringBuilder keyProperties = new StringBuilder(); + for (String keyProperty : ms.getKeyProperties()) { + keyProperties.append(keyProperty).append(","); + } + keyProperties.delete(keyProperties.length() - 1, keyProperties.length()); + builder.keyProperty(keyProperties.toString()); + } + builder.timeout(ms.getTimeout()); + builder.parameterMap(ms.getParameterMap()); + // count查询返回值int + List resultMaps = new ArrayList(); + ResultMap resultMap = new ResultMap.Builder(ms.getConfiguration(), ms.getId(), Long.class, + EMPTY_RESULTMAPPING) + .build(); + resultMaps.add(resultMap); + builder.resultMaps(resultMaps); + builder.resultSetType(ms.getResultSetType()); + builder.cache(ms.getCache()); + builder.flushCacheRequired(ms.isFlushCacheRequired()); + builder.useCache(ms.isUseCache()); + return builder.build(); + } + + public static Long getCount(Executor executor, MappedStatement mappedStatement, Object parameter, + BoundSql boundSql, RowBounds rowBounds, ResultHandler resultHandler, String countSql) + throws SQLException { + + Map additionalParameters = boundSql.getAdditionalParameters(); + + BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, + boundSql.getParameterMappings(), parameter); + for (String key : additionalParameters.keySet()) { + countBoundSql.setAdditionalParameter(key, additionalParameters.get(key)); + } + CacheKey countKey = executor.createCacheKey(mappedStatement, parameter, RowBounds.DEFAULT, countBoundSql); + + List query = executor.query( + createCountMappedStatement(mappedStatement, getCountMSId(mappedStatement)), + parameter, RowBounds.DEFAULT, resultHandler, countKey, + countBoundSql); + return (Long) query.get(0); + } + + private static String getCountMSId(MappedStatement mappedStatement) { + return mappedStatement.getId() + SELECT_COUNT_SUFIX; + } + + public static Statement getCountSql(Select select) { + PlainSelect plain = select.getPlainSelect(); + PlainSelect countPlain = new PlainSelect(); + countPlain.setSelectItems(List.of(new SelectItem<>(new Column("COUNT(0)")))); + countPlain.setJoins(plain.getJoins()); + countPlain.setWhere(plain.getWhere()); + countPlain.setFromItem(plain.getFromItem()); + countPlain.setDistinct(plain.getDistinct()); + countPlain.setHaving(plain.getHaving()); + countPlain.setIntoTables(plain.getIntoTables()); + // countPlain.setOrderByElements(plain.getOrderByElements()); + return plain; + } + + private static Statement handleLimit(Select select, PageInfo pageInfo) { + Limit limit = new Limit(); + limit.setRowCount(new Column(pageInfo.getPageSize().toString())); + limit.setOffset(new Column(pageInfo.getOffeset().toString())); + PlainSelect plain = select.getPlainSelect(); + plain.setLimit(limit); + return select; + } + +} diff --git a/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/interceptor/mybatis/MybatisInterceptor.java b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/interceptor/mybatis/MybatisInterceptor.java new file mode 100644 index 0000000..dcf8813 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/interceptor/mybatis/MybatisInterceptor.java @@ -0,0 +1,108 @@ +package com.boyue.mybatisinterceptor.interceptor.mybatis; + +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.ibatis.cache.CacheKey; +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.plugin.Intercepts; +import org.apache.ibatis.plugin.Invocation; +import org.apache.ibatis.plugin.Signature; +import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.session.RowBounds; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.boyue.mybatisinterceptor.annotation.MybatisHandlerOrder; +import com.boyue.mybatisinterceptor.handler.MybatisAfterHandler; +import com.boyue.mybatisinterceptor.handler.MybatisPreHandler; + +import jakarta.annotation.PostConstruct; + +@Component +@Intercepts({ + @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, + RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class }), + @Signature(type = Executor.class, method = "query", args = { + MappedStatement.class, Object.class, RowBounds.class, + ResultHandler.class }) + +}) +public class MybatisInterceptor implements Interceptor { + + @Autowired + private List preHandlerBeans; + + @Autowired + private List afterHandlerBeans; + + private static List preHandlersChain; + + private static List afterHandlersChain; + + @PostConstruct + public void init() { + // 对处理器按注解排序 + preHandlersChain = sortHandlers(preHandlerBeans); + afterHandlersChain = sortHandlers(afterHandlerBeans); + } + + /** + * 通用的处理器排序方法 + */ + private List sortHandlers(List handlers) { + return handlers.stream() + .sorted((item1, item2) -> { + MybatisHandlerOrder ann1 = item1.getClass().getAnnotation(MybatisHandlerOrder.class); + MybatisHandlerOrder ann2 = item2.getClass().getAnnotation(MybatisHandlerOrder.class); + int a = ann1 == null ? 0 : ann1.value(); + int b = ann2 == null ? 0 : ann2.value(); + return a - b; + }).collect(Collectors.toList()); + } + + @Override + public Object intercept(Invocation invocation) throws Throwable { + Executor targetExecutor = (Executor) invocation.getTarget(); + Object[] args = invocation.getArgs(); + if (args.length < 6) { + if (preHandlersChain != null && preHandlersChain.size() > 0) { + MappedStatement ms = (MappedStatement) args[0]; + Object parameterObject = args[1]; + RowBounds rowBounds = (RowBounds) args[2]; + Executor executor = (Executor) invocation.getTarget(); + BoundSql boundSql = ms.getBoundSql(parameterObject); + // 可以对参数做各种处理 + CacheKey cacheKey = executor.createCacheKey(ms, parameterObject, rowBounds, boundSql); + for (MybatisPreHandler item : preHandlersChain) { + item.preHandle(targetExecutor, ms, args[1], (RowBounds) args[2], + (ResultHandler) args[3], cacheKey, boundSql); + } + } + Object result = invocation.proceed(); + if (afterHandlersChain != null && afterHandlersChain.size() > 0) { + for (MybatisAfterHandler item : afterHandlersChain) { + item.handleObject(result); + } + } + return result; + } + if (preHandlersChain != null && preHandlersChain.size() > 0) { + for (MybatisPreHandler item : preHandlersChain) { + item.preHandle(targetExecutor, (MappedStatement) args[0], args[1], (RowBounds) args[2], + (ResultHandler) args[3], (CacheKey) args[4], (BoundSql) args[5]); + } + } + Object result = invocation.proceed(); + if (afterHandlersChain != null && afterHandlersChain.size() > 0) { + for (MybatisAfterHandler item : afterHandlersChain) { + result = item.handleObject(result); + } + } + return result; + } + +} diff --git a/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/model/JoinTableModel.java b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/model/JoinTableModel.java new file mode 100644 index 0000000..e9e3a42 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/model/JoinTableModel.java @@ -0,0 +1,85 @@ +package com.boyue.mybatisinterceptor.model; + +import com.boyue.common.utils.StringUtils; + +public class JoinTableModel { + private String joinTable; + + private String joinTableAlise; + + private String fromTable; + + private String fromTableAlise; + + private String joinTableColumn; + + private String fromTableColumn; + + public String getJoinTable() { + return joinTable; + } + + public void setJoinTable(String joinTable) { + this.joinTable = joinTable; + } + + public String getJoinTableAlise() { + if (StringUtils.isEmpty(this.joinTableAlise)) { + return this.joinTable; + } + return joinTableAlise; + } + + public void setJoinTableAlise(String joinTableAlise) { + + this.joinTableAlise = joinTableAlise; + } + + public String getFromTable() { + return fromTable; + } + + public void setFromTable(String fromTable) { + this.fromTable = fromTable; + } + + public String getFromTableAlise() { + if (StringUtils.isEmpty(this.fromTableAlise)) { + return this.fromTable; + } + return fromTableAlise; + } + + public void setFromTableAlise(String fromTableAlise) { + this.fromTableAlise = fromTableAlise; + } + + public String getJoinTableColumn() { + + return joinTableColumn; + } + + public void setJoinTableColumn(String joinTableColumn) { + this.joinTableColumn = joinTableColumn; + } + + public String getFromTableColumn() { + return fromTableColumn; + } + + public void setFromTableColumn(String fromTableColumn) { + this.fromTableColumn = fromTableColumn; + } + + public String getJoinTableColumnString() { + return this.getJoinTableAlise() + "." + this.joinTableColumn; + } + + public String getFromTableColumnString() { + if (StringUtils.isEmpty(this.getFromTableAlise())) { + return this.fromTableColumn; + } + return this.getFromTableAlise() + "." + this.fromTableColumn; + } + +} diff --git a/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/model/WhereModel.java b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/model/WhereModel.java new file mode 100644 index 0000000..0091a0b --- /dev/null +++ b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/model/WhereModel.java @@ -0,0 +1,67 @@ +package com.boyue.mybatisinterceptor.model; + +import com.boyue.common.utils.StringUtils; + +public class WhereModel { + private String whereColumn; + private String table; + private Object value; + private String connectType; + private String method; + + public static final String METHOD_EQUAS = "="; + public static final String METHOD_LIKE = "like"; + public static final String CONNECT_AND = "AND"; + public static final String CONNECT_OR = "OR"; + + public String getWhereColumn() { + return whereColumn; + } + + public void setWhereColumn(String whereColumn) { + this.whereColumn = whereColumn; + } + + public String getTable() { + return table; + } + + public void setTable(String table) { + this.table = table; + } + + public Object getValue() { + return value; + } + + public void setValue(Object value) { + this.value = value; + } + + public String getFullTableColumn() { + if (StringUtils.isEmpty(this.table)) { + return this.whereColumn; + } + return this.table + "." + this.whereColumn; + } + + public String getConnectType() { + return connectType; + } + + public void setConnectType(String connectType) { + this.connectType = connectType; + } + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public String getSqlString() { + return String.format(" %s %s %s %s ", this.getConnectType(), this.getFullTableColumn(), this.method, this.value); + } +} diff --git a/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/util/DataSecurityUtil.java b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/util/DataSecurityUtil.java new file mode 100644 index 0000000..bd6c5c0 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/util/DataSecurityUtil.java @@ -0,0 +1,14 @@ +package com.boyue.mybatisinterceptor.util; + +import com.boyue.mybatisinterceptor.context.sqlContext.SqlContextHolder; + +public class DataSecurityUtil { + + public static void closeDataSecurity() { + SqlContextHolder.clearCache(); + } + + public static void startDataSecurity() { + SqlContextHolder.startDataSecurity(); + } +} diff --git a/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/util/PageUtils.java b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/util/PageUtils.java new file mode 100644 index 0000000..d9e1026 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/util/PageUtils.java @@ -0,0 +1,30 @@ +package com.boyue.mybatisinterceptor.util; + +import java.util.List; + +import com.boyue.mybatisinterceptor.context.page.PageContextHolder; +import com.boyue.mybatisinterceptor.context.page.model.BoYueTableData; +import com.boyue.mybatisinterceptor.context.page.model.TableInfo; + +public class PageUtils { + public static BoYueTableData toTableInfo(List list) { + if (list instanceof TableInfo) { + TableInfo tableInfo = (TableInfo) list; + BoYueTableData BoYueTableData = new BoYueTableData(); + BoYueTableData.setData(list); + BoYueTableData.setTotal(tableInfo.getTotal()); + return BoYueTableData; + } + BoYueTableData BoYueTableData = new BoYueTableData(); + BoYueTableData.setData(list); + BoYueTableData.setTotal(-1L); + return BoYueTableData; + + } + + public static void boyueStartPage() { + PageContextHolder.startPage(); + PageContextHolder.setPageInfo(); + } + +} diff --git a/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/util/SqlUtil.java b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/util/SqlUtil.java new file mode 100644 index 0000000..1ebd96f --- /dev/null +++ b/boyue-plugins/boyue-mybatis-interceptor/src/main/java/com/boyue/mybatisinterceptor/util/SqlUtil.java @@ -0,0 +1,64 @@ +package com.boyue.mybatisinterceptor.util; + +import com.boyue.common.exception.UtilException; +import com.boyue.common.utils.StringUtils; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserManager; +import net.sf.jsqlparser.statement.Statement; + +import java.io.StringReader; + +/** + * sql操作工具类 + * + * @author boyue + */ +public class SqlUtil { + /** + * 定义常用的 sql关键字 + */ + public static String SQL_REGEX = "and |extractvalue|updatexml|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |+|user()"; + + /** + * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) + */ + public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+"; + + private static final CCJSqlParserManager parserManager = new CCJSqlParserManager(); + + /** + * 检查字符,防止注入绕过 + */ + public static String escapeOrderBySql(String value) { + if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) { + throw new UtilException("参数不符合规范,不能进行查询"); + } + return value; + } + + /** + * 验证 order by 语法是否符合规范 + */ + public static boolean isValidOrderBySql(String value) { + return value.matches(SQL_PATTERN); + } + + /** + * SQL关键字检查 + */ + public static void filterKeyword(String value) { + if (StringUtils.isEmpty(value)) { + return; + } + String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|"); + for (String sqlKeyword : sqlKeywords) { + if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1) { + throw new UtilException("参数存在SQL注入风险"); + } + } + } + + public static Statement parseSql(String sql) throws JSQLParserException { + return parserManager.parse(new StringReader(sql)); + } +} diff --git a/boyue-plugins/boyue-mybatis-jpa/pom.xml b/boyue-plugins/boyue-mybatis-jpa/pom.xml new file mode 100644 index 0000000..fb92d44 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-jpa/pom.xml @@ -0,0 +1,33 @@ + + + + boyue-plugins + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-mybatis-jpa + + + boyue-mybatis-jpa + + + + + + + com.boyue + boyue-common + + + + com.baomidou + mybatis-plus-spring-boot3-starter + true + + + + + diff --git a/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/annotation/Column.java b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/annotation/Column.java new file mode 100644 index 0000000..73f7263 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/annotation/Column.java @@ -0,0 +1,18 @@ +package com.boyue.mybatis.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 标注数据库字段 + * + * @author Dftre + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Column { + String name(); // 对应数据库字段名 + boolean primaryKey() default false; // 是否是主键 +} diff --git a/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/annotation/ColumnMap.java b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/annotation/ColumnMap.java new file mode 100644 index 0000000..9dae5f2 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/annotation/ColumnMap.java @@ -0,0 +1,25 @@ +package com.boyue.mybatis.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 标注数据库映射字段 + * + * @author Dftre + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ColumnMap { + String name(); // 对应数据库字段 + + String target(); // 映射表来源 + + String on() default ""; // 映射表字段 + + String onLeft() default ""; // 映射表左字段 + + String onRight() default ""; // 映射表右字段 +} diff --git a/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/annotation/EnableTableMap.java b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/annotation/EnableTableMap.java new file mode 100644 index 0000000..35559b0 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/annotation/EnableTableMap.java @@ -0,0 +1,35 @@ +package com.boyue.mybatis.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 开启数据库关联 + * + * @author Dftre + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface EnableTableMap { + String name() default "t"; + + String dept() default ""; + + String user() default ""; + + String userOn() default "user_id"; + + String userOnLeft() default ""; + + String userOnRight() default ""; + + String deptOn() default "dept_id"; + + String deptOnLeft() default ""; + + String deptOnRight() default ""; + + String deptFrom() default ""; +} diff --git a/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/annotation/Query.java b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/annotation/Query.java new file mode 100644 index 0000000..c98a7bf --- /dev/null +++ b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/annotation/Query.java @@ -0,0 +1,27 @@ +package com.boyue.mybatis.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.boyue.mybatis.enums.QueryEnum; + +/** + * 标注查询条件 + * + * @author Dftre + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Query { + QueryEnum operation() default QueryEnum.eq; // 操作符,如 eq, like, gt 等 + + String[] sections() default { "begin", "end" }; + + String section() default "section"; + + boolean params() default false; + + String sql() default ""; +} diff --git a/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/annotation/Table.java b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/annotation/Table.java new file mode 100644 index 0000000..a12ab7d --- /dev/null +++ b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/annotation/Table.java @@ -0,0 +1,14 @@ +package com.boyue.mybatis.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Table { + String name(); + String[] columns() default {}; + String[] orderBy() default {}; +} diff --git a/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/domain/BaseColumnInfo.java b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/domain/BaseColumnInfo.java new file mode 100644 index 0000000..31e94b3 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/domain/BaseColumnInfo.java @@ -0,0 +1,54 @@ +package com.boyue.mybatis.domain; + +import java.lang.reflect.Field; + +import com.boyue.mybatis.annotation.Query; +import com.boyue.mybatis.utils.QueryUtil; + +public class BaseColumnInfo { + protected String columnName; + protected String fieldName; + protected Field field; + protected Query query; + protected String querySql; + + public Field getField() { + return field; + } + + public Query getQuery() { + return query; + } + + public String getTemplate() { + return getTemplate(false); + } + + public String getColumnName() { + return columnName; + } + + public String getUnqualifiedColumnName() { + return columnName; + } + + public String getTemplate(boolean params) { + if (params) { + return "#{params." + fieldName + "}"; + } else { + return "#{" + fieldName + "}"; + } + } + + public boolean fieldQueryIsNotNull(Object entity) { + return QueryUtil.fieldQueryIsNotNull(entity, field, query); + } + + public boolean fieldIsNotNull(Object entity) { + try { + return this.field.get(entity) != null; + } catch (IllegalAccessException e) { + throw new RuntimeException("Failed to access field for building query conditions.", e); + } + } +} diff --git a/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/domain/ColumnInfo.java b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/domain/ColumnInfo.java new file mode 100644 index 0000000..916fd79 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/domain/ColumnInfo.java @@ -0,0 +1,39 @@ +package com.boyue.mybatis.domain; + +import java.lang.reflect.Field; + +import org.springframework.core.annotation.AnnotationUtils; + +import com.boyue.mybatis.annotation.Column; +import com.boyue.mybatis.annotation.Query; +import com.boyue.mybatis.utils.QueryUtil; + +public class ColumnInfo extends BaseColumnInfo { + private Column column; + + public ColumnInfo(Field field) { + this.field = field; + this.column = AnnotationUtils.findAnnotation(this.field, Column.class); + this.query = AnnotationUtils.findAnnotation(this.field, Query.class); + this.columnName = this.column.name(); + this.fieldName = this.field.getName(); + this.field.setAccessible(true); + this.querySql = this.getQuerySql(this.query); + } + + public boolean isPrimaryKey() { + return this.column.primaryKey(); + } + + public String getQuerySql(Query query) { + return QueryUtil.getQuerySql(this.getColumnName(), getTemplate(), query); + } + + public String getQuerySql() { + return this.querySql; + } + + public String getFullyQualifiedColumnName(String tableName) { + return tableName + "." + this.getColumnName(); + } +} diff --git a/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/domain/MapColumnInfo.java b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/domain/MapColumnInfo.java new file mode 100644 index 0000000..acf6f9a --- /dev/null +++ b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/domain/MapColumnInfo.java @@ -0,0 +1,45 @@ +package com.boyue.mybatis.domain; + +import java.lang.reflect.Field; + +import org.springframework.core.annotation.AnnotationUtils; + +import com.boyue.mybatis.annotation.ColumnMap; +import com.boyue.mybatis.annotation.Query; +import com.boyue.mybatis.utils.QueryUtil; + +/** + * 数据库关联字段信息 + * + * @author Dftre + */ +public class MapColumnInfo extends BaseColumnInfo { + private ColumnMap columnMap; + + public MapColumnInfo(Field field) { + this.field = field; + this.columnMap = AnnotationUtils.findAnnotation(this.field, ColumnMap.class); + this.columnName = this.columnMap.name(); + this.query = AnnotationUtils.findAnnotation(this.field, Query.class); + this.fieldName = this.field.getName(); + this.field.setAccessible(true); + this.querySql = this.getQuerySql(this.query); + } + + public ColumnMap getJoin() { + return this.columnMap; + } + + public String getQuerySql(Query query) { + return QueryUtil.getQuerySql(this.getColumnName(), getTemplate(), query); + } + + public String getQuerySql() { + return this.columnMap.target() + "." + this.querySql; + } + + public String getFullyQualifiedColumnName() { + return this.columnMap.target() + "." + this.getColumnName(); + } + +} diff --git a/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/domain/TableInfo.java b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/domain/TableInfo.java new file mode 100644 index 0000000..a8a82fb --- /dev/null +++ b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/domain/TableInfo.java @@ -0,0 +1,206 @@ +package com.boyue.mybatis.domain; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.springframework.core.annotation.AnnotationUtils; + +import com.boyue.common.utils.StringUtils; +import com.boyue.mybatis.annotation.Column; +import com.boyue.mybatis.annotation.ColumnMap; +import com.boyue.mybatis.annotation.EnableTableMap; +import com.boyue.mybatis.annotation.Table; +import com.boyue.mybatis.utils.QueryUtil; + +/** + * 数据库表信息 + * + * @author Dftre + */ +public class TableInfo { + private String tableName; + private EnableTableMap enableTableMap; + private Table table; + private List columns = new ArrayList<>(); // 所有标记column注解的字段 + private List primaryKeys = new ArrayList<>(); + private List mapColumns = new ArrayList<>(); + private Set joinSql = new HashSet<>(); + boolean hasDataScopeValue = false; + + public TableInfo(Class cls) { + this.table = AnnotationUtils.findAnnotation(cls, Table.class); + if (this.table == null) { + throw new RuntimeException("error , not find tableName"); + } + this.tableName = this.table.name(); + this.enableTableMap = AnnotationUtils.findAnnotation(cls, EnableTableMap.class); + + Arrays.stream(cls.getDeclaredFields()) + .filter(field -> AnnotationUtils.findAnnotation(field, Column.class) != null) + .map(ColumnInfo::new) + .forEach(this.columns::add); + + Arrays.stream(cls.getDeclaredFields()) + .filter(field -> AnnotationUtils.findAnnotation(field, ColumnMap.class) != null) + .map(MapColumnInfo::new) + .forEach(this.mapColumns::add); + + this.getColumns().stream() + .filter(ColumnInfo::isPrimaryKey) + .forEach(this.primaryKeys::add); + + this.getMapColumns().stream() + .map(MapColumnInfo::getJoin) + .map(join -> { + String lf = join.onLeft(); + String rf = join.onRight(); + String lt = this.getTableNameT(); + String rt = join.target(); + if (StringUtils.isEmpty(lf) || StringUtils.isEmpty(rf)) { + lf = join.on(); + rf = join.on(); + } + return QueryUtil.getJoinSql(lt, rt, lf, rf); + }) + .forEach(joinSql::add); + + if (this.enableTableMap != null) { + if (StringUtils.isNotEmpty(this.enableTableMap.user())) { + String lf = this.enableTableMap.userOnLeft(); + String rf = this.enableTableMap.userOnRight(); + String lt = this.getTableNameT(); + String rt = this.enableTableMap.user(); + if (StringUtils.isEmpty(lf) || StringUtils.isEmpty(rf)) { + lf = this.enableTableMap.userOn(); + rf = this.enableTableMap.userOn(); + } + this.joinSql.add("sys_user " + QueryUtil.getJoinSql(lt, rt, lf, rf)); + this.hasDataScopeValue = true; + } + + if (StringUtils.isNotEmpty(this.enableTableMap.dept())) { + String lf = this.enableTableMap.deptOnLeft(); + String rf = this.enableTableMap.deptOnRight(); + String lt = StringUtils.isNotEmpty(this.enableTableMap.deptFrom()) ? this.enableTableMap.deptFrom() + : this.getTableNameT(); + String rt = this.enableTableMap.dept(); + if (StringUtils.isEmpty(lf) || StringUtils.isEmpty(rf)) { + lf = this.enableTableMap.deptOn(); + rf = this.enableTableMap.deptOn(); + } + this.joinSql.add("sys_dept " + QueryUtil.getJoinSql(lt, rt, lf, rf)); + this.hasDataScopeValue = true; + } + } + } + + public String[] getOrderBy() { + return this.table.orderBy(); + } + + public boolean hasDataScope() { + return this.hasDataScopeValue; + } + + public Set getJoinSql() { + return this.joinSql; + } + + public Boolean isEnbleMap() { + return this.enableTableMap != null; + } + + public String getTableNameT() { + return this.enableTableMap.name(); + } + + public String getTableNameFrom() { + if (this.isEnbleMap()){ + return this.tableName + " " + this.enableTableMap.name(); + }else{ + return this.tableName; + } + } + + public List getQueryColumnNames() { + List columns = Arrays.asList(this.table.columns()); + if (columns.size() <= 0) { + columns = this.columns.stream() + .map(ColumnInfo::getColumnName) + .collect(Collectors.toList()); + } + if (this.isEnbleMap()) { + columns = columns.stream() + .map(column -> this.getTableNameT() + "." + column) + .collect(Collectors.toList()); + this.mapColumns.stream() + .map(MapColumnInfo::getFullyQualifiedColumnName) + .forEach(columns::add); + } + + return columns; + + } + + public List getColumnNames() { + List columns = this.columns.stream() + .map(ColumnInfo::getColumnName) + .collect(Collectors.toList()); + + if (this.isEnbleMap()) { + columns = columns.stream() + .map(column -> this.getTableNameT() + "." + column) + .collect(Collectors.toList()); + this.mapColumns.stream() + .map(MapColumnInfo::getFullyQualifiedColumnName) + .forEach(columns::add); + } + + return columns; + } + + public List getColumns() { + return columns; + } + + public List getMapColumns() { + return mapColumns; + } + + public List getNotNullColumnsForQuery(T entity) { + return this.columns.stream() + .filter(column -> column.fieldQueryIsNotNull(entity)) + .collect(Collectors.toList()); + } + + public List getNotNullColumns(T entity) { + return this.columns.stream() + .filter(column -> column.fieldIsNotNull(entity)) + .collect(Collectors.toList()); + } + + public List getNotNullMapColumnsForQuery(T entity) { + return this.mapColumns.stream() + .filter(column -> column.fieldQueryIsNotNull(entity)) + .collect(Collectors.toList()); + } + + public List getNotNullMapColumns(T entity) { + return this.mapColumns.stream() + .filter(column -> column.fieldIsNotNull(entity)) + .collect(Collectors.toList()); + } + + public List getPrimaryKeys() { + return primaryKeys; + } + + public String getTableName() { + return tableName; + } + +} diff --git a/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/enums/QueryEnum.java b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/enums/QueryEnum.java new file mode 100644 index 0000000..f42576f --- /dev/null +++ b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/enums/QueryEnum.java @@ -0,0 +1,29 @@ +package com.boyue.mybatis.enums; + +/** + * 数据库查询枚举 + * + * @author Dftre + */ +public enum QueryEnum { + eq, // 等于 = + ne, // 不等于 <> + gt, // 大于 > + ge, // 大于等于 >= + lt, // 小于 < + le, // 小于等于 <= + like, // 模糊查询 + notLike, + likeLeft, // 左模糊查询 + likeRight, // 右模糊查询 + notLikeLeft, + notLikeRight, + in, // in + notIn, // not in + between, // between and + notBetween, // not between and + isNull, //is null + isNotNull, + inSql, + notInSql; +} diff --git a/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/mapper/JPAMapper.java b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/mapper/JPAMapper.java new file mode 100644 index 0000000..615fc58 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/mapper/JPAMapper.java @@ -0,0 +1,30 @@ +package com.boyue.mybatis.mapper; + +import java.util.List; + +import org.apache.ibatis.annotations.DeleteProvider; +import org.apache.ibatis.annotations.InsertProvider; +import org.apache.ibatis.annotations.Options; +import org.apache.ibatis.annotations.SelectProvider; +import org.apache.ibatis.annotations.UpdateProvider; + +import com.boyue.common.core.domain.BaseEntity; +import com.boyue.mybatis.utils.SQLGenerator; + +public interface JPAMapper { + @SelectProvider(value = SQLGenerator.class, method = SQLGenerator.Method.SELECT_BY_ID) + public T selectById(T entity); + + @SelectProvider(value = SQLGenerator.class, method = SQLGenerator.Method.LIST) + public List selectList(T entity); + + @InsertProvider(value = SQLGenerator.class, method = SQLGenerator.Method.INSERT) + @Options(useGeneratedKeys = true) + public int insert(T entity); + + @UpdateProvider(value = SQLGenerator.class, method = SQLGenerator.Method.UPDATE) + public int update(T entity); + + @DeleteProvider(value = SQLGenerator.class, method = SQLGenerator.Method.DELETE_BY_ID) + public int deleteById(T entity); +} diff --git a/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/service/JPAService.java b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/service/JPAService.java new file mode 100644 index 0000000..3f6e807 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/service/JPAService.java @@ -0,0 +1,18 @@ +package com.boyue.mybatis.service; + +import java.util.List; + +import com.boyue.common.core.domain.BaseEntity; + +public interface JPAService { + + public T get(T entity); + + public List list(T entity); + + public int add(T entity); + + public int update(T entity); + + public int del(T entity); +} diff --git a/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/service/impl/JPAServiceImpl.java b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/service/impl/JPAServiceImpl.java new file mode 100644 index 0000000..387bf26 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/service/impl/JPAServiceImpl.java @@ -0,0 +1,40 @@ +package com.boyue.mybatis.service.impl; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; + +import com.boyue.common.core.domain.BaseEntity; +import com.boyue.mybatis.mapper.JPAMapper; +import com.boyue.mybatis.service.JPAService; + +public class JPAServiceImpl implements JPAService { + + @Autowired + private JPAMapper mapper; + + @Override + public T get(T entity) { + return mapper.selectById(entity); + }; + + @Override + public List list(T entity) { + return mapper.selectList(entity); + } + + @Override + public int add(T entity) { + return mapper.insert(entity); + } + + @Override + public int update(T entity) { + return mapper.update(entity); + } + + @Override + public int del(T entity) { + return mapper.deleteById(entity); + } +} diff --git a/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/utils/QueryUtil.java b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/utils/QueryUtil.java new file mode 100644 index 0000000..1e8265c --- /dev/null +++ b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/utils/QueryUtil.java @@ -0,0 +1,105 @@ +package com.boyue.mybatis.utils; + +import java.io.Serializable; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import com.boyue.common.core.domain.BaseEntity; +import com.boyue.mybatis.annotation.Query; + +public class QueryUtil { + + /** + * 判断该字段是否可以被列为查询条件 + * @param entity 参与判断的对象 + * @param field 参与判断的字段 + * @param query 参与判断的查询注解 + * @return + */ + public static boolean fieldQueryIsNotNull(Object entity, Field field, Query query) { + try { + if (query == null)return false; + BaseEntity baseEntity = (BaseEntity) entity; + Map map = baseEntity.getParams(); + return switch(query.operation()){ + case between -> map.get(query.sections()[0]) != null && map.get(query.sections()[1]) != null; + case in -> { + Object section = map.get(query.section()); + if (section == null) yield false; + if (section instanceof String) { + List list = new ArrayList<>(); + for (String split : ((String) section).split(",")) { + list.add("\"" + split + "\""); + } + map.put(query.section() + "_operation", String.join(",", list)); + yield true; + } else if (section instanceof Collection) { + List list = new ArrayList<>(); + for (Object split : ((Collection) section)) { + list.add("\"" + split.toString() + "\""); + } + map.put(query.section() + "_operation", String.join(",", list)); + yield true; + } else { + yield false; + } + } + default ->field.get(entity) != null; + }; + } catch (IllegalAccessException e) { + throw new RuntimeException("Failed to access field for building query conditions.", e); + } + } + + public static String getJoinSql(String leftTable, String rightTable, String leftField, String rightField) { + return rightTable + " on " + + leftTable + "." + leftField + " = " + + rightTable + "." + rightField; + } + + public static String getQuerySql(String column, String iField, Query query) { + if (query == null) + return ""; + String begin = "#{params." + query.sections()[0] + "}"; + String end = "#{params." + query.sections()[1] + "}"; + String inParams = "${params." + query.section() + "_operation" + "}"; + return switch (query.operation()) { + case eq -> column + " = " + iField; + case ne -> column + " <> " + iField; + case gt -> column + " > " + iField; + case ge -> column + " >= " + iField; + case le -> column + " < " + iField; + case lt -> column + " <= " + iField; + case between -> column + " between " + begin + " and " + end; + case notBetween -> column + " not between " + begin + " and " + end; + case like -> column + " like concat('%'," + iField + ",'%')"; + case notLike -> column + "not like concat('%'," + iField + ",'%')"; + case likeLeft -> column + "like concat('%'," + iField + ")"; + case likeRight -> column + "like concat(" + iField + ",'%')"; + case notLikeLeft -> column + "not like concat('%'," + iField + ")"; + case notLikeRight -> column + "not like concat(" + iField + ",'%')"; + case isNull -> column + " is null"; + case isNotNull -> column + " is not null"; + case in -> column + " in (" + inParams + ")"; + case notIn -> column + " not in (" + inParams + ")"; + case inSql -> column + " in (" + query.sql() + ")"; + case notInSql -> column + " not in (" + query.sql() + ")"; + default -> throw new IllegalArgumentException( + "Unsupported operation: " + query.operation()); + }; + } + + public static String listToInSQL(Collection oList) { + StringBuilder sb = new StringBuilder(); + sb.append("("); + for (Serializable o : oList) { + sb.append(o).append(","); + } + sb.deleteCharAt(sb.length() - 1); + sb.append(")"); + return sb.toString(); + } +} diff --git a/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/utils/QueryWrapperUtil.java b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/utils/QueryWrapperUtil.java new file mode 100644 index 0000000..556e618 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/utils/QueryWrapperUtil.java @@ -0,0 +1,84 @@ +package com.boyue.mybatis.utils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.boyue.common.core.domain.BaseEntity; +import com.boyue.mybatis.annotation.Query; +import com.boyue.mybatis.domain.TableInfo; + +public class QueryWrapperUtil { + public static List getArrayFromParam(Object obj) { + List list = new ArrayList<>(); + if (obj instanceof String) { + for (String split : ((String) obj).split(",")) { + list.add(split); + } + } else if (obj instanceof Collection) { + for (Object split : ((Collection) obj)) { + list.add(split.toString()); + } + } + return list; + } + + public static QueryWrapper initQueryWrapper(T entity) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + TableInfo tableInfo = TableContainer.getTableInfo(entity); + Map params = entity.getParams(); + + tableInfo.getNotNullColumnsForQuery(entity).stream() + .forEach(field -> { + Object fieldValue = null; + try { + fieldValue = field.getField().get(entity); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + Query queryAnnotation = field.getQuery(); + switch (queryAnnotation.operation()) { + case eq -> queryWrapper.eq(field.getUnqualifiedColumnName(), fieldValue); + case ne -> queryWrapper.ne(field.getUnqualifiedColumnName(), fieldValue); + case gt -> queryWrapper.gt(field.getUnqualifiedColumnName(), fieldValue); + case ge -> queryWrapper.ge(field.getUnqualifiedColumnName(), fieldValue); + case le -> queryWrapper.le(field.getUnqualifiedColumnName(), fieldValue); + case lt -> queryWrapper.lt(field.getUnqualifiedColumnName(), fieldValue); + case between -> { + String begin = queryAnnotation.sections()[0]; + String end = queryAnnotation.sections()[1]; + queryWrapper.between(field.getUnqualifiedColumnName(), params.get(begin), params.get(end)); + } + case notBetween -> { + String begin = queryAnnotation.sections()[0]; + String end = queryAnnotation.sections()[1]; + queryWrapper.notBetween(field.getUnqualifiedColumnName(), params.get(begin), + params.get(end)); + } + case like -> queryWrapper.like(field.getUnqualifiedColumnName(), fieldValue); + case notLike -> queryWrapper.notLike(field.getUnqualifiedColumnName(), fieldValue); + case likeLeft -> queryWrapper.likeLeft(field.getUnqualifiedColumnName(), fieldValue); + case likeRight -> queryWrapper.likeRight(field.getUnqualifiedColumnName(), fieldValue); + case notLikeLeft -> queryWrapper.notLikeLeft(field.getUnqualifiedColumnName(), fieldValue); + case notLikeRight -> queryWrapper.notLikeRight(field.getUnqualifiedColumnName(), fieldValue); + case isNull -> queryWrapper.isNull(field.getUnqualifiedColumnName()); + case isNotNull -> queryWrapper.isNotNull(field.getUnqualifiedColumnName()); + case in -> queryWrapper.in(field.getUnqualifiedColumnName(), + getArrayFromParam(params.get(queryAnnotation.section()))); + case notIn -> queryWrapper.notIn(field.getUnqualifiedColumnName(), + getArrayFromParam(params.get(queryAnnotation.section()))); + case inSql -> queryWrapper.inSql(field.getUnqualifiedColumnName(), queryAnnotation.sql()); + case notInSql -> queryWrapper.notInSql(field.getUnqualifiedColumnName(), queryAnnotation.sql()); + default -> + throw new IllegalArgumentException( + "Unsupported operation: " + queryAnnotation.operation()); + } + }); + + return queryWrapper; + } +} diff --git a/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/utils/SQLGenerator.java b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/utils/SQLGenerator.java new file mode 100644 index 0000000..4e8eac4 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/utils/SQLGenerator.java @@ -0,0 +1,196 @@ +package com.boyue.mybatis.utils; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collection; + +import org.apache.ibatis.jdbc.SQL; + +import com.boyue.common.core.domain.BaseEntity; +import com.boyue.common.utils.StringUtils; +import com.boyue.mybatis.domain.ColumnInfo; +import com.boyue.mybatis.domain.MapColumnInfo; +import com.boyue.mybatis.domain.TableInfo; + +public class SQLGenerator { + + public static class Method { + public final static String LIST = "list"; + public final static String INSERT = "insert"; + public final static String UPDATE = "update"; + public final static String SELECT_BY_ID = "selectById"; + public final static String DELETE_BY_ID = "deleteById"; + public final static String SELECT_BY_IDS = "selectByIds"; + public final static String DELETE_BY_IDS = "deleteByIds"; + public final static String SELECT_BY_ID_WITH_ENTITY = "selectByIdWithEntity"; + public final static String DELETE_BY_ID_WITH_ENTITY = "deleteByIdWithEntity"; + } + + public static String list(T entity) { + SQL sql = new SQL(); + TableInfo tableInfo = TableContainer.getTableInfo(entity); + sql.SELECT(String.join(",", tableInfo.getQueryColumnNames())) + .FROM(tableInfo.getTableNameFrom()); + + if (tableInfo.isEnbleMap()) { + tableInfo.getJoinSql().stream() + .filter(StringUtils::isNotEmpty) + .forEach(sql::LEFT_OUTER_JOIN); + tableInfo.getNotNullMapColumnsForQuery(entity).stream() + .map(MapColumnInfo::getQuerySql) + .forEach(sql::WHERE); + tableInfo.getNotNullColumnsForQuery(entity).stream() + .map(ColumnInfo::getQuerySql) + .map(query -> tableInfo.getTableNameT() + "." + query) + .forEach(sql::WHERE); + if (tableInfo.hasDataScope()) { + sql.WHERE("1=1 ${params.dataScope}"); + } + + Arrays.stream(tableInfo.getOrderBy()) + .filter(StringUtils::isNotEmpty) + .map(order -> tableInfo.getTableNameT() + "." + order) + .forEach(sql::ORDER_BY); + } else { + tableInfo.getNotNullColumnsForQuery(entity).stream() + .map(ColumnInfo::getQuerySql) + .filter(StringUtils::isNotEmpty) + .forEach(sql::WHERE); + Arrays.stream(tableInfo.getOrderBy()) + .filter(StringUtils::isNotEmpty) + .forEach(sql::ORDER_BY); + } + return sql.toString(); + } + + public static String insert(T entity) { + SQL sql = new SQL(); + TableInfo tableInfo = TableContainer.getTableInfo(entity); + sql.INSERT_INTO(tableInfo.getTableName()); + tableInfo.getNotNullColumns(entity).stream() + .forEach(column -> sql.VALUES(column.getColumnName(), column.getTemplate())); + return sql.toString(); + } + + public static String update(T entity) { + SQL sql = new SQL(); + TableInfo tableInfo = TableContainer.getTableInfo(entity); + sql.UPDATE(tableInfo.getTableName()); + tableInfo.getPrimaryKeys().stream() + .map(column -> column.getColumnName() + " = " + column.getTemplate()) + .forEach(sql::WHERE); + if (tableInfo.hasDataScope()) { + sql.WHERE("1=1 ${params.dataScope}"); + } + tableInfo.getNotNullColumns(entity).stream() + .filter(column -> !column.isPrimaryKey()) + .map(column -> column.getColumnName() + " = " + column.getTemplate()) + .forEach(sql::SET); + return sql.toString(); + } + + public static String deleteByIdWithEntity(T entity) { + SQL sql = new SQL(); + TableInfo tableInfo = TableContainer.getTableInfo(entity); + sql.DELETE_FROM(tableInfo.getTableName()); + tableInfo.getPrimaryKeys().stream() + .map(column -> column.getColumnName() + " = " + column.getTemplate()) + .forEach(sql::WHERE); + + if (tableInfo.hasDataScope()) { + sql.WHERE("1=1 ${params.dataScope}"); + } + return sql.toString(); + } + + public static String selectByIdWithEntity(T entity) { + SQL sql = new SQL(); + TableInfo tableInfo = TableContainer.getTableInfo(entity); + sql.SELECT(String.join(",", tableInfo.getColumnNames())) + .FROM(tableInfo.getTableNameFrom()); + if (tableInfo.isEnbleMap()) { + tableInfo.getJoinSql().stream() + .forEach(sql::LEFT_OUTER_JOIN); + + tableInfo.getPrimaryKeys().stream() + .map(column -> tableInfo.getTableNameT() + "." + column.getColumnName() + " = " + + column.getTemplate()) + .forEach(sql::WHERE); + } else { + tableInfo.getPrimaryKeys().stream() + .map(column -> column.getColumnName() + " = " + column.getTemplate()) + .forEach(sql::WHERE); + } + if (tableInfo.hasDataScope()) { + sql.WHERE("1=1 ${params.dataScope}"); + } + return sql.toString(); + } + + public static String selectById(Serializable id, Class clz) { + SQL sql = new SQL(); + TableInfo tableInfo = TableContainer.getTableInfo(clz); + sql.SELECT(String.join(",", tableInfo.getColumnNames())) + .FROM(tableInfo.getTableNameFrom()); + if (tableInfo.isEnbleMap()) { + tableInfo.getJoinSql().stream().forEach(sql::LEFT_OUTER_JOIN); + ColumnInfo columnInfo = tableInfo.getPrimaryKeys().get(0); + String columnName = tableInfo.getTableNameT() + "." + columnInfo.getColumnName(); + sql.WHERE(columnName + " = " + id); + } else { + ColumnInfo columnInfo = tableInfo.getPrimaryKeys().get(0); + sql.WHERE(columnInfo.getColumnName() + " = " + id); + } + if (tableInfo.hasDataScope()) { + sql.WHERE("1=1 ${params.dataScope}"); + } + return sql.toString(); + } + + public static String selectByIds(Collection ids, Class clz) { + SQL sql = new SQL(); + TableInfo tableInfo = TableContainer.getTableInfo(clz); + sql.SELECT(String.join(",", tableInfo.getColumnNames())) + .FROM(tableInfo.getTableNameFrom()); + if (tableInfo.isEnbleMap()) { + tableInfo.getJoinSql().stream() + .forEach(sql::LEFT_OUTER_JOIN); + + ColumnInfo columnInfo = tableInfo.getPrimaryKeys().get(0); + String columnName = tableInfo.getTableNameT() + "." + columnInfo.getColumnName(); + sql.WHERE(columnName + " in " + QueryUtil.listToInSQL(ids)); + } else { + ColumnInfo columnInfo = tableInfo.getPrimaryKeys().get(0); + sql.WHERE(columnInfo.getColumnName() + " in " + QueryUtil.listToInSQL(ids)); + } + if (tableInfo.hasDataScope()) { + sql.WHERE("1=1 ${params.dataScope}"); + } + return sql.toString(); + } + + public static String deleteById(Serializable id, Class clz) { + SQL sql = new SQL(); + TableInfo tableInfo = TableContainer.getTableInfo(clz); + sql.DELETE_FROM(tableInfo.getTableName()); + ColumnInfo columnInfo = tableInfo.getPrimaryKeys().get(0); + sql.WHERE(columnInfo.getColumnName() + " = " + id); + if (tableInfo.hasDataScope()) { + sql.WHERE("1=1 ${params.dataScope}"); + } + return sql.toString(); + } + + public static String deleteByIds(Collection ids, Class clz) { + SQL sql = new SQL(); + TableInfo tableInfo = TableContainer.getTableInfo(clz); + sql.DELETE_FROM(tableInfo.getTableName()); + ColumnInfo columnInfo = tableInfo.getPrimaryKeys().get(0); + sql.WHERE(columnInfo.getColumnName() + " in " + QueryUtil.listToInSQL(ids)); + if (tableInfo.hasDataScope()) { + sql.WHERE("1=1 ${params.dataScope}"); + } + return sql.toString(); + } + +} diff --git a/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/utils/TableContainer.java b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/utils/TableContainer.java new file mode 100644 index 0000000..167eed8 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-jpa/src/main/java/com/boyue/mybatis/utils/TableContainer.java @@ -0,0 +1,31 @@ +package com.boyue.mybatis.utils; + +import java.util.HashMap; +import java.util.Map; + +import com.boyue.common.core.domain.BaseEntity; +import com.boyue.mybatis.domain.TableInfo; + +/** + * sql构建工具 + * + * @author Dftre + */ +public class TableContainer { + + private static final Map, TableInfo> tableInfoMap = new HashMap<>(); + + public static TableInfo getTableInfo(T entity) { + Class clazz = entity.getClass(); + return getTableInfo(clazz); + } + + public static TableInfo getTableInfo(Class clazz) { + TableInfo tableInfo = tableInfoMap.get(clazz); + if (tableInfo == null) { + tableInfo = new TableInfo(clazz); + } + return tableInfo; + } + +} diff --git a/boyue-plugins/boyue-mybatis-plus/pom.xml b/boyue-plugins/boyue-mybatis-plus/pom.xml new file mode 100644 index 0000000..76a9c0e --- /dev/null +++ b/boyue-plugins/boyue-mybatis-plus/pom.xml @@ -0,0 +1,32 @@ + + + + boyue-plugins + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-mybatis-plus + + + boyue-mybatis-plus模块 + + + + + + + com.boyue + boyue-common + + + + + com.baomidou + mybatis-plus-spring-boot3-starter + + + + diff --git a/boyue-plugins/boyue-mybatis-plus/src/main/java/com/boyue/mybatisplus/config/MybatisPlusConfig.java b/boyue-plugins/boyue-mybatis-plus/src/main/java/com/boyue/mybatisplus/config/MybatisPlusConfig.java new file mode 100644 index 0000000..e2801e1 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-plus/src/main/java/com/boyue/mybatisplus/config/MybatisPlusConfig.java @@ -0,0 +1,99 @@ +package com.boyue.mybatisplus.config; + +import javax.sql.DataSource; + +import org.apache.ibatis.io.VFS; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.session.SqlSessionFactory; +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.core.io.DefaultResourceLoader; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.autoconfigure.SpringBootVFS; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean; +import com.github.pagehelper.PageInterceptor; +import com.github.pagehelper.autoconfigure.PageHelperStandardProperties; +import com.boyue.common.service.mybatis.CreateSqlSessionFactory; +import com.boyue.common.utils.MybatisUtils; +import com.boyue.common.utils.StringUtils; + +/** + * Mybatis Plus 配置 + * + * @author boyue + */ +@Configuration +public class MybatisPlusConfig { + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + // 分页插件 + interceptor.addInnerInterceptor(paginationInnerInterceptor()); + // 乐观锁插件 + interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor()); + // 阻断插件 + interceptor.addInnerInterceptor(blockAttackInnerInterceptor()); + return interceptor; + } + + /** + * 分页插件,自动识别数据库类型 https://baomidou.com/guide/interceptor-pagination.html + */ + public PaginationInnerInterceptor paginationInnerInterceptor() { + PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(); + // 设置数据库类型为mysql + paginationInnerInterceptor.setDbType(DbType.MYSQL); + // 设置最大单页限制数量,默认 500 条,-1 不受限制 + paginationInnerInterceptor.setMaxLimit(-1L); + return paginationInnerInterceptor; + } + + /** + * 乐观锁插件 https://baomidou.com/guide/interceptor-optimistic-locker.html + */ + public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() { + return new OptimisticLockerInnerInterceptor(); + } + + /** + * 如果是对全表的删除或更新操作,就会终止该操作 + * https://baomidou.com/guide/interceptor-block-attack.html + */ + public BlockAttackInnerInterceptor blockAttackInnerInterceptor() { + return new BlockAttackInnerInterceptor(); + } + + @Bean + @ConditionalOnProperty(prefix = "createSqlSessionFactory", name = "use", havingValue = "mybatis-plus") + public CreateSqlSessionFactory createSqlSessionFactory(PageHelperStandardProperties packageHelperStandardProperties) { + return new CreateSqlSessionFactory() { + public SqlSessionFactory createSqlSessionFactory(Environment env, DataSource dataSource) throws Exception { + String typeAliasesPackage = env.getProperty("mybatis-plus.typeAliasesPackage"); + String mapperLocations = env.getProperty("mybatis-plus.mapperLocations"); + String configLocation = env.getProperty("mybatis-plus.configLocation"); + typeAliasesPackage = MybatisUtils.setTypeAliasesPackage(typeAliasesPackage); + VFS.addImplClass(SpringBootVFS.class); + + final MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean(); + sessionFactory.setPlugins(new Interceptor[] { mybatisPlusInterceptor() }); + sessionFactory.setDataSource(dataSource); + sessionFactory.setTypeAliasesPackage(typeAliasesPackage); + sessionFactory.setMapperLocations( + MybatisUtils.resolveMapperLocations(StringUtils.split(mapperLocations, ","))); + sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation)); + PageInterceptor interceptor = new PageInterceptor(); + interceptor.setProperties(packageHelperStandardProperties.getProperties()); + sessionFactory.addPlugins(interceptor); + return sessionFactory.getObject(); + } + }; + } + +} diff --git a/boyue-plugins/boyue-mybatis-plus/src/main/java/com/boyue/mybatisplus/controller/SysStudentController.java b/boyue-plugins/boyue-mybatis-plus/src/main/java/com/boyue/mybatisplus/controller/SysStudentController.java new file mode 100644 index 0000000..2f1ac70 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-plus/src/main/java/com/boyue/mybatisplus/controller/SysStudentController.java @@ -0,0 +1,107 @@ +package com.boyue.mybatisplus.controller; + + +import java.util.Arrays; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.boyue.common.annotation.Log; +import com.boyue.common.core.controller.BaseController; +import com.boyue.common.core.domain.AjaxResult; +import com.boyue.common.core.page.TableDataInfo; +import com.boyue.common.enums.BusinessType; +import com.boyue.common.utils.poi.ExcelUtil; +import com.boyue.mybatisplus.domain.SysStudent; +import com.boyue.mybatisplus.service.ISysStudentService; + +/** + * 学生信息Controller + * + * @author boyue + */ +@RestController +@RequestMapping("/system/student") +public class SysStudentController extends BaseController +{ + @Autowired + private ISysStudentService sysStudentService; + + /** + * 查询学生信息列表 + */ + @PreAuthorize("@ss.hasPermi('system:student:list')") + @GetMapping("/list") + public TableDataInfo list(SysStudent sysStudent) + { + Page page = new Page(1,2); + List list = sysStudentService.list(page); + return getDataTable(list); + } + + /** + * 导出学生信息列表 + */ + @PreAuthorize("@ss.hasPermi('system:student:export')") + @Log(title = "学生信息", businessType = BusinessType.EXPORT) + @GetMapping("/export") + public AjaxResult export(SysStudent sysStudent) + { + List list = sysStudentService.queryList(sysStudent); + ExcelUtil util = new ExcelUtil(SysStudent.class); + return util.exportExcel(list, "student"); + } + + /** + * 获取学生信息详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:student:query')") + @GetMapping(value = "/{studentId}") + public AjaxResult getInfo(@PathVariable("studentId") Long studentId) + { + return AjaxResult.success(sysStudentService.getById(studentId)); + } + + /** + * 新增学生信息 + */ + @PreAuthorize("@ss.hasPermi('system:student:add')") + @Log(title = "学生信息", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody SysStudent sysStudent) + { + return toAjax(sysStudentService.save(sysStudent)); + } + + /** + * 修改学生信息 + */ + @PreAuthorize("@ss.hasPermi('system:student:edit')") + @Log(title = "学生信息", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody SysStudent sysStudent) + { + return toAjax(sysStudentService.updateById(sysStudent)); + } + + /** + * 删除学生信息 + */ + @PreAuthorize("@ss.hasPermi('system:student:remove')") + @Log(title = "学生信息", businessType = BusinessType.DELETE) + @DeleteMapping("/{studentIds}") + public AjaxResult remove(@PathVariable Long[] studentIds) + { + return toAjax(sysStudentService.removeByIds(Arrays.asList(studentIds))); + } +} \ No newline at end of file diff --git a/boyue-plugins/boyue-mybatis-plus/src/main/java/com/boyue/mybatisplus/domain/SysStudent.java b/boyue-plugins/boyue-mybatis-plus/src/main/java/com/boyue/mybatisplus/domain/SysStudent.java new file mode 100644 index 0000000..afd4b7f --- /dev/null +++ b/boyue-plugins/boyue-mybatis-plus/src/main/java/com/boyue/mybatisplus/domain/SysStudent.java @@ -0,0 +1,132 @@ +package com.boyue.mybatisplus.domain; + +import java.io.Serializable; +import java.util.Date; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.boyue.common.annotation.Excel; + +/** + * 学生信息对象 sys_student + * + * @author boyue + */ +@TableName(value = "sys_student") +public class SysStudent implements Serializable +{ + @TableField(exist = false) + private static final long serialVersionUID = 1L; + + /** 编号 */ + @TableId(type = IdType.AUTO) + private Long studentId; + + /** 学生名称 */ + @Excel(name = "学生名称") + private String studentName; + + /** 年龄 */ + @Excel(name = "年龄") + private Integer studentAge; + + /** 爱好(0代码 1音乐 2电影) */ + @Excel(name = "爱好", readConverterExp = "0=代码,1=音乐,2=电影") + private String studentHobby; + + /** 性别(0男 1女 2未知) */ + @Excel(name = "性别", readConverterExp = "0=男,1=女,2=未知") + private String studentSex; + + /** 状态(0正常 1停用) */ + @Excel(name = "状态", readConverterExp = "0=正常,1=停用") + private String studentStatus; + + /** 生日 */ + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "生日", width = 30, dateFormat = "yyyy-MM-dd") + private Date studentBirthday; + + public void setStudentId(Long studentId) + { + this.studentId = studentId; + } + + public Long getStudentId() + { + return studentId; + } + public void setStudentName(String studentName) + { + this.studentName = studentName; + } + + public String getStudentName() + { + return studentName; + } + public void setStudentAge(Integer studentAge) + { + this.studentAge = studentAge; + } + + public Integer getStudentAge() + { + return studentAge; + } + public void setStudentHobby(String studentHobby) + { + this.studentHobby = studentHobby; + } + + public String getStudentHobby() + { + return studentHobby; + } + public void setStudentSex(String studentSex) + { + this.studentSex = studentSex; + } + + public String getStudentSex() + { + return studentSex; + } + public void setStudentStatus(String studentStatus) + { + this.studentStatus = studentStatus; + } + + public String getStudentStatus() + { + return studentStatus; + } + public void setStudentBirthday(Date studentBirthday) + { + this.studentBirthday = studentBirthday; + } + + public Date getStudentBirthday() + { + return studentBirthday; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("studentId", getStudentId()) + .append("studentName", getStudentName()) + .append("studentAge", getStudentAge()) + .append("studentHobby", getStudentHobby()) + .append("studentSex", getStudentSex()) + .append("studentStatus", getStudentStatus()) + .append("studentBirthday", getStudentBirthday()) + .toString(); + } +} \ No newline at end of file diff --git a/boyue-plugins/boyue-mybatis-plus/src/main/java/com/boyue/mybatisplus/mapper/SysStudentMapper.java b/boyue-plugins/boyue-mybatis-plus/src/main/java/com/boyue/mybatisplus/mapper/SysStudentMapper.java new file mode 100644 index 0000000..58f4bf1 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-plus/src/main/java/com/boyue/mybatisplus/mapper/SysStudentMapper.java @@ -0,0 +1,14 @@ +package com.boyue.mybatisplus.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.boyue.mybatisplus.domain.SysStudent; + +/** + * 学生信息Mapper接口 + * + * @author boyue + */ +public interface SysStudentMapper extends BaseMapper +{ + +} \ No newline at end of file diff --git a/boyue-plugins/boyue-mybatis-plus/src/main/java/com/boyue/mybatisplus/service/ISysStudentService.java b/boyue-plugins/boyue-mybatis-plus/src/main/java/com/boyue/mybatisplus/service/ISysStudentService.java new file mode 100644 index 0000000..fcd6d92 --- /dev/null +++ b/boyue-plugins/boyue-mybatis-plus/src/main/java/com/boyue/mybatisplus/service/ISysStudentService.java @@ -0,0 +1,21 @@ +package com.boyue.mybatisplus.service; +import java.util.List; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.boyue.mybatisplus.domain.SysStudent; + +/** + * 学生信息Service接口 + * + * @author boyue + */ +public interface ISysStudentService extends IService +{ + /** + * 查询学生信息列表 + * + * @param sysStudent 学生信息 + * @return 学生信息集合 + */ + public List queryList(SysStudent sysStudent); +} \ No newline at end of file diff --git a/boyue-plugins/boyue-mybatis-plus/src/main/java/com/boyue/mybatisplus/service/impl/SysStudentServiceImpl.java b/boyue-plugins/boyue-mybatis-plus/src/main/java/com/boyue/mybatisplus/service/impl/SysStudentServiceImpl.java new file mode 100644 index 0000000..9452bdb --- /dev/null +++ b/boyue-plugins/boyue-mybatis-plus/src/main/java/com/boyue/mybatisplus/service/impl/SysStudentServiceImpl.java @@ -0,0 +1,43 @@ +package com.boyue.mybatisplus.service.impl; +import java.util.List; + +import org.springframework.stereotype.Service; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.boyue.common.utils.StringUtils; +import com.boyue.mybatisplus.domain.SysStudent; +import com.boyue.mybatisplus.mapper.SysStudentMapper; +import com.boyue.mybatisplus.service.ISysStudentService; + +/** + * 学生信息Service业务层处理 + * + * @author boyue + */ +@Service +public class SysStudentServiceImpl extends ServiceImpl implements ISysStudentService +{ + @Override + public List queryList(SysStudent sysStudent) + { + // 注意:mybatis-plus lambda 模式不支持 eclipse 的编译器 + // LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(); + // queryWrapper.eq(SysStudent::getStudentName, sysStudent.getStudentName()); + QueryWrapper queryWrapper = Wrappers.query(); + if (StringUtils.isNotEmpty(sysStudent.getStudentName())) + { + queryWrapper.eq("student_name", sysStudent.getStudentName()); + } + if (StringUtils.isNotNull(sysStudent.getStudentAge())) + { + queryWrapper.eq("student_age", sysStudent.getStudentAge()); + } + if (StringUtils.isNotEmpty(sysStudent.getStudentHobby())) + { + queryWrapper.eq("student_hobby", sysStudent.getStudentHobby()); + } + return this.list(queryWrapper); + } +} \ No newline at end of file diff --git a/boyue-plugins/boyue-netty/pom.xml b/boyue-plugins/boyue-netty/pom.xml new file mode 100644 index 0000000..42715f7 --- /dev/null +++ b/boyue-plugins/boyue-netty/pom.xml @@ -0,0 +1,30 @@ + + + + boyue-plugins + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-netty + + + 19 + 19 + UTF-8 + + + + + io.netty + netty-all + + + com.boyue + boyue-common + + + diff --git a/boyue-plugins/boyue-netty/src/main/java/com/boyue/netty/websocket/NettyServerRunner.java b/boyue-plugins/boyue-netty/src/main/java/com/boyue/netty/websocket/NettyServerRunner.java new file mode 100644 index 0000000..86096c3 --- /dev/null +++ b/boyue-plugins/boyue-netty/src/main/java/com/boyue/netty/websocket/NettyServerRunner.java @@ -0,0 +1,28 @@ +package com.boyue.netty.websocket; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; + +import com.boyue.netty.websocket.nettyServer.NettyWebSocketServer; + +import jakarta.annotation.PreDestroy; + +@Component +public class NettyServerRunner implements ApplicationRunner { + + @Autowired + private NettyWebSocketServer server; + + @Override + public void run(ApplicationArguments args) throws Exception { + server.start(); + } + + @PreDestroy + public void destroy() { + server.shutdown(); + } + +} diff --git a/boyue-plugins/boyue-netty/src/main/java/com/boyue/netty/websocket/annotations/NettyWebSocketEndpoint.java b/boyue-plugins/boyue-netty/src/main/java/com/boyue/netty/websocket/annotations/NettyWebSocketEndpoint.java new file mode 100644 index 0000000..0338960 --- /dev/null +++ b/boyue-plugins/boyue-netty/src/main/java/com/boyue/netty/websocket/annotations/NettyWebSocketEndpoint.java @@ -0,0 +1,12 @@ +package com.boyue.netty.websocket.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface NettyWebSocketEndpoint { + String path(); +} diff --git a/boyue-plugins/boyue-netty/src/main/java/com/boyue/netty/websocket/endpoints/TestNettyWebSocket.java b/boyue-plugins/boyue-netty/src/main/java/com/boyue/netty/websocket/endpoints/TestNettyWebSocket.java new file mode 100644 index 0000000..3d0be2f --- /dev/null +++ b/boyue-plugins/boyue-netty/src/main/java/com/boyue/netty/websocket/endpoints/TestNettyWebSocket.java @@ -0,0 +1,44 @@ +package com.boyue.netty.websocket.endpoints; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.stereotype.Component; + +import com.boyue.netty.websocket.annotations.NettyWebSocketEndpoint; +import com.boyue.netty.websocket.nettyServer.NettyWebSocketEndpointHandler; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.FullHttpMessage; +import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; + +@Component +@NettyWebSocketEndpoint(path = "/test/{seqNumber}") +public class TestNettyWebSocket extends NettyWebSocketEndpointHandler { + + private static final Map map = new ConcurrentHashMap<>(); + + @Override + public void onMessage(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) { + System.out.println(textWebSocketFrame.text()); + } + + @Override + public void onOpen(ChannelHandlerContext channelHandlerContext, FullHttpMessage fullHttpMessage) { + map.put(getPathParam("seqNumber"), channelHandlerContext); + } + + @Override + public void onClose(ChannelHandlerContext channelHandlerContext) { + map.remove(getPathParam("seqNumber")); + } + + @Override + public void onError(ChannelHandlerContext channelHandlerContext, Throwable throwable) { + + } + + public static void send(String seqNumber, String msg) { + sendMsg(map.get(seqNumber), msg); + } +} diff --git a/boyue-plugins/boyue-netty/src/main/java/com/boyue/netty/websocket/nettyServer/NettyWebSocketEndpointHandler.java b/boyue-plugins/boyue-netty/src/main/java/com/boyue/netty/websocket/nettyServer/NettyWebSocketEndpointHandler.java new file mode 100644 index 0000000..17d18cf --- /dev/null +++ b/boyue-plugins/boyue-netty/src/main/java/com/boyue/netty/websocket/nettyServer/NettyWebSocketEndpointHandler.java @@ -0,0 +1,63 @@ +package com.boyue.netty.websocket.nettyServer; + +import java.util.Map; + +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.FullHttpMessage; +import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; + +public abstract class NettyWebSocketEndpointHandler { + + + private Map pathParam; + + private Map urlParam; + + + public static void sendMsg(ChannelHandlerContext context, String msg) { + TextWebSocketFrame textWebSocketFrame = new TextWebSocketFrame(msg); + context.channel().writeAndFlush(textWebSocketFrame); + } + + public abstract void onMessage(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame); + + public abstract void onOpen(ChannelHandlerContext channelHandlerContext, FullHttpMessage fullHttpMessage); + + public abstract void onClose(ChannelHandlerContext channelHandlerContext); + + public abstract void onError(ChannelHandlerContext channelHandlerContext, Throwable throwable); + + + public Map getPathParam() { + return pathParam; + } + + public void setPathParam(Map pathParam) { + this.pathParam = pathParam; + } + + public Map getUrlParam() { + return urlParam; + } + + public void setUrlParam(Map urlParam) { + this.urlParam = urlParam; + } + + public Long getLongPathParam(String key) { + return Long.valueOf(pathParam.get(key)); + } + + public String getPathParam(String key) { + return pathParam.get(key); + } + + public Double getDoublePathParam(String key) { + return Double.parseDouble(pathParam.get(key)); + } + + public void closeChannel(ChannelHandlerContext channelHandlerContext) { + channelHandlerContext.close().addListener(ChannelFutureListener.CLOSE); + } +} diff --git a/boyue-plugins/boyue-netty/src/main/java/com/boyue/netty/websocket/nettyServer/NettyWebSocketServer.java b/boyue-plugins/boyue-netty/src/main/java/com/boyue/netty/websocket/nettyServer/NettyWebSocketServer.java new file mode 100644 index 0000000..f0ef595 --- /dev/null +++ b/boyue-plugins/boyue-netty/src/main/java/com/boyue/netty/websocket/nettyServer/NettyWebSocketServer.java @@ -0,0 +1,89 @@ +package com.boyue.netty.websocket.nettyServer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import com.boyue.netty.websocket.nettyServer.handler.WebSocketHandler; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpServerCodec; +import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; + +@Component +public class NettyWebSocketServer { + + private static final Logger log = LoggerFactory.getLogger(NettyWebSocketServer.class); + private ServerBootstrap serverBootstrap; + private Channel serverChannel; + + @Value("${netty.websocket.maxMessageSize}") + private Long messageSize; + + @Value("${netty.websocket.bossThreads}") + private Long bossThreads; + + @Value("${netty.websocket.workerThreads}") + private Long workerThreads; + + @Value("${netty.websocket.port}") + private Long port; + + @Value("${netty.websocket.enable}") + private Boolean enable; + + public ServerBootstrap start() throws InterruptedException { + if (!enable) { + return null; + } + ServerBootstrap serverBootstrap = new ServerBootstrap(); + NioEventLoopGroup boss = new NioEventLoopGroup(4); + NioEventLoopGroup worker = new NioEventLoopGroup(workerThreads.intValue()); + serverBootstrap.group(boss, worker); + serverBootstrap.channel(NioServerSocketChannel.class); + serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true); + serverBootstrap.childHandler(new ChannelInitializer() { + @Override + protected void initChannel(NioSocketChannel channel) throws Exception { + ChannelPipeline pipeline = channel.pipeline(); + pipeline.addLast(new HttpServerCodec()); + pipeline.addLast(new HttpObjectAggregator(messageSize.intValue())); + pipeline.addLast(new WebSocketHandler()); + pipeline.addLast(new WebSocketServerProtocolHandler("/", true)); + } + }); + ChannelFuture future = serverBootstrap.bind(port.intValue()).sync(); + serverChannel = future.channel(); + log.info("netty for websocket start success, running in port: {}", this.port); + this.serverBootstrap = serverBootstrap; + return this.serverBootstrap; + } + + public void shutdown() { + if (serverChannel != null) { + serverChannel.close().syncUninterruptibly(); + } + if (serverBootstrap != null) { + EventLoopGroup bossGroup = serverBootstrap.config().group(); + EventLoopGroup workerGroup = serverBootstrap.config().childGroup(); + if (bossGroup != null) { + bossGroup.shutdownGracefully().syncUninterruptibly(); + } + if (workerGroup != null) { + workerGroup.shutdownGracefully().syncUninterruptibly(); + } + } + log.info("netty for websocket shudown success"); + } +} diff --git a/boyue-plugins/boyue-netty/src/main/java/com/boyue/netty/websocket/nettyServer/handler/WebSocketHandler.java b/boyue-plugins/boyue-netty/src/main/java/com/boyue/netty/websocket/nettyServer/handler/WebSocketHandler.java new file mode 100644 index 0000000..9bf0772 --- /dev/null +++ b/boyue-plugins/boyue-netty/src/main/java/com/boyue/netty/websocket/nettyServer/handler/WebSocketHandler.java @@ -0,0 +1,167 @@ +package com.boyue.netty.websocket.nettyServer.handler; + +import java.util.concurrent.ConcurrentHashMap; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import com.boyue.common.utils.StringUtils; +import com.boyue.netty.websocket.annotations.NettyWebSocketEndpoint; +import com.boyue.netty.websocket.nettyServer.NettyWebSocketEndpointHandler; +import com.boyue.netty.websocket.utils.CommonUtil; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelId; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.group.ChannelGroup; +import io.netty.channel.group.DefaultChannelGroup; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; +import io.netty.util.concurrent.GlobalEventExecutor; +import jakarta.annotation.PostConstruct; + +import java.lang.reflect.Constructor; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; + +@Component +public class WebSocketHandler extends SimpleChannelInboundHandler { + + @Autowired + private List handlers; + + private static final Map uriHandlerMapper = new ConcurrentHashMap<>(); + + public static final ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); + + public static final Map channelHandlerMap = new ConcurrentHashMap<>(); + + @PostConstruct + private void init() throws URISyntaxException, NoSuchMethodException, SecurityException { + for (NettyWebSocketEndpointHandler handler : handlers) { + Class handlerClass = handler.getClass(); + NettyWebSocketEndpoint annotation = handlerClass.getAnnotation(NettyWebSocketEndpoint.class); + if (annotation == null || StringUtils.isEmpty(annotation.path())) { + throw new RuntimeException("未配置路径的 netty websocket endpoint "); + } + // uriHandlerMap.put(uri.getPath(), handler); + PathMatchModel pathMachModel = parseHandler(annotation.path(), handlerClass); + uriHandlerMapper.put(pathMachModel.path, pathMachModel); + } + } + + @Override + protected void channelRead0(ChannelHandlerContext context, TextWebSocketFrame webSocketFrame) throws Exception { + NettyWebSocketEndpointHandler handler = channelHandlerMap.get(context.channel().id()); + handler.onMessage(context, webSocketFrame); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + channelGroup.add(ctx.channel()); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + NettyWebSocketEndpointHandler handler = channelHandlerMap.get(ctx.channel().id()); + if (handler != null) { + handler.onClose(ctx); + } + + channelHandlerMap.remove(ctx.channel().id()); + channelGroup.remove(ctx.channel()); + } + + @Override + public void channelRegistered(ChannelHandlerContext ctx) throws Exception { + + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + channelHandlerMap.get(ctx.channel().id()).onError(ctx, cause); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (msg instanceof FullHttpRequest) { + FullHttpRequest fullHttpRequest = (FullHttpRequest) msg; + if (channelHandlerMap.get(ctx.channel().id()) != null) { + super.channelRead(ctx, fullHttpRequest); + return; + } + URI uri = new URI(fullHttpRequest.uri()); + PathMatchModel mathPathMachModel = mathPathMachModel(uri.getPath()); + if (mathPathMachModel == null) { + ctx.channel() + .writeAndFlush(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND)); + ctx.close().addListener(ChannelFutureListener.CLOSE); + return; + } + NettyWebSocketEndpointHandler newInstance = (NettyWebSocketEndpointHandler) mathPathMachModel.handlerConstructor + .newInstance(); + if (!(mathPathMachModel.pathParams == null || mathPathMachModel.pathParams.isEmpty())) { + newInstance.setPathParam( + CommonUtil.parsePathParam(uri.getPath(), mathPathMachModel.pathParams, mathPathMachModel.path)); + super.channelRead(ctx, msg); + } + newInstance.setUrlParam(CommonUtil.parseQueryParameters(uri.getQuery())); + + channelHandlerMap.put(ctx.channel().id(), newInstance); + newInstance.onOpen(ctx, fullHttpRequest); + } else if (msg instanceof TextWebSocketFrame) { + super.channelRead(ctx, msg); + } + + } + + private static PathMatchModel parseHandler(String path, Class handlerClass) + throws NoSuchMethodException, SecurityException { + List paramName = new ArrayList<>(); + String[] split = path.split("/"); + for (int index = 1; index < split.length; index++) { + String item = split[index]; + if (item.startsWith("{") && item.endsWith("}")) { + paramName.add(item.substring(1, item.length() - 1).trim()); + split[index] = "?"; + } + } + StringBuilder finalPath = new StringBuilder(""); + for (int index = 1; index < split.length; index++) { + finalPath.append("/").append(split[index]); + } + return new PathMatchModel(paramName, finalPath.toString(), handlerClass.getDeclaredConstructor()); + } + + private static PathMatchModel mathPathMachModel(String uri) { + Map map = new HashMap<>(); + for (String key : uriHandlerMapper.keySet()) { + int mathUri = CommonUtil.mathUri(uri, key); + if (mathUri > 0) { + map.put(mathUri, uriHandlerMapper.get(key)); + } + } + if (map.keySet() == null || map.keySet().isEmpty()) { + return null; + } + Integer max = CommonUtil.getMax(map.keySet()); + return map.get(max); + } + + private static final class PathMatchModel { + private final List pathParams; + + private final String path; + + private final Constructor handlerConstructor; + + public PathMatchModel(List pathParams, String path, Constructor handlerConstructor) { + this.pathParams = pathParams; + this.path = path; + this.handlerConstructor = handlerConstructor; + } + + } +} diff --git a/boyue-plugins/boyue-netty/src/main/java/com/boyue/netty/websocket/utils/CommonUtil.java b/boyue-plugins/boyue-netty/src/main/java/com/boyue/netty/websocket/utils/CommonUtil.java new file mode 100644 index 0000000..f42a5de --- /dev/null +++ b/boyue-plugins/boyue-netty/src/main/java/com/boyue/netty/websocket/utils/CommonUtil.java @@ -0,0 +1,78 @@ +package com.boyue.netty.websocket.utils; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +public class CommonUtil { + + /** + * @param uri + * @param uriTemplates + * @return + */ + public static int mathUri(String uri, String uriTemplate) { + String[] uriSplit = uri.split("/"); + String[] tempalteSplit = uriTemplate.split("/"); + if (uriSplit.length != tempalteSplit.length) { + return -1; + } + int mathLevel = 0; + for (int index = 1; index < tempalteSplit.length; index++) { + if (tempalteSplit[index].equals("?")) { + mathLevel = mathLevel + index; + continue; + } + if (!tempalteSplit[index].equals(uriSplit[index])) { + return -1; + } else { + mathLevel = mathLevel + tempalteSplit.length + 1; + } + } + return mathLevel; + } + + public static Map parseQueryParameters(String query) { + if (query == null || query.isEmpty()) { + return Map.of(); + } + + Map params = new HashMap<>(); + String[] pairs = query.split("&"); + for (String pair : pairs) { + String[] keyValue = pair.split("="); + if (keyValue.length > 1) { + params.put(keyValue[0], keyValue[1]); + } else { + params.put(keyValue[0], ""); + } + } + return params; + } + + public static Map parsePathParam(String uri, List pathParams, String uriTemplate) { + int index = 0; + String[] split = uriTemplate.split("/"); + String[] split2 = uri.split("/"); + Map map = new HashMap<>(); + for (int i = 1; i < split.length; i++) { + if (split[i].equals("?")) { + map.put(pathParams.get(index), split2[i]); + index++; + } + } + return map; + } + + public static Integer getMax(Set set) { + Optional maxNumber = set.stream().max(Integer::compare); + if (maxNumber.isPresent()) { + System.out.println("Max number: " + maxNumber.get()); + } else { + System.out.println("The list is empty"); + } + return maxNumber.get(); + } +} diff --git a/boyue-plugins/boyue-plugins-starter/pom.xml b/boyue-plugins/boyue-plugins-starter/pom.xml new file mode 100644 index 0000000..016fbb2 --- /dev/null +++ b/boyue-plugins/boyue-plugins-starter/pom.xml @@ -0,0 +1,66 @@ + + + + boyue-plugins + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-plugins-starter + + + 中间件 + + + + + + + com.boyue + boyue-common + + + + + com.boyue + boyue-ehcache + + + + + com.boyue + boyue-websocket + + + + + com.boyue + boyue-mybatis-jpa + + + + + com.boyue + boyue-mybatis-plus + + + + com.boyue + boyue-mybatis-interceptor + + + + com.boyue + boyue-netty + + + + + + + diff --git a/boyue-plugins/boyue-websocket/pom.xml b/boyue-plugins/boyue-websocket/pom.xml new file mode 100644 index 0000000..7733196 --- /dev/null +++ b/boyue-plugins/boyue-websocket/pom.xml @@ -0,0 +1,31 @@ + + + + boyue-plugins + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-websocket + + + websocket系统模块 + + + + + com.boyue + boyue-common + + + + + org.springframework.boot + spring-boot-starter-websocket + + + + + diff --git a/boyue-plugins/boyue-websocket/src/main/java/com/boyue/websocket/SemaphoreUtils.java b/boyue-plugins/boyue-websocket/src/main/java/com/boyue/websocket/SemaphoreUtils.java new file mode 100644 index 0000000..fdc6caf --- /dev/null +++ b/boyue-plugins/boyue-websocket/src/main/java/com/boyue/websocket/SemaphoreUtils.java @@ -0,0 +1,59 @@ +package com.boyue.websocket; + +import java.util.concurrent.Semaphore; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 信号量相关处理 + * + * @author boyue + */ +public class SemaphoreUtils +{ + /** + * SemaphoreUtils 日志控制器 + */ + private static final Logger LOGGER = LoggerFactory.getLogger(SemaphoreUtils.class); + + /** + * 获取信号量 + * + * @param semaphore + * @return + */ + public static boolean tryAcquire(Semaphore semaphore) + { + boolean flag = false; + + try + { + flag = semaphore.tryAcquire(); + } + catch (Exception e) + { + LOGGER.error("获取信号量异常", e); + } + + return flag; + } + + /** + * 释放信号量 + * + * @param semaphore + */ + public static void release(Semaphore semaphore) + { + + try + { + semaphore.release(); + } + catch (Exception e) + { + LOGGER.error("释放信号量异常", e); + } + } +} diff --git a/boyue-plugins/boyue-websocket/src/main/java/com/boyue/websocket/WebSocketConfig.java b/boyue-plugins/boyue-websocket/src/main/java/com/boyue/websocket/WebSocketConfig.java new file mode 100644 index 0000000..7fdb7b9 --- /dev/null +++ b/boyue-plugins/boyue-websocket/src/main/java/com/boyue/websocket/WebSocketConfig.java @@ -0,0 +1,20 @@ +package com.boyue.websocket; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; + +/** + * websocket 配置 + * + * @author boyue + */ +@Configuration +public class WebSocketConfig +{ + @Bean + public ServerEndpointExporter serverEndpointExporter() + { + return new ServerEndpointExporter(); + } +} diff --git a/boyue-plugins/boyue-websocket/src/main/java/com/boyue/websocket/WebSocketServer.java b/boyue-plugins/boyue-websocket/src/main/java/com/boyue/websocket/WebSocketServer.java new file mode 100644 index 0000000..09dbba6 --- /dev/null +++ b/boyue-plugins/boyue-websocket/src/main/java/com/boyue/websocket/WebSocketServer.java @@ -0,0 +1,105 @@ +package com.boyue.websocket; + +import java.util.concurrent.Semaphore; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import jakarta.websocket.OnClose; +import jakarta.websocket.OnError; +import jakarta.websocket.OnMessage; +import jakarta.websocket.OnOpen; +import jakarta.websocket.Session; +import jakarta.websocket.server.ServerEndpoint; + +/** + * websocket 消息处理 + * + * @author boyue + */ +@Component +@ServerEndpoint("/websocket/message") +public class WebSocketServer +{ + /** + * WebSocketServer 日志控制器 + */ + private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketServer.class); + + /** + * 默认最多允许同时在线人数100 + */ + public static int socketMaxOnlineCount = 100; + + private static Semaphore socketSemaphore = new Semaphore(socketMaxOnlineCount); + + /** + * 连接建立成功调用的方法 + */ + @OnOpen + public void onOpen(Session session) throws Exception + { + boolean semaphoreFlag = false; + // 尝试获取信号量 + semaphoreFlag = SemaphoreUtils.tryAcquire(socketSemaphore); + if (!semaphoreFlag) + { + // 未获取到信号量 + LOGGER.error("\n 当前在线人数超过限制数- {}", socketMaxOnlineCount); + WebSocketUsers.sendMessageToUserByText(session, "当前在线人数超过限制数:" + socketMaxOnlineCount); + session.close(); + } + else + { + // 添加用户 + WebSocketUsers.put(session.getId(), session); + LOGGER.info("\n 建立连接 - {}", session); + LOGGER.info("\n 当前人数 - {}", WebSocketUsers.getUsers().size()); + WebSocketUsers.sendMessageToUserByText(session, "连接成功"); + } + } + + /** + * 连接关闭时处理 + */ + @OnClose + public void onClose(Session session) + { + LOGGER.info("\n 关闭连接 - {}", session); + // 移除用户 + WebSocketUsers.remove(session.getId()); + // 获取到信号量则需释放 + SemaphoreUtils.release(socketSemaphore); + } + + /** + * 抛出异常时处理 + */ + @OnError + public void onError(Session session, Throwable exception) throws Exception + { + if (session.isOpen()) + { + // 关闭连接 + session.close(); + } + String sessionId = session.getId(); + LOGGER.info("\n 连接异常 - {}", sessionId); + LOGGER.info("\n 异常信息 - {}", exception); + // 移出用户 + WebSocketUsers.remove(sessionId); + // 获取到信号量则需释放 + SemaphoreUtils.release(socketSemaphore); + } + + /** + * 服务器接收到客户端消息时调用的方法 + */ + @OnMessage + public void onMessage(String message, Session session) + { + String msg = message.replace("你", "我").replace("吗", ""); + WebSocketUsers.sendMessageToUserByText(session, msg); + } +} diff --git a/boyue-plugins/boyue-websocket/src/main/java/com/boyue/websocket/WebSocketUsers.java b/boyue-plugins/boyue-websocket/src/main/java/com/boyue/websocket/WebSocketUsers.java new file mode 100644 index 0000000..46bd9a0 --- /dev/null +++ b/boyue-plugins/boyue-websocket/src/main/java/com/boyue/websocket/WebSocketUsers.java @@ -0,0 +1,142 @@ +package com.boyue.websocket; + +import java.io.IOException; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.websocket.Session; + +/** + * websocket 客户端用户集 + * + * @author boyue + */ +public class WebSocketUsers +{ + /** + * WebSocketUsers 日志控制器 + */ + private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketUsers.class); + + /** + * 用户集 + */ + private static Map USERS = new ConcurrentHashMap(); + + /** + * 存储用户 + * + * @param key 唯一键 + * @param session 用户信息 + */ + public static void put(String key, Session session) + { + USERS.put(key, session); + } + + /** + * 移除用户 + * + * @param session 用户信息 + * + * @return 移除结果 + */ + public static boolean remove(Session session) + { + String key = null; + boolean flag = USERS.containsValue(session); + if (flag) + { + Set> entries = USERS.entrySet(); + for (Map.Entry entry : entries) + { + Session value = entry.getValue(); + if (value.equals(session)) + { + key = entry.getKey(); + break; + } + } + } + else + { + return true; + } + return remove(key); + } + + /** + * 移出用户 + * + * @param key 键 + */ + public static boolean remove(String key) + { + LOGGER.info("\n 正在移出用户 - {}", key); + Session remove = USERS.remove(key); + if (remove != null) + { + boolean containsValue = USERS.containsValue(remove); + LOGGER.info("\n 移出结果 - {}", containsValue ? "失败" : "成功"); + return containsValue; + } + else + { + return true; + } + } + + /** + * 获取在线用户列表 + * + * @return 返回用户集合 + */ + public static Map getUsers() + { + return USERS; + } + + /** + * 群发消息文本消息 + * + * @param message 消息内容 + */ + public static void sendMessageToUsersByText(String message) + { + Collection values = USERS.values(); + for (Session value : values) + { + sendMessageToUserByText(value, message); + } + } + + /** + * 发送文本消息 + * + * @param userName 自己的用户名 + * @param message 消息内容 + */ + public static void sendMessageToUserByText(Session session, String message) + { + if (session != null) + { + try + { + session.getBasicRemote().sendText(message); + } + catch (IOException e) + { + LOGGER.error("\n[发送消息异常]", e); + } + } + else + { + LOGGER.info("\n[你已离线]"); + } + } +} diff --git a/boyue-plugins/pom.xml b/boyue-plugins/pom.xml new file mode 100644 index 0000000..b02206a --- /dev/null +++ b/boyue-plugins/pom.xml @@ -0,0 +1,123 @@ + + + + boyuehasfj-java + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-plugins + + + 3.10.8 + 3.5.8 + 4.1.112.Final + 6.0.0 + + + + + + + + + + org.ehcache + ehcache + ${ehcache.version} + + + + com.baomidou + mybatis-plus-spring-boot3-starter + ${mybatis-plus.version} + + + + + com.boyue + boyue-ehcache + ${boyue.version} + + + + + com.boyue + boyue-mybatis-jpa + ${boyue.version} + + + + + com.boyue + boyue-mybatis-plus + ${boyue.version} + + + + + com.boyue + boyue-websocket + ${boyue.version} + + + + + com.boyue + boyue-plugins-starter + ${boyue.version} + + + + com.boyue + boyue-mybatis-interceptor + ${boyue.version} + + + + com.boyue + boyue-netty + ${boyue.version} + + + + io.netty + netty-all + ${netty.version} + + + + + + com.boyue + boyue-atomikos + ${boyue.version} + + + + + + + com.atomikos + transactions-spring-boot3-starter + ${transactions.version} + + + + + + + + boyue-ehcache + boyue-mybatis-jpa + boyue-mybatis-plus + boyue-websocket + boyue-plugins-starter + boyue-mybatis-interceptor + boyue-netty + boyue-atomikos + + pom + diff --git a/boyue-system/pom.xml b/boyue-system/pom.xml new file mode 100644 index 0000000..1a0a9eb --- /dev/null +++ b/boyue-system/pom.xml @@ -0,0 +1,28 @@ + + + + boyuehasfj-java + com.boyue + 3.8.9-G + + 4.0.0 + + boyue-system + + + system系统模块 + + + + + + + com.boyue + boyue-common + + + + + diff --git a/boyue-system/src/main/java/com/boyue/system/domain/SysCache.java b/boyue-system/src/main/java/com/boyue/system/domain/SysCache.java new file mode 100644 index 0000000..fb4779d --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/domain/SysCache.java @@ -0,0 +1,88 @@ +package com.boyue.system.domain; + +import com.boyue.common.utils.StringUtils; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 缓存信息 + * + * @author boyue + */ +@Schema(title = "缓存信息") +public class SysCache +{ + /** 缓存名称 */ + @Schema(title = "缓存名称") + private String cacheName = ""; + + /** 缓存键名 */ + @Schema(title = "缓存键名") + private String cacheKey = ""; + + /** 缓存内容 */ + @Schema(title = "缓存内容") + private String cacheValue = ""; + + /** 备注 */ + @Schema(title = "备注") + private String remark = ""; + + public SysCache() + { + + } + + public SysCache(String cacheName, String remark) + { + this.cacheName = cacheName; + this.remark = remark; + } + + public SysCache(String cacheName, String cacheKey, String cacheValue) + { + this.cacheName = StringUtils.replace(cacheName, ":", ""); + this.cacheKey = StringUtils.replace(cacheKey, cacheName, ""); + this.cacheValue = cacheValue; + } + + public String getCacheName() + { + return cacheName; + } + + public void setCacheName(String cacheName) + { + this.cacheName = cacheName; + } + + public String getCacheKey() + { + return cacheKey; + } + + public void setCacheKey(String cacheKey) + { + this.cacheKey = cacheKey; + } + + public String getCacheValue() + { + return cacheValue; + } + + public void setCacheValue(String cacheValue) + { + this.cacheValue = cacheValue; + } + + public String getRemark() + { + return remark; + } + + public void setRemark(String remark) + { + this.remark = remark; + } +} diff --git a/boyue-system/src/main/java/com/boyue/system/domain/SysConfig.java b/boyue-system/src/main/java/com/boyue/system/domain/SysConfig.java new file mode 100644 index 0000000..c794a5a --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/domain/SysConfig.java @@ -0,0 +1,120 @@ +package com.boyue.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.boyue.common.annotation.Excel; +import com.boyue.common.annotation.Excel.ColumnType; +import com.boyue.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +/** + * 参数配置表 sys_config + * + * @author boyue + */ +@Schema(title = "参数配置表") +public class SysConfig extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 参数主键 */ + @Schema(title = "参数主键") + @Excel(name = "参数主键", cellType = ColumnType.NUMERIC) + private Long configId; + + /** 参数名称 */ + @Schema(title = "参数名称") + @Excel(name = "参数名称") + private String configName; + + /** 参数键名 */ + @Schema(title = "参数键名") + @Excel(name = "参数键名") + private String configKey; + + /** 参数键值 */ + @Schema(title = "参数键值") + @Excel(name = "参数键值") + private String configValue; + + /** 系统内置(Y是 N否) */ + @Schema(title = "系统内置") + @Excel(name = "系统内置", readConverterExp = "Y=是,N=否") + private String configType; + + public Long getConfigId() + { + return configId; + } + + public void setConfigId(Long configId) + { + this.configId = configId; + } + + @NotBlank(message = "参数名称不能为空") + @Size(min = 0, max = 100, message = "参数名称不能超过100个字符") + public String getConfigName() + { + return configName; + } + + public void setConfigName(String configName) + { + this.configName = configName; + } + + @NotBlank(message = "参数键名长度不能为空") + @Size(min = 0, max = 100, message = "参数键名长度不能超过100个字符") + public String getConfigKey() + { + return configKey; + } + + public void setConfigKey(String configKey) + { + this.configKey = configKey; + } + + @NotBlank(message = "参数键值不能为空") + @Size(min = 0, max = 500, message = "参数键值长度不能超过500个字符") + public String getConfigValue() + { + return configValue; + } + + public void setConfigValue(String configValue) + { + this.configValue = configValue; + } + + public String getConfigType() + { + return configType; + } + + public void setConfigType(String configType) + { + this.configType = configType; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("configId", getConfigId()) + .append("configName", getConfigName()) + .append("configKey", getConfigKey()) + .append("configValue", getConfigValue()) + .append("configType", getConfigType()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/boyue-system/src/main/java/com/boyue/system/domain/SysLogininfor.java b/boyue-system/src/main/java/com/boyue/system/domain/SysLogininfor.java new file mode 100644 index 0000000..7386d4b --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/domain/SysLogininfor.java @@ -0,0 +1,157 @@ +package com.boyue.system.domain; + +import java.util.Date; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.boyue.common.annotation.Excel; +import com.boyue.common.annotation.Excel.ColumnType; +import com.boyue.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 系统访问记录表 sys_logininfor + * + * @author boyue + */ +@Schema(title = "系统访问记录表") +public class SysLogininfor extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** ID */ + @Schema(title = "序号") + @Excel(name = "序号", cellType = ColumnType.NUMERIC) + private Long infoId; + + /** 用户账号 */ + @Schema(title = "用户账号") + @Excel(name = "用户账号") + private String userName; + + /** 登录状态 0成功 1失败 */ + @Schema(title = "登录状态") + @Excel(name = "登录状态", readConverterExp = "0=成功,1=失败") + private String status; + + /** 登录IP地址 */ + @Schema(title = "登录地址") + @Excel(name = "登录地址") + private String ipaddr; + + /** 登录地点 */ + @Schema(title = "登录地点") + @Excel(name = "登录地点") + private String loginLocation; + + /** 浏览器类型 */ + @Schema(title = "浏览器") + @Excel(name = "浏览器") + private String browser; + + /** 操作系统 */ + @Schema(title = "操作系统") + @Excel(name = "操作系统") + private String os; + + /** 提示消息 */ + @Schema(title = "提示消息") + @Excel(name = "提示消息") + private String msg; + + /** 访问时间 */ + @Schema(title = "访问时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "访问时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + private Date loginTime; + + public Long getInfoId() + { + return infoId; + } + + public void setInfoId(Long infoId) + { + this.infoId = infoId; + } + + public String getUserName() + { + return userName; + } + + public void setUserName(String userName) + { + this.userName = userName; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getIpaddr() + { + return ipaddr; + } + + public void setIpaddr(String ipaddr) + { + this.ipaddr = ipaddr; + } + + public String getLoginLocation() + { + return loginLocation; + } + + public void setLoginLocation(String loginLocation) + { + this.loginLocation = loginLocation; + } + + public String getBrowser() + { + return browser; + } + + public void setBrowser(String browser) + { + this.browser = browser; + } + + public String getOs() + { + return os; + } + + public void setOs(String os) + { + this.os = os; + } + + public String getMsg() + { + return msg; + } + + public void setMsg(String msg) + { + this.msg = msg; + } + + public Date getLoginTime() + { + return loginTime; + } + + public void setLoginTime(Date loginTime) + { + this.loginTime = loginTime; + } +} diff --git a/boyue-system/src/main/java/com/boyue/system/domain/SysNotice.java b/boyue-system/src/main/java/com/boyue/system/domain/SysNotice.java new file mode 100644 index 0000000..db577e6 --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/domain/SysNotice.java @@ -0,0 +1,111 @@ +package com.boyue.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.boyue.common.core.domain.BaseEntity; +import com.boyue.common.xss.Xss; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +/** + * 通知公告表 sys_notice + * + * @author boyue + */ +@Schema(title = "系统访问记录表") +public class SysNotice extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 公告ID */ + @Schema(title = "公告ID") + private Long noticeId; + + /** 公告标题 */ + @Schema(title = "公告标题") + private String noticeTitle; + + /** 公告类型(1通知 2公告) */ + @Schema(title = "公告类型") + private String noticeType; + + /** 公告内容 */ + @Schema(title = "公告内容") + private String noticeContent; + + /** 公告状态(0正常 1关闭) */ + @Schema(title = "公告状态") + private String status; + + public Long getNoticeId() + { + return noticeId; + } + + public void setNoticeId(Long noticeId) + { + this.noticeId = noticeId; + } + + public void setNoticeTitle(String noticeTitle) + { + this.noticeTitle = noticeTitle; + } + + @Xss(message = "公告标题不能包含脚本字符") + @NotBlank(message = "公告标题不能为空") + @Size(min = 0, max = 50, message = "公告标题不能超过50个字符") + public String getNoticeTitle() + { + return noticeTitle; + } + + public void setNoticeType(String noticeType) + { + this.noticeType = noticeType; + } + + public String getNoticeType() + { + return noticeType; + } + + public void setNoticeContent(String noticeContent) + { + this.noticeContent = noticeContent; + } + + public String getNoticeContent() + { + return noticeContent; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getStatus() + { + return status; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("noticeId", getNoticeId()) + .append("noticeTitle", getNoticeTitle()) + .append("noticeType", getNoticeType()) + .append("noticeContent", getNoticeContent()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/boyue-system/src/main/java/com/boyue/system/domain/SysOperLog.java b/boyue-system/src/main/java/com/boyue/system/domain/SysOperLog.java new file mode 100644 index 0000000..3da4930 --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/domain/SysOperLog.java @@ -0,0 +1,291 @@ +package com.boyue.system.domain; + +import java.util.Date; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.boyue.common.annotation.Excel; +import com.boyue.common.annotation.Excel.ColumnType; +import com.boyue.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 操作日志记录表 oper_log + * + * @author boyue + */ +@Schema(title = "操作日志记录表") +public class SysOperLog extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 日志主键 */ + @Schema(title = "操作序号") + @Excel(name = "操作序号", cellType = ColumnType.NUMERIC) + private Long operId; + + /** 操作模块 */ + @Schema(title = "操作模块") + @Excel(name = "操作模块") + private String title; + + /** 业务类型(0其它 1新增 2修改 3删除) */ + @Schema(title = "业务类型") + @Excel(name = "业务类型", readConverterExp = "0=其它,1=新增,2=修改,3=删除,4=授权,5=导出,6=导入,7=强退,8=生成代码,9=清空数据") + private Integer businessType; + + /** 业务类型数组 */ + @Schema(title = "业务类型数组") + private Integer[] businessTypes; + + /** 请求方法 */ + @Schema(title = "请求方法") + @Excel(name = "请求方法") + private String method; + + /** 请求方式 */ + @Schema(title = "请求方式") + @Excel(name = "请求方式") + private String requestMethod; + + /** 操作类别(0其它 1后台用户 2手机端用户) */ + @Schema(title = "操作类别") + @Excel(name = "操作类别", readConverterExp = "0=其它,1=后台用户,2=手机端用户") + private Integer operatorType; + + /** 操作人员 */ + @Schema(title = "操作人员") + @Excel(name = "操作人员") + private String operName; + + /** 部门名称 */ + @Schema(title = "部门名称") + @Excel(name = "部门名称") + private String deptName; + + /** 请求url */ + @Schema(title = "请求地址") + @Excel(name = "请求地址") + private String operUrl; + + /** 操作地址 */ + @Schema(title = "操作地址") + @Excel(name = "操作地址") + private String operIp; + + /** 操作地点 */ + @Schema(title = "操作地点") + @Excel(name = "操作地点") + private String operLocation; + + /** 请求参数 */ + @Schema(title = "请求参数") + @Excel(name = "请求参数") + private String operParam; + + /** 返回参数 */ + @Schema(title = "返回参数") + @Excel(name = "返回参数") + private String jsonResult; + + /** 操作状态(0正常 1异常) */ + @Schema(title = "操作状态") + @Excel(name = "操作状态", readConverterExp = "0=正常,1=异常") + private Integer status; + + /** 错误消息 */ + @Schema(title = "错误消息") + @Excel(name = "错误消息") + private String errorMsg; + + /** 操作时间 */ + @Schema(title = "操作时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "操作时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + private Date operTime; + + /** 消耗时间 */ + @Schema(title = "消耗时间") + @Excel(name = "消耗时间", suffix = "毫秒") + private Long costTime; + + public Long getOperId() + { + return operId; + } + + public void setOperId(Long operId) + { + this.operId = operId; + } + + public String getTitle() + { + return title; + } + + public void setTitle(String title) + { + this.title = title; + } + + public Integer getBusinessType() + { + return businessType; + } + + public void setBusinessType(Integer businessType) + { + this.businessType = businessType; + } + + public Integer[] getBusinessTypes() + { + return businessTypes; + } + + public void setBusinessTypes(Integer[] businessTypes) + { + this.businessTypes = businessTypes; + } + + public String getMethod() + { + return method; + } + + public void setMethod(String method) + { + this.method = method; + } + + public String getRequestMethod() + { + return requestMethod; + } + + public void setRequestMethod(String requestMethod) + { + this.requestMethod = requestMethod; + } + + public Integer getOperatorType() + { + return operatorType; + } + + public void setOperatorType(Integer operatorType) + { + this.operatorType = operatorType; + } + + public String getOperName() + { + return operName; + } + + public void setOperName(String operName) + { + this.operName = operName; + } + + public String getDeptName() + { + return deptName; + } + + public void setDeptName(String deptName) + { + this.deptName = deptName; + } + + public String getOperUrl() + { + return operUrl; + } + + public void setOperUrl(String operUrl) + { + this.operUrl = operUrl; + } + + public String getOperIp() + { + return operIp; + } + + public void setOperIp(String operIp) + { + this.operIp = operIp; + } + + public String getOperLocation() + { + return operLocation; + } + + public void setOperLocation(String operLocation) + { + this.operLocation = operLocation; + } + + public String getOperParam() + { + return operParam; + } + + public void setOperParam(String operParam) + { + this.operParam = operParam; + } + + public String getJsonResult() + { + return jsonResult; + } + + public void setJsonResult(String jsonResult) + { + this.jsonResult = jsonResult; + } + + public Integer getStatus() + { + return status; + } + + public void setStatus(Integer status) + { + this.status = status; + } + + public String getErrorMsg() + { + return errorMsg; + } + + public void setErrorMsg(String errorMsg) + { + this.errorMsg = errorMsg; + } + + public Date getOperTime() + { + return operTime; + } + + public void setOperTime(Date operTime) + { + this.operTime = operTime; + } + + public Long getCostTime() + { + return costTime; + } + + public void setCostTime(Long costTime) + { + this.costTime = costTime; + } +} diff --git a/boyue-system/src/main/java/com/boyue/system/domain/SysPost.java b/boyue-system/src/main/java/com/boyue/system/domain/SysPost.java new file mode 100644 index 0000000..04b577a --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/domain/SysPost.java @@ -0,0 +1,134 @@ +package com.boyue.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import com.boyue.common.annotation.Excel; +import com.boyue.common.annotation.Excel.ColumnType; +import com.boyue.common.core.domain.BaseEntity; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +/** + * 岗位表 sys_post + * + * @author boyue + */ +@Schema(title = "岗位表") +public class SysPost extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 岗位序号 */ + @Schema(title = "岗位序号") + @Excel(name = "岗位序号", cellType = ColumnType.NUMERIC) + private Long postId; + + /** 岗位编码 */ + @Schema(title = "岗位编码") + @Excel(name = "岗位编码") + private String postCode; + + /** 岗位名称 */ + @Schema(title = "岗位名称") + @Excel(name = "岗位名称") + private String postName; + + /** 岗位排序 */ + @Schema(title = "岗位排序") + @Excel(name = "岗位排序") + private Integer postSort; + + /** 状态(0正常 1停用) */ + @Schema(title = "状态") + @Excel(name = "状态", readConverterExp = "0=正常,1=停用") + private String status; + + /** 用户是否存在此岗位标识 默认不存在 */ + @Schema(title = "用户是否存在此岗位标识") + private boolean flag = false; + + public Long getPostId() + { + return postId; + } + + public void setPostId(Long postId) + { + this.postId = postId; + } + + @NotBlank(message = "岗位编码不能为空") + @Size(min = 0, max = 64, message = "岗位编码长度不能超过64个字符") + public String getPostCode() + { + return postCode; + } + + public void setPostCode(String postCode) + { + this.postCode = postCode; + } + + @NotBlank(message = "岗位名称不能为空") + @Size(min = 0, max = 50, message = "岗位名称长度不能超过50个字符") + public String getPostName() + { + return postName; + } + + public void setPostName(String postName) + { + this.postName = postName; + } + + @NotNull(message = "显示顺序不能为空") + public Integer getPostSort() + { + return postSort; + } + + public void setPostSort(Integer postSort) + { + this.postSort = postSort; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public boolean isFlag() + { + return flag; + } + + public void setFlag(boolean flag) + { + this.flag = flag; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("postId", getPostId()) + .append("postCode", getPostCode()) + .append("postName", getPostName()) + .append("postSort", getPostSort()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/boyue-system/src/main/java/com/boyue/system/domain/SysRoleDept.java b/boyue-system/src/main/java/com/boyue/system/domain/SysRoleDept.java new file mode 100644 index 0000000..ae2fa35 --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/domain/SysRoleDept.java @@ -0,0 +1,51 @@ +package com.boyue.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 角色和部门关联 sys_role_dept + * + * @author boyue + */ +@Schema(title = "角色和部门关联") +public class SysRoleDept +{ + /** 角色ID */ + @Schema(title = "角色ID") + private Long roleId; + + /** 部门ID */ + @Schema(title = "部门ID") + private Long deptId; + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + public Long getDeptId() + { + return deptId; + } + + public void setDeptId(Long deptId) + { + this.deptId = deptId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("roleId", getRoleId()) + .append("deptId", getDeptId()) + .toString(); + } +} diff --git a/boyue-system/src/main/java/com/boyue/system/domain/SysRoleMenu.java b/boyue-system/src/main/java/com/boyue/system/domain/SysRoleMenu.java new file mode 100644 index 0000000..c70e04d --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/domain/SysRoleMenu.java @@ -0,0 +1,51 @@ +package com.boyue.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 角色和菜单关联 sys_role_menu + * + * @author boyue + */ +@Schema(title = "角色和菜单关联") +public class SysRoleMenu +{ + /** 角色ID */ + @Schema(title = "角色ID") + private Long roleId; + + /** 菜单ID */ + @Schema(title = "菜单ID") + private Long menuId; + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + public Long getMenuId() + { + return menuId; + } + + public void setMenuId(Long menuId) + { + this.menuId = menuId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("roleId", getRoleId()) + .append("menuId", getMenuId()) + .toString(); + } +} diff --git a/boyue-system/src/main/java/com/boyue/system/domain/SysUserOnline.java b/boyue-system/src/main/java/com/boyue/system/domain/SysUserOnline.java new file mode 100644 index 0000000..31b93ef --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/domain/SysUserOnline.java @@ -0,0 +1,124 @@ +package com.boyue.system.domain; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 当前在线会话 + * + * @author boyue + */ +@Schema(title = "当前在线会话") +public class SysUserOnline +{ + /** 会话编号 */ + @Schema(title = "会话编号") + private String tokenId; + + /** 部门名称 */ + @Schema(title = "部门名称") + private String deptName; + + /** 用户名称 */ + @Schema(title = "用户名称") + private String userName; + + /** 登录IP地址 */ + @Schema(title = "登录IP地址") + private String ipaddr; + + /** 登录地址 */ + @Schema(title = "登录地址") + private String loginLocation; + + /** 浏览器类型 */ + @Schema(title = "浏览器类型") + private String browser; + + /** 操作系统 */ + @Schema(title = "操作系统") + private String os; + + /** 登录时间 */ + @Schema(title = "登录时间") + private Long loginTime; + + public String getTokenId() + { + return tokenId; + } + + public void setTokenId(String tokenId) + { + this.tokenId = tokenId; + } + + public String getDeptName() + { + return deptName; + } + + public void setDeptName(String deptName) + { + this.deptName = deptName; + } + + public String getUserName() + { + return userName; + } + + public void setUserName(String userName) + { + this.userName = userName; + } + + public String getIpaddr() + { + return ipaddr; + } + + public void setIpaddr(String ipaddr) + { + this.ipaddr = ipaddr; + } + + public String getLoginLocation() + { + return loginLocation; + } + + public void setLoginLocation(String loginLocation) + { + this.loginLocation = loginLocation; + } + + public String getBrowser() + { + return browser; + } + + public void setBrowser(String browser) + { + this.browser = browser; + } + + public String getOs() + { + return os; + } + + public void setOs(String os) + { + this.os = os; + } + + public Long getLoginTime() + { + return loginTime; + } + + public void setLoginTime(Long loginTime) + { + this.loginTime = loginTime; + } +} diff --git a/boyue-system/src/main/java/com/boyue/system/domain/SysUserPost.java b/boyue-system/src/main/java/com/boyue/system/domain/SysUserPost.java new file mode 100644 index 0000000..497b4b7 --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/domain/SysUserPost.java @@ -0,0 +1,51 @@ +package com.boyue.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 用户和岗位关联 sys_user_post + * + * @author boyue + */ +@Schema(title = "用户和岗位关联") +public class SysUserPost +{ + /** 用户ID */ + @Schema(title = "用户ID") + private Long userId; + + /** 岗位ID */ + @Schema(title = "岗位ID") + private Long postId; + + public Long getUserId() + { + return userId; + } + + public void setUserId(Long userId) + { + this.userId = userId; + } + + public Long getPostId() + { + return postId; + } + + public void setPostId(Long postId) + { + this.postId = postId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("userId", getUserId()) + .append("postId", getPostId()) + .toString(); + } +} diff --git a/boyue-system/src/main/java/com/boyue/system/domain/SysUserRole.java b/boyue-system/src/main/java/com/boyue/system/domain/SysUserRole.java new file mode 100644 index 0000000..9303da2 --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/domain/SysUserRole.java @@ -0,0 +1,51 @@ +package com.boyue.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import io.swagger.v3.oas.annotations.media.Schema; + +/** + * 用户和角色关联 sys_user_role + * + * @author boyue + */ +@Schema(title = "用户和角色关联") +public class SysUserRole +{ + /** 用户ID */ + @Schema(title = "用户ID") + private Long userId; + + /** 角色ID */ + @Schema(title = "角色ID") + private Long roleId; + + public Long getUserId() + { + return userId; + } + + public void setUserId(Long userId) + { + this.userId = userId; + } + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("userId", getUserId()) + .append("roleId", getRoleId()) + .toString(); + } +} diff --git a/boyue-system/src/main/java/com/boyue/system/domain/vo/MetaVo.java b/boyue-system/src/main/java/com/boyue/system/domain/vo/MetaVo.java new file mode 100644 index 0000000..69f65fb --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/domain/vo/MetaVo.java @@ -0,0 +1,106 @@ +package com.boyue.system.domain.vo; + +import com.boyue.common.utils.StringUtils; + +/** + * 路由显示信息 + * + * @author boyue + */ +public class MetaVo +{ + /** + * 设置该路由在侧边栏和面包屑中展示的名字 + */ + private String title; + + /** + * 设置该路由的图标,对应路径src/assets/icons/svg + */ + private String icon; + + /** + * 设置为true,则不会被 缓存 + */ + private boolean noCache; + + /** + * 内链地址(http(s)://开头) + */ + private String link; + + public MetaVo() + { + } + + public MetaVo(String title, String icon) + { + this.title = title; + this.icon = icon; + } + + public MetaVo(String title, String icon, boolean noCache) + { + this.title = title; + this.icon = icon; + this.noCache = noCache; + } + + public MetaVo(String title, String icon, String link) + { + this.title = title; + this.icon = icon; + this.link = link; + } + + public MetaVo(String title, String icon, boolean noCache, String link) + { + this.title = title; + this.icon = icon; + this.noCache = noCache; + if (StringUtils.ishttp(link)) + { + this.link = link; + } + } + + public boolean isNoCache() + { + return noCache; + } + + public void setNoCache(boolean noCache) + { + this.noCache = noCache; + } + + public String getTitle() + { + return title; + } + + public void setTitle(String title) + { + this.title = title; + } + + public String getIcon() + { + return icon; + } + + public void setIcon(String icon) + { + this.icon = icon; + } + + public String getLink() + { + return link; + } + + public void setLink(String link) + { + this.link = link; + } +} diff --git a/boyue-system/src/main/java/com/boyue/system/domain/vo/RouterVo.java b/boyue-system/src/main/java/com/boyue/system/domain/vo/RouterVo.java new file mode 100644 index 0000000..fe59802 --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/domain/vo/RouterVo.java @@ -0,0 +1,148 @@ +package com.boyue.system.domain.vo; + +import com.fasterxml.jackson.annotation.JsonInclude; +import java.util.List; + +/** + * 路由配置信息 + * + * @author boyue + */ +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class RouterVo +{ + /** + * 路由名字 + */ + private String name; + + /** + * 路由地址 + */ + private String path; + + /** + * 是否隐藏路由,当设置 true 的时候该路由不会再侧边栏出现 + */ + private boolean hidden; + + /** + * 重定向地址,当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 + */ + private String redirect; + + /** + * 组件地址 + */ + private String component; + + /** + * 路由参数:如 {"id": 1, "name": "ry"} + */ + private String query; + + /** + * 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面 + */ + private Boolean alwaysShow; + + /** + * 其他元素 + */ + private MetaVo meta; + + /** + * 子路由 + */ + private List children; + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + public String getPath() + { + return path; + } + + public void setPath(String path) + { + this.path = path; + } + + public boolean getHidden() + { + return hidden; + } + + public void setHidden(boolean hidden) + { + this.hidden = hidden; + } + + public String getRedirect() + { + return redirect; + } + + public void setRedirect(String redirect) + { + this.redirect = redirect; + } + + public String getComponent() + { + return component; + } + + public void setComponent(String component) + { + this.component = component; + } + + public String getQuery() + { + return query; + } + + public void setQuery(String query) + { + this.query = query; + } + + public Boolean getAlwaysShow() + { + return alwaysShow; + } + + public void setAlwaysShow(Boolean alwaysShow) + { + this.alwaysShow = alwaysShow; + } + + public MetaVo getMeta() + { + return meta; + } + + public void setMeta(MetaVo meta) + { + this.meta = meta; + } + + public List getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } +} diff --git a/boyue-system/src/main/java/com/boyue/system/mapper/SysConfigMapper.java b/boyue-system/src/main/java/com/boyue/system/mapper/SysConfigMapper.java new file mode 100644 index 0000000..7a103a0 --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/mapper/SysConfigMapper.java @@ -0,0 +1,76 @@ +package com.boyue.system.mapper; + +import java.util.List; +import com.boyue.system.domain.SysConfig; + +/** + * 参数配置 数据层 + * + * @author boyue + */ +public interface SysConfigMapper +{ + /** + * 查询参数配置信息 + * + * @param config 参数配置信息 + * @return 参数配置信息 + */ + public SysConfig selectConfig(SysConfig config); + + /** + * 通过ID查询配置 + * + * @param configId 参数ID + * @return 参数配置信息 + */ + public SysConfig selectConfigById(Long configId); + + /** + * 查询参数配置列表 + * + * @param config 参数配置信息 + * @return 参数配置集合 + */ + public List selectConfigList(SysConfig config); + + /** + * 根据键名查询参数配置信息 + * + * @param configKey 参数键名 + * @return 参数配置信息 + */ + public SysConfig checkConfigKeyUnique(String configKey); + + /** + * 新增参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + public int insertConfig(SysConfig config); + + /** + * 修改参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + public int updateConfig(SysConfig config); + + /** + * 删除参数配置 + * + * @param configId 参数ID + * @return 结果 + */ + public int deleteConfigById(Long configId); + + /** + * 批量删除参数信息 + * + * @param configIds 需要删除的参数ID + * @return 结果 + */ + public int deleteConfigByIds(Long[] configIds); +} diff --git a/boyue-system/src/main/java/com/boyue/system/mapper/SysDeptMapper.java b/boyue-system/src/main/java/com/boyue/system/mapper/SysDeptMapper.java new file mode 100644 index 0000000..6653c24 --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/mapper/SysDeptMapper.java @@ -0,0 +1,118 @@ +package com.boyue.system.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.boyue.common.core.domain.entity.SysDept; + +/** + * 部门管理 数据层 + * + * @author boyue + */ +public interface SysDeptMapper +{ + /** + * 查询部门管理数据 + * + * @param dept 部门信息 + * @return 部门信息集合 + */ + public List selectDeptList(SysDept dept); + + /** + * 根据角色ID查询部门树信息 + * + * @param roleId 角色ID + * @param deptCheckStrictly 部门树选择项是否关联显示 + * @return 选中部门列表 + */ + public List selectDeptListByRoleId(@Param("roleId") Long roleId, @Param("deptCheckStrictly") boolean deptCheckStrictly); + + /** + * 根据部门ID查询信息 + * + * @param deptId 部门ID + * @return 部门信息 + */ + public SysDept selectDeptById(Long deptId); + + /** + * 根据ID查询所有子部门 + * + * @param deptId 部门ID + * @return 部门列表 + */ + public List selectChildrenDeptById(Long deptId); + + /** + * 根据ID查询所有子部门(正常状态) + * + * @param deptId 部门ID + * @return 子部门数 + */ + public int selectNormalChildrenDeptById(Long deptId); + + /** + * 是否存在子节点 + * + * @param deptId 部门ID + * @return 结果 + */ + public int hasChildByDeptId(Long deptId); + + /** + * 查询部门是否存在用户 + * + * @param deptId 部门ID + * @return 结果 + */ + public int checkDeptExistUser(Long deptId); + + /** + * 校验部门名称是否唯一 + * + * @param deptName 部门名称 + * @param parentId 父部门ID + * @return 结果 + */ + public SysDept checkDeptNameUnique(@Param("deptName") String deptName, @Param("parentId") Long parentId); + + /** + * 新增部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + public int insertDept(SysDept dept); + + /** + * 修改部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + public int updateDept(SysDept dept); + + /** + * 修改所在部门正常状态 + * + * @param deptIds 部门ID组 + */ + public void updateDeptStatusNormal(Long[] deptIds); + + /** + * 修改子元素关系 + * + * @param depts 子元素 + * @return 结果 + */ + public int updateDeptChildren(@Param("depts") List depts); + + /** + * 删除部门管理信息 + * + * @param deptId 部门ID + * @return 结果 + */ + public int deleteDeptById(Long deptId); +} diff --git a/boyue-system/src/main/java/com/boyue/system/mapper/SysDictDataMapper.java b/boyue-system/src/main/java/com/boyue/system/mapper/SysDictDataMapper.java new file mode 100644 index 0000000..d7a3fd1 --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/mapper/SysDictDataMapper.java @@ -0,0 +1,95 @@ +package com.boyue.system.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.boyue.common.core.domain.entity.SysDictData; + +/** + * 字典表 数据层 + * + * @author boyue + */ +public interface SysDictDataMapper +{ + /** + * 根据条件分页查询字典数据 + * + * @param dictData 字典数据信息 + * @return 字典数据集合信息 + */ + public List selectDictDataList(SysDictData dictData); + + /** + * 根据字典类型查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据集合信息 + */ + public List selectDictDataByType(String dictType); + + /** + * 根据字典类型和字典键值查询字典数据信息 + * + * @param dictType 字典类型 + * @param dictValue 字典键值 + * @return 字典标签 + */ + public String selectDictLabel(@Param("dictType") String dictType, @Param("dictValue") String dictValue); + + /** + * 根据字典数据ID查询信息 + * + * @param dictCode 字典数据ID + * @return 字典数据 + */ + public SysDictData selectDictDataById(Long dictCode); + + /** + * 查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据 + */ + public int countDictDataByType(String dictType); + + /** + * 通过字典ID删除字典数据信息 + * + * @param dictCode 字典数据ID + * @return 结果 + */ + public int deleteDictDataById(Long dictCode); + + /** + * 批量删除字典数据信息 + * + * @param dictCodes 需要删除的字典数据ID + * @return 结果 + */ + public int deleteDictDataByIds(Long[] dictCodes); + + /** + * 新增字典数据信息 + * + * @param dictData 字典数据信息 + * @return 结果 + */ + public int insertDictData(SysDictData dictData); + + /** + * 修改字典数据信息 + * + * @param dictData 字典数据信息 + * @return 结果 + */ + public int updateDictData(SysDictData dictData); + + /** + * 同步修改字典类型 + * + * @param oldDictType 旧字典类型 + * @param newDictType 新旧字典类型 + * @return 结果 + */ + public int updateDictDataType(@Param("oldDictType") String oldDictType, @Param("newDictType") String newDictType); +} diff --git a/boyue-system/src/main/java/com/boyue/system/mapper/SysDictTypeMapper.java b/boyue-system/src/main/java/com/boyue/system/mapper/SysDictTypeMapper.java new file mode 100644 index 0000000..a22e8ab --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/mapper/SysDictTypeMapper.java @@ -0,0 +1,83 @@ +package com.boyue.system.mapper; + +import java.util.List; +import com.boyue.common.core.domain.entity.SysDictType; + +/** + * 字典表 数据层 + * + * @author boyue + */ +public interface SysDictTypeMapper +{ + /** + * 根据条件分页查询字典类型 + * + * @param dictType 字典类型信息 + * @return 字典类型集合信息 + */ + public List selectDictTypeList(SysDictType dictType); + + /** + * 根据所有字典类型 + * + * @return 字典类型集合信息 + */ + public List selectDictTypeAll(); + + /** + * 根据字典类型ID查询信息 + * + * @param dictId 字典类型ID + * @return 字典类型 + */ + public SysDictType selectDictTypeById(Long dictId); + + /** + * 根据字典类型查询信息 + * + * @param dictType 字典类型 + * @return 字典类型 + */ + public SysDictType selectDictTypeByType(String dictType); + + /** + * 通过字典ID删除字典信息 + * + * @param dictId 字典ID + * @return 结果 + */ + public int deleteDictTypeById(Long dictId); + + /** + * 批量删除字典类型信息 + * + * @param dictIds 需要删除的字典ID + * @return 结果 + */ + public int deleteDictTypeByIds(Long[] dictIds); + + /** + * 新增字典类型信息 + * + * @param dictType 字典类型信息 + * @return 结果 + */ + public int insertDictType(SysDictType dictType); + + /** + * 修改字典类型信息 + * + * @param dictType 字典类型信息 + * @return 结果 + */ + public int updateDictType(SysDictType dictType); + + /** + * 校验字典类型称是否唯一 + * + * @param dictType 字典类型 + * @return 结果 + */ + public SysDictType checkDictTypeUnique(String dictType); +} diff --git a/boyue-system/src/main/java/com/boyue/system/mapper/SysLogininforMapper.java b/boyue-system/src/main/java/com/boyue/system/mapper/SysLogininforMapper.java new file mode 100644 index 0000000..a894a11 --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/mapper/SysLogininforMapper.java @@ -0,0 +1,42 @@ +package com.boyue.system.mapper; + +import java.util.List; +import com.boyue.system.domain.SysLogininfor; + +/** + * 系统访问日志情况信息 数据层 + * + * @author boyue + */ +public interface SysLogininforMapper +{ + /** + * 新增系统登录日志 + * + * @param logininfor 访问日志对象 + */ + public void insertLogininfor(SysLogininfor logininfor); + + /** + * 查询系统登录日志集合 + * + * @param logininfor 访问日志对象 + * @return 登录记录集合 + */ + public List selectLogininforList(SysLogininfor logininfor); + + /** + * 批量删除系统登录日志 + * + * @param infoIds 需要删除的登录日志ID + * @return 结果 + */ + public int deleteLogininforByIds(Long[] infoIds); + + /** + * 清空系统登录日志 + * + * @return 结果 + */ + public int cleanLogininfor(); +} diff --git a/boyue-system/src/main/java/com/boyue/system/mapper/SysMenuMapper.java b/boyue-system/src/main/java/com/boyue/system/mapper/SysMenuMapper.java new file mode 100644 index 0000000..5ed4157 --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/mapper/SysMenuMapper.java @@ -0,0 +1,125 @@ +package com.boyue.system.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.boyue.common.core.domain.entity.SysMenu; + +/** + * 菜单表 数据层 + * + * @author boyue + */ +public interface SysMenuMapper +{ + /** + * 查询系统菜单列表 + * + * @param menu 菜单信息 + * @return 菜单列表 + */ + public List selectMenuList(SysMenu menu); + + /** + * 根据用户所有权限 + * + * @return 权限列表 + */ + public List selectMenuPerms(); + + /** + * 根据用户查询系统菜单列表 + * + * @param menu 菜单信息 + * @return 菜单列表 + */ + public List selectMenuListByUserId(SysMenu menu); + + /** + * 根据角色ID查询权限 + * + * @param roleId 角色ID + * @return 权限列表 + */ + public List selectMenuPermsByRoleId(Long roleId); + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + public List selectMenuPermsByUserId(Long userId); + + /** + * 根据用户ID查询菜单 + * + * @return 菜单列表 + */ + public List selectMenuTreeAll(); + + /** + * 根据用户ID查询菜单 + * + * @param userId 用户ID + * @return 菜单列表 + */ + public List selectMenuTreeByUserId(Long userId); + + /** + * 根据角色ID查询菜单树信息 + * + * @param roleId 角色ID + * @param menuCheckStrictly 菜单树选择项是否关联显示 + * @return 选中菜单列表 + */ + public List selectMenuListByRoleId(@Param("roleId") Long roleId, @Param("menuCheckStrictly") boolean menuCheckStrictly); + + /** + * 根据菜单ID查询信息 + * + * @param menuId 菜单ID + * @return 菜单信息 + */ + public SysMenu selectMenuById(Long menuId); + + /** + * 是否存在菜单子节点 + * + * @param menuId 菜单ID + * @return 结果 + */ + public int hasChildByMenuId(Long menuId); + + /** + * 新增菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + public int insertMenu(SysMenu menu); + + /** + * 修改菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + public int updateMenu(SysMenu menu); + + /** + * 删除菜单管理信息 + * + * @param menuId 菜单ID + * @return 结果 + */ + public int deleteMenuById(Long menuId); + + /** + * 校验菜单名称是否唯一 + * + * @param menuName 菜单名称 + * @param parentId 父菜单ID + * @return 结果 + */ + public SysMenu checkMenuNameUnique(@Param("menuName") String menuName, @Param("parentId") Long parentId); +} diff --git a/boyue-system/src/main/java/com/boyue/system/mapper/SysNoticeMapper.java b/boyue-system/src/main/java/com/boyue/system/mapper/SysNoticeMapper.java new file mode 100644 index 0000000..176c08c --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/mapper/SysNoticeMapper.java @@ -0,0 +1,60 @@ +package com.boyue.system.mapper; + +import java.util.List; +import com.boyue.system.domain.SysNotice; + +/** + * 通知公告表 数据层 + * + * @author boyue + */ +public interface SysNoticeMapper +{ + /** + * 查询公告信息 + * + * @param noticeId 公告ID + * @return 公告信息 + */ + public SysNotice selectNoticeById(Long noticeId); + + /** + * 查询公告列表 + * + * @param notice 公告信息 + * @return 公告集合 + */ + public List selectNoticeList(SysNotice notice); + + /** + * 新增公告 + * + * @param notice 公告信息 + * @return 结果 + */ + public int insertNotice(SysNotice notice); + + /** + * 修改公告 + * + * @param notice 公告信息 + * @return 结果 + */ + public int updateNotice(SysNotice notice); + + /** + * 批量删除公告 + * + * @param noticeId 公告ID + * @return 结果 + */ + public int deleteNoticeById(Long noticeId); + + /** + * 批量删除公告信息 + * + * @param noticeIds 需要删除的公告ID + * @return 结果 + */ + public int deleteNoticeByIds(Long[] noticeIds); +} diff --git a/boyue-system/src/main/java/com/boyue/system/mapper/SysOperLogMapper.java b/boyue-system/src/main/java/com/boyue/system/mapper/SysOperLogMapper.java new file mode 100644 index 0000000..fb80738 --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/mapper/SysOperLogMapper.java @@ -0,0 +1,70 @@ +package com.boyue.system.mapper; + +import java.util.List; +import java.util.Map; + +import com.boyue.system.domain.SysOperLog; + +/** + * 操作日志 数据层 + * + * @author boyue + */ +public interface SysOperLogMapper +{ + /** + * 新增操作日志 + * + * @param operLog 操作日志对象 + */ + public void insertOperlog(SysOperLog operLog); + + /** + * 查询系统操作日志集合 + * + * @param operLog 操作日志对象 + * @return 操作日志集合 + */ + public List selectOperLogList(SysOperLog operLog); + + /** + * 批量删除系统操作日志 + * + * @param operIds 需要删除的操作日志ID + * @return 结果 + */ + public int deleteOperLogByIds(Long[] operIds); + + /** + * 查询操作日志详细 + * + * @param operId 操作ID + * @return 操作日志对象 + */ + public SysOperLog selectOperLogById(Long operId); + + /** + * 清空操作日志 + */ + public void cleanOperLog(); + + /** + * 获取成功操作的统计信息 + */ + public List> getSuccessOperationStats(SysOperLog operLog); + + /** + * 获取失败操作的统计信息 + */ + public List> getFailureOperationStats(SysOperLog operLog); + + /** + * 获取按状态分类的操作统计信息 + */ + public List> getStatusStats(SysOperLog operLog); + + /** + * 获取按模块和操作类型分类的操作统计信息 + */ + public List> getModuleOperationStats(SysOperLog operLog); +} diff --git a/boyue-system/src/main/java/com/boyue/system/mapper/SysPostMapper.java b/boyue-system/src/main/java/com/boyue/system/mapper/SysPostMapper.java new file mode 100644 index 0000000..a321c92 --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/mapper/SysPostMapper.java @@ -0,0 +1,99 @@ +package com.boyue.system.mapper; + +import java.util.List; +import com.boyue.system.domain.SysPost; + +/** + * 岗位信息 数据层 + * + * @author boyue + */ +public interface SysPostMapper +{ + /** + * 查询岗位数据集合 + * + * @param post 岗位信息 + * @return 岗位数据集合 + */ + public List selectPostList(SysPost post); + + /** + * 查询所有岗位 + * + * @return 岗位列表 + */ + public List selectPostAll(); + + /** + * 通过岗位ID查询岗位信息 + * + * @param postId 岗位ID + * @return 角色对象信息 + */ + public SysPost selectPostById(Long postId); + + /** + * 根据用户ID获取岗位选择框列表 + * + * @param userId 用户ID + * @return 选中岗位ID列表 + */ + public List selectPostListByUserId(Long userId); + + /** + * 查询用户所属岗位组 + * + * @param userName 用户名 + * @return 结果 + */ + public List selectPostsByUserName(String userName); + + /** + * 删除岗位信息 + * + * @param postId 岗位ID + * @return 结果 + */ + public int deletePostById(Long postId); + + /** + * 批量删除岗位信息 + * + * @param postIds 需要删除的岗位ID + * @return 结果 + */ + public int deletePostByIds(Long[] postIds); + + /** + * 修改岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + public int updatePost(SysPost post); + + /** + * 新增岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + public int insertPost(SysPost post); + + /** + * 校验岗位名称 + * + * @param postName 岗位名称 + * @return 结果 + */ + public SysPost checkPostNameUnique(String postName); + + /** + * 校验岗位编码 + * + * @param postCode 岗位编码 + * @return 结果 + */ + public SysPost checkPostCodeUnique(String postCode); +} diff --git a/boyue-system/src/main/java/com/boyue/system/mapper/SysRoleDeptMapper.java b/boyue-system/src/main/java/com/boyue/system/mapper/SysRoleDeptMapper.java new file mode 100644 index 0000000..33fd011 --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/mapper/SysRoleDeptMapper.java @@ -0,0 +1,44 @@ +package com.boyue.system.mapper; + +import java.util.List; +import com.boyue.system.domain.SysRoleDept; + +/** + * 角色与部门关联表 数据层 + * + * @author boyue + */ +public interface SysRoleDeptMapper +{ + /** + * 通过角色ID删除角色和部门关联 + * + * @param roleId 角色ID + * @return 结果 + */ + public int deleteRoleDeptByRoleId(Long roleId); + + /** + * 批量删除角色部门关联信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteRoleDept(Long[] ids); + + /** + * 查询部门使用数量 + * + * @param deptId 部门ID + * @return 结果 + */ + public int selectCountRoleDeptByDeptId(Long deptId); + + /** + * 批量新增角色部门信息 + * + * @param roleDeptList 角色部门列表 + * @return 结果 + */ + public int batchRoleDept(List roleDeptList); +} diff --git a/boyue-system/src/main/java/com/boyue/system/mapper/SysRoleMapper.java b/boyue-system/src/main/java/com/boyue/system/mapper/SysRoleMapper.java new file mode 100644 index 0000000..300ae41 --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/mapper/SysRoleMapper.java @@ -0,0 +1,107 @@ +package com.boyue.system.mapper; + +import java.util.List; +import com.boyue.common.core.domain.entity.SysRole; + +/** + * 角色表 数据层 + * + * @author boyue + */ +public interface SysRoleMapper +{ + /** + * 根据条件分页查询角色数据 + * + * @param role 角色信息 + * @return 角色数据集合信息 + */ + public List selectRoleList(SysRole role); + + /** + * 根据用户ID查询角色 + * + * @param userId 用户ID + * @return 角色列表 + */ + public List selectRolePermissionByUserId(Long userId); + + /** + * 查询所有角色 + * + * @return 角色列表 + */ + public List selectRoleAll(); + + /** + * 根据用户ID获取角色选择框列表 + * + * @param userId 用户ID + * @return 选中角色ID列表 + */ + public List selectRoleListByUserId(Long userId); + + /** + * 通过角色ID查询角色 + * + * @param roleId 角色ID + * @return 角色对象信息 + */ + public SysRole selectRoleById(Long roleId); + + /** + * 根据用户ID查询角色 + * + * @param userName 用户名 + * @return 角色列表 + */ + public List selectRolesByUserName(String userName); + + /** + * 校验角色名称是否唯一 + * + * @param roleName 角色名称 + * @return 角色信息 + */ + public SysRole checkRoleNameUnique(String roleName); + + /** + * 校验角色权限是否唯一 + * + * @param roleKey 角色权限 + * @return 角色信息 + */ + public SysRole checkRoleKeyUnique(String roleKey); + + /** + * 修改角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int updateRole(SysRole role); + + /** + * 新增角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int insertRole(SysRole role); + + /** + * 通过角色ID删除角色 + * + * @param roleId 角色ID + * @return 结果 + */ + public int deleteRoleById(Long roleId); + + /** + * 批量删除角色信息 + * + * @param roleIds 需要删除的角色ID + * @return 结果 + */ + public int deleteRoleByIds(Long[] roleIds); +} diff --git a/boyue-system/src/main/java/com/boyue/system/mapper/SysRoleMenuMapper.java b/boyue-system/src/main/java/com/boyue/system/mapper/SysRoleMenuMapper.java new file mode 100644 index 0000000..210e296 --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/mapper/SysRoleMenuMapper.java @@ -0,0 +1,44 @@ +package com.boyue.system.mapper; + +import java.util.List; +import com.boyue.system.domain.SysRoleMenu; + +/** + * 角色与菜单关联表 数据层 + * + * @author boyue + */ +public interface SysRoleMenuMapper +{ + /** + * 查询菜单使用数量 + * + * @param menuId 菜单ID + * @return 结果 + */ + public int checkMenuExistRole(Long menuId); + + /** + * 通过角色ID删除角色和菜单关联 + * + * @param roleId 角色ID + * @return 结果 + */ + public int deleteRoleMenuByRoleId(Long roleId); + + /** + * 批量删除角色菜单关联信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteRoleMenu(Long[] ids); + + /** + * 批量新增角色菜单信息 + * + * @param roleMenuList 角色菜单列表 + * @return 结果 + */ + public int batchRoleMenu(List roleMenuList); +} diff --git a/boyue-system/src/main/java/com/boyue/system/mapper/SysUserMapper.java b/boyue-system/src/main/java/com/boyue/system/mapper/SysUserMapper.java new file mode 100644 index 0000000..757e827 --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/mapper/SysUserMapper.java @@ -0,0 +1,144 @@ +package com.boyue.system.mapper; + +import java.util.List; + +import org.apache.ibatis.annotations.Param; + +import com.boyue.common.core.domain.entity.SysUser; + +/** + * 用户表 数据层 + * + * @author boyue + */ +public interface SysUserMapper { + /** + * 根据条件分页查询用户列表 + * + * @param sysUser 用户信息 + * @return 用户信息集合信息 + */ + public List selectUserList(SysUser sysUser); + + /** + * 根据条件分页查询已配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectAllocatedList(SysUser user); + + /** + * 根据条件分页查询未分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectUnallocatedList(SysUser user); + + /** + * 通过用户名查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + public SysUser selectUserByUserName(String userName); + + /** + * 通过用户ID查询用户 + * + * @param userId 用户ID + * @return 用户对象信息 + */ + public SysUser selectUserById(Long userId); + + /** + * 通过手机号查询用户 + * + * @param phone 手机号 + * @return 用户对象信息 + */ + public SysUser selectUserByPhone(String phone); + + /** + * 通过邮箱查询用户 + * + * @param email 邮箱 + * @return 用户对象信息 + */ + public SysUser selectUserByEmail(String email); + + /** + * 新增用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int insertUser(SysUser user); + + /** + * 修改用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUser(SysUser user); + + /** + * 修改用户头像 + * + * @param userName 用户名 + * @param avatar 头像地址 + * @return 结果 + */ + public int updateUserAvatar(@Param("userName") String userName, @Param("avatar") String avatar); + + /** + * 重置用户密码 + * + * @param userName 用户名 + * @param password 密码 + * @return 结果 + */ + public int resetUserPwd(@Param("userName") String userName, @Param("password") String password); + + /** + * 通过用户ID删除用户 + * + * @param userId 用户ID + * @return 结果 + */ + public int deleteUserById(Long userId); + + /** + * 批量删除用户信息 + * + * @param userIds 需要删除的用户ID + * @return 结果 + */ + public int deleteUserByIds(Long[] userIds); + + /** + * 校验用户名称是否唯一 + * + * @param userName 用户名称 + * @return 结果 + */ + public SysUser checkUserNameUnique(String userName); + + /** + * 校验手机号码是否唯一 + * + * @param phonenumber 手机号码 + * @return 结果 + */ + public SysUser checkPhoneUnique(String phonenumber); + + /** + * 校验email是否唯一 + * + * @param email 用户邮箱 + * @return 结果 + */ + public SysUser checkEmailUnique(String email); +} diff --git a/boyue-system/src/main/java/com/boyue/system/mapper/SysUserPostMapper.java b/boyue-system/src/main/java/com/boyue/system/mapper/SysUserPostMapper.java new file mode 100644 index 0000000..d4f93bf --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/mapper/SysUserPostMapper.java @@ -0,0 +1,44 @@ +package com.boyue.system.mapper; + +import java.util.List; +import com.boyue.system.domain.SysUserPost; + +/** + * 用户与岗位关联表 数据层 + * + * @author boyue + */ +public interface SysUserPostMapper +{ + /** + * 通过用户ID删除用户和岗位关联 + * + * @param userId 用户ID + * @return 结果 + */ + public int deleteUserPostByUserId(Long userId); + + /** + * 通过岗位ID查询岗位使用数量 + * + * @param postId 岗位ID + * @return 结果 + */ + public int countUserPostById(Long postId); + + /** + * 批量删除用户和岗位关联 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteUserPost(Long[] ids); + + /** + * 批量新增用户岗位信息 + * + * @param userPostList 用户岗位列表 + * @return 结果 + */ + public int batchUserPost(List userPostList); +} diff --git a/boyue-system/src/main/java/com/boyue/system/mapper/SysUserRoleMapper.java b/boyue-system/src/main/java/com/boyue/system/mapper/SysUserRoleMapper.java new file mode 100644 index 0000000..9686c41 --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/mapper/SysUserRoleMapper.java @@ -0,0 +1,62 @@ +package com.boyue.system.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.boyue.system.domain.SysUserRole; + +/** + * 用户与角色关联表 数据层 + * + * @author boyue + */ +public interface SysUserRoleMapper +{ + /** + * 通过用户ID删除用户和角色关联 + * + * @param userId 用户ID + * @return 结果 + */ + public int deleteUserRoleByUserId(Long userId); + + /** + * 批量删除用户和角色关联 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteUserRole(Long[] ids); + + /** + * 通过角色ID查询角色使用数量 + * + * @param roleId 角色ID + * @return 结果 + */ + public int countUserRoleByRoleId(Long roleId); + + /** + * 批量新增用户角色信息 + * + * @param userRoleList 用户角色列表 + * @return 结果 + */ + public int batchUserRole(List userRoleList); + + /** + * 删除用户和角色关联信息 + * + * @param userRole 用户和角色关联信息 + * @return 结果 + */ + public int deleteUserRoleInfo(SysUserRole userRole); + + /** + * 批量取消授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要删除的用户数据ID + * @return 结果 + */ + public int deleteUserRoleInfos(@Param("roleId") Long roleId, @Param("userIds") Long[] userIds); +} diff --git a/boyue-system/src/main/java/com/boyue/system/service/ISysConfigService.java b/boyue-system/src/main/java/com/boyue/system/service/ISysConfigService.java new file mode 100644 index 0000000..3135734 --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/service/ISysConfigService.java @@ -0,0 +1,89 @@ +package com.boyue.system.service; + +import java.util.List; +import com.boyue.system.domain.SysConfig; + +/** + * 参数配置 服务层 + * + * @author boyue + */ +public interface ISysConfigService +{ + /** + * 查询参数配置信息 + * + * @param configId 参数配置ID + * @return 参数配置信息 + */ + public SysConfig selectConfigById(Long configId); + + /** + * 根据键名查询参数配置信息 + * + * @param configKey 参数键名 + * @return 参数键值 + */ + public String selectConfigByKey(String configKey); + + /** + * 获取验证码开关 + * + * @return true开启,false关闭 + */ + public boolean selectCaptchaEnabled(); + + /** + * 查询参数配置列表 + * + * @param config 参数配置信息 + * @return 参数配置集合 + */ + public List selectConfigList(SysConfig config); + + /** + * 新增参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + public int insertConfig(SysConfig config); + + /** + * 修改参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + public int updateConfig(SysConfig config); + + /** + * 批量删除参数信息 + * + * @param configIds 需要删除的参数ID + */ + public void deleteConfigByIds(Long[] configIds); + + /** + * 加载参数缓存数据 + */ + public void loadingConfigCache(); + + /** + * 清空参数缓存数据 + */ + public void clearConfigCache(); + + /** + * 重置参数缓存数据 + */ + public void resetConfigCache(); + + /** + * 校验参数键名是否唯一 + * + * @param config 参数信息 + * @return 结果 + */ + public boolean checkConfigKeyUnique(SysConfig config); +} diff --git a/boyue-system/src/main/java/com/boyue/system/service/ISysDeptService.java b/boyue-system/src/main/java/com/boyue/system/service/ISysDeptService.java new file mode 100644 index 0000000..7b94abf --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/service/ISysDeptService.java @@ -0,0 +1,124 @@ +package com.boyue.system.service; + +import java.util.List; +import com.boyue.common.core.domain.TreeSelect; +import com.boyue.common.core.domain.entity.SysDept; + +/** + * 部门管理 服务层 + * + * @author boyue + */ +public interface ISysDeptService +{ + /** + * 查询部门管理数据 + * + * @param dept 部门信息 + * @return 部门信息集合 + */ + public List selectDeptList(SysDept dept); + + /** + * 查询部门树结构信息 + * + * @param dept 部门信息 + * @return 部门树信息集合 + */ + public List selectDeptTreeList(SysDept dept); + + /** + * 构建前端所需要树结构 + * + * @param depts 部门列表 + * @return 树结构列表 + */ + public List buildDeptTree(List depts); + + /** + * 构建前端所需要下拉树结构 + * + * @param depts 部门列表 + * @return 下拉树结构列表 + */ + public List buildDeptTreeSelect(List depts); + + /** + * 根据角色ID查询部门树信息 + * + * @param roleId 角色ID + * @return 选中部门列表 + */ + public List selectDeptListByRoleId(Long roleId); + + /** + * 根据部门ID查询信息 + * + * @param deptId 部门ID + * @return 部门信息 + */ + public SysDept selectDeptById(Long deptId); + + /** + * 根据ID查询所有子部门(正常状态) + * + * @param deptId 部门ID + * @return 子部门数 + */ + public int selectNormalChildrenDeptById(Long deptId); + + /** + * 是否存在部门子节点 + * + * @param deptId 部门ID + * @return 结果 + */ + public boolean hasChildByDeptId(Long deptId); + + /** + * 查询部门是否存在用户 + * + * @param deptId 部门ID + * @return 结果 true 存在 false 不存在 + */ + public boolean checkDeptExistUser(Long deptId); + + /** + * 校验部门名称是否唯一 + * + * @param dept 部门信息 + * @return 结果 + */ + public boolean checkDeptNameUnique(SysDept dept); + + /** + * 校验部门是否有数据权限 + * + * @param deptId 部门id + */ + public void checkDeptDataScope(Long deptId); + + /** + * 新增保存部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + public int insertDept(SysDept dept); + + /** + * 修改保存部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + public int updateDept(SysDept dept); + + /** + * 删除部门管理信息 + * + * @param deptId 部门ID + * @return 结果 + */ + public int deleteDeptById(Long deptId); +} diff --git a/boyue-system/src/main/java/com/boyue/system/service/ISysDictDataService.java b/boyue-system/src/main/java/com/boyue/system/service/ISysDictDataService.java new file mode 100644 index 0000000..54a553d --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/service/ISysDictDataService.java @@ -0,0 +1,60 @@ +package com.boyue.system.service; + +import java.util.List; +import com.boyue.common.core.domain.entity.SysDictData; + +/** + * 字典 业务层 + * + * @author boyue + */ +public interface ISysDictDataService +{ + /** + * 根据条件分页查询字典数据 + * + * @param dictData 字典数据信息 + * @return 字典数据集合信息 + */ + public List selectDictDataList(SysDictData dictData); + + /** + * 根据字典类型和字典键值查询字典数据信息 + * + * @param dictType 字典类型 + * @param dictValue 字典键值 + * @return 字典标签 + */ + public String selectDictLabel(String dictType, String dictValue); + + /** + * 根据字典数据ID查询信息 + * + * @param dictCode 字典数据ID + * @return 字典数据 + */ + public SysDictData selectDictDataById(Long dictCode); + + /** + * 批量删除字典数据信息 + * + * @param dictCodes 需要删除的字典数据ID + */ + public void deleteDictDataByIds(Long[] dictCodes); + + /** + * 新增保存字典数据信息 + * + * @param dictData 字典数据信息 + * @return 结果 + */ + public int insertDictData(SysDictData dictData); + + /** + * 修改保存字典数据信息 + * + * @param dictData 字典数据信息 + * @return 结果 + */ + public int updateDictData(SysDictData dictData); +} diff --git a/boyue-system/src/main/java/com/boyue/system/service/ISysDictTypeService.java b/boyue-system/src/main/java/com/boyue/system/service/ISysDictTypeService.java new file mode 100644 index 0000000..372e35d --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/service/ISysDictTypeService.java @@ -0,0 +1,98 @@ +package com.boyue.system.service; + +import java.util.List; +import com.boyue.common.core.domain.entity.SysDictData; +import com.boyue.common.core.domain.entity.SysDictType; + +/** + * 字典 业务层 + * + * @author boyue + */ +public interface ISysDictTypeService +{ + /** + * 根据条件分页查询字典类型 + * + * @param dictType 字典类型信息 + * @return 字典类型集合信息 + */ + public List selectDictTypeList(SysDictType dictType); + + /** + * 根据所有字典类型 + * + * @return 字典类型集合信息 + */ + public List selectDictTypeAll(); + + /** + * 根据字典类型查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据集合信息 + */ + public List selectDictDataByType(String dictType); + + /** + * 根据字典类型ID查询信息 + * + * @param dictId 字典类型ID + * @return 字典类型 + */ + public SysDictType selectDictTypeById(Long dictId); + + /** + * 根据字典类型查询信息 + * + * @param dictType 字典类型 + * @return 字典类型 + */ + public SysDictType selectDictTypeByType(String dictType); + + /** + * 批量删除字典信息 + * + * @param dictIds 需要删除的字典ID + */ + public void deleteDictTypeByIds(Long[] dictIds); + + /** + * 加载字典缓存数据 + */ + public void loadingDictCache(); + + /** + * 清空字典缓存数据 + */ + public void clearDictCache(); + + /** + * 重置字典缓存数据 + */ + public void resetDictCache(); + + /** + * 新增保存字典类型信息 + * + * @param dictType 字典类型信息 + * @return 结果 + */ + public int insertDictType(SysDictType dictType); + + /** + * 修改保存字典类型信息 + * + * @param dictType 字典类型信息 + * @return 结果 + */ + public int updateDictType(SysDictType dictType); + + /** + * 校验字典类型称是否唯一 + * + * @param dictType 字典类型 + * @return 结果 + */ + public boolean checkDictTypeUnique(SysDictType dictType); +} diff --git a/boyue-system/src/main/java/com/boyue/system/service/ISysLogininforService.java b/boyue-system/src/main/java/com/boyue/system/service/ISysLogininforService.java new file mode 100644 index 0000000..8460917 --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/service/ISysLogininforService.java @@ -0,0 +1,40 @@ +package com.boyue.system.service; + +import java.util.List; +import com.boyue.system.domain.SysLogininfor; + +/** + * 系统访问日志情况信息 服务层 + * + * @author boyue + */ +public interface ISysLogininforService +{ + /** + * 新增系统登录日志 + * + * @param logininfor 访问日志对象 + */ + public void insertLogininfor(SysLogininfor logininfor); + + /** + * 查询系统登录日志集合 + * + * @param logininfor 访问日志对象 + * @return 登录记录集合 + */ + public List selectLogininforList(SysLogininfor logininfor); + + /** + * 批量删除系统登录日志 + * + * @param infoIds 需要删除的登录日志ID + * @return 结果 + */ + public int deleteLogininforByIds(Long[] infoIds); + + /** + * 清空系统登录日志 + */ + public void cleanLogininfor(); +} diff --git a/boyue-system/src/main/java/com/boyue/system/service/ISysMenuService.java b/boyue-system/src/main/java/com/boyue/system/service/ISysMenuService.java new file mode 100644 index 0000000..a093c13 --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/service/ISysMenuService.java @@ -0,0 +1,144 @@ +package com.boyue.system.service; + +import java.util.List; +import java.util.Set; +import com.boyue.common.core.domain.TreeSelect; +import com.boyue.common.core.domain.entity.SysMenu; +import com.boyue.system.domain.vo.RouterVo; + +/** + * 菜单 业务层 + * + * @author boyue + */ +public interface ISysMenuService +{ + /** + * 根据用户查询系统菜单列表 + * + * @param userId 用户ID + * @return 菜单列表 + */ + public List selectMenuList(Long userId); + + /** + * 根据用户查询系统菜单列表 + * + * @param menu 菜单信息 + * @param userId 用户ID + * @return 菜单列表 + */ + public List selectMenuList(SysMenu menu, Long userId); + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + public Set selectMenuPermsByUserId(Long userId); + + /** + * 根据角色ID查询权限 + * + * @param roleId 角色ID + * @return 权限列表 + */ + public Set selectMenuPermsByRoleId(Long roleId); + + /** + * 根据用户ID查询菜单树信息 + * + * @param userId 用户ID + * @return 菜单列表 + */ + public List selectMenuTreeByUserId(Long userId); + + /** + * 根据角色ID查询菜单树信息 + * + * @param roleId 角色ID + * @return 选中菜单列表 + */ + public List selectMenuListByRoleId(Long roleId); + + /** + * 构建前端路由所需要的菜单 + * + * @param menus 菜单列表 + * @return 路由列表 + */ + public List buildMenus(List menus); + + /** + * 构建前端所需要树结构 + * + * @param menus 菜单列表 + * @return 树结构列表 + */ + public List buildMenuTree(List menus); + + /** + * 构建前端所需要下拉树结构 + * + * @param menus 菜单列表 + * @return 下拉树结构列表 + */ + public List buildMenuTreeSelect(List menus); + + /** + * 根据菜单ID查询信息 + * + * @param menuId 菜单ID + * @return 菜单信息 + */ + public SysMenu selectMenuById(Long menuId); + + /** + * 是否存在菜单子节点 + * + * @param menuId 菜单ID + * @return 结果 true 存在 false 不存在 + */ + public boolean hasChildByMenuId(Long menuId); + + /** + * 查询菜单是否存在角色 + * + * @param menuId 菜单ID + * @return 结果 true 存在 false 不存在 + */ + public boolean checkMenuExistRole(Long menuId); + + /** + * 新增保存菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + public int insertMenu(SysMenu menu); + + /** + * 修改保存菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + public int updateMenu(SysMenu menu); + + /** + * 删除菜单管理信息 + * + * @param menuId 菜单ID + * @return 结果 + */ + public int deleteMenuById(Long menuId); + + /** + * 校验菜单名称是否唯一 + * + * @param menu 菜单信息 + * @return 结果 + */ + public boolean checkMenuNameUnique(SysMenu menu); +} diff --git a/boyue-system/src/main/java/com/boyue/system/service/ISysNoticeService.java b/boyue-system/src/main/java/com/boyue/system/service/ISysNoticeService.java new file mode 100644 index 0000000..6a8abea --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/service/ISysNoticeService.java @@ -0,0 +1,60 @@ +package com.boyue.system.service; + +import java.util.List; +import com.boyue.system.domain.SysNotice; + +/** + * 公告 服务层 + * + * @author boyue + */ +public interface ISysNoticeService +{ + /** + * 查询公告信息 + * + * @param noticeId 公告ID + * @return 公告信息 + */ + public SysNotice selectNoticeById(Long noticeId); + + /** + * 查询公告列表 + * + * @param notice 公告信息 + * @return 公告集合 + */ + public List selectNoticeList(SysNotice notice); + + /** + * 新增公告 + * + * @param notice 公告信息 + * @return 结果 + */ + public int insertNotice(SysNotice notice); + + /** + * 修改公告 + * + * @param notice 公告信息 + * @return 结果 + */ + public int updateNotice(SysNotice notice); + + /** + * 删除公告信息 + * + * @param noticeId 公告ID + * @return 结果 + */ + public int deleteNoticeById(Long noticeId); + + /** + * 批量删除公告信息 + * + * @param noticeIds 需要删除的公告ID + * @return 结果 + */ + public int deleteNoticeByIds(Long[] noticeIds); +} diff --git a/boyue-system/src/main/java/com/boyue/system/service/ISysOperLogService.java b/boyue-system/src/main/java/com/boyue/system/service/ISysOperLogService.java new file mode 100644 index 0000000..0eb75ad --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/service/ISysOperLogService.java @@ -0,0 +1,70 @@ +package com.boyue.system.service; + +import java.util.List; +import java.util.Map; + +import com.boyue.system.domain.SysOperLog; + +/** + * 操作日志 服务层 + * + * @author boyue + */ +public interface ISysOperLogService +{ + /** + * 新增操作日志 + * + * @param operLog 操作日志对象 + */ + public void insertOperlog(SysOperLog operLog); + + /** + * 查询系统操作日志集合 + * + * @param operLog 操作日志对象 + * @return 操作日志集合 + */ + public List selectOperLogList(SysOperLog operLog); + + /** + * 批量删除系统操作日志 + * + * @param operIds 需要删除的操作日志ID + * @return 结果 + */ + public int deleteOperLogByIds(Long[] operIds); + + /** + * 查询操作日志详细 + * + * @param operId 操作ID + * @return 操作日志对象 + */ + public SysOperLog selectOperLogById(Long operId); + + /** + * 清空操作日志 + */ + public void cleanOperLog(); + + /** + * 获取成功操作的统计信息 + */ + List> getSuccessOperationStats(SysOperLog operLog); + + /** + * 获取失败操作的统计信息 + */ + List> getFailureOperationStats(SysOperLog operLog); + + /** + * 获取按状态分类的操作统计信息 + */ + List> getStatusStats(SysOperLog operLog); + + /** + * 获取按模块和操作类型分类的操作统计信息 + */ + List> getModuleOperationStats(SysOperLog operLog); +} diff --git a/boyue-system/src/main/java/com/boyue/system/service/ISysPostService.java b/boyue-system/src/main/java/com/boyue/system/service/ISysPostService.java new file mode 100644 index 0000000..746fb5d --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/service/ISysPostService.java @@ -0,0 +1,99 @@ +package com.boyue.system.service; + +import java.util.List; +import com.boyue.system.domain.SysPost; + +/** + * 岗位信息 服务层 + * + * @author boyue + */ +public interface ISysPostService +{ + /** + * 查询岗位信息集合 + * + * @param post 岗位信息 + * @return 岗位列表 + */ + public List selectPostList(SysPost post); + + /** + * 查询所有岗位 + * + * @return 岗位列表 + */ + public List selectPostAll(); + + /** + * 通过岗位ID查询岗位信息 + * + * @param postId 岗位ID + * @return 角色对象信息 + */ + public SysPost selectPostById(Long postId); + + /** + * 根据用户ID获取岗位选择框列表 + * + * @param userId 用户ID + * @return 选中岗位ID列表 + */ + public List selectPostListByUserId(Long userId); + + /** + * 校验岗位名称 + * + * @param post 岗位信息 + * @return 结果 + */ + public boolean checkPostNameUnique(SysPost post); + + /** + * 校验岗位编码 + * + * @param post 岗位信息 + * @return 结果 + */ + public boolean checkPostCodeUnique(SysPost post); + + /** + * 通过岗位ID查询岗位使用数量 + * + * @param postId 岗位ID + * @return 结果 + */ + public int countUserPostById(Long postId); + + /** + * 删除岗位信息 + * + * @param postId 岗位ID + * @return 结果 + */ + public int deletePostById(Long postId); + + /** + * 批量删除岗位信息 + * + * @param postIds 需要删除的岗位ID + * @return 结果 + */ + public int deletePostByIds(Long[] postIds); + + /** + * 新增保存岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + public int insertPost(SysPost post); + + /** + * 修改保存岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + public int updatePost(SysPost post); +} diff --git a/boyue-system/src/main/java/com/boyue/system/service/ISysRoleService.java b/boyue-system/src/main/java/com/boyue/system/service/ISysRoleService.java new file mode 100644 index 0000000..ea6b4dd --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/service/ISysRoleService.java @@ -0,0 +1,173 @@ +package com.boyue.system.service; + +import java.util.List; +import java.util.Set; +import com.boyue.common.core.domain.entity.SysRole; +import com.boyue.system.domain.SysUserRole; + +/** + * 角色业务层 + * + * @author boyue + */ +public interface ISysRoleService +{ + /** + * 根据条件分页查询角色数据 + * + * @param role 角色信息 + * @return 角色数据集合信息 + */ + public List selectRoleList(SysRole role); + + /** + * 根据用户ID查询角色列表 + * + * @param userId 用户ID + * @return 角色列表 + */ + public List selectRolesByUserId(Long userId); + + /** + * 根据用户ID查询角色权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + public Set selectRolePermissionByUserId(Long userId); + + /** + * 查询所有角色 + * + * @return 角色列表 + */ + public List selectRoleAll(); + + /** + * 根据用户ID获取角色选择框列表 + * + * @param userId 用户ID + * @return 选中角色ID列表 + */ + public List selectRoleListByUserId(Long userId); + + /** + * 通过角色ID查询角色 + * + * @param roleId 角色ID + * @return 角色对象信息 + */ + public SysRole selectRoleById(Long roleId); + + /** + * 校验角色名称是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + public boolean checkRoleNameUnique(SysRole role); + + /** + * 校验角色权限是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + public boolean checkRoleKeyUnique(SysRole role); + + /** + * 校验角色是否允许操作 + * + * @param role 角色信息 + */ + public void checkRoleAllowed(SysRole role); + + /** + * 校验角色是否有数据权限 + * + * @param roleId 角色id + */ + public void checkRoleDataScope(Long roleId); + + /** + * 通过角色ID查询角色使用数量 + * + * @param roleId 角色ID + * @return 结果 + */ + public int countUserRoleByRoleId(Long roleId); + + /** + * 新增保存角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int insertRole(SysRole role); + + /** + * 修改保存角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int updateRole(SysRole role); + + /** + * 修改角色状态 + * + * @param role 角色信息 + * @return 结果 + */ + public int updateRoleStatus(SysRole role); + + /** + * 修改数据权限信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int authDataScope(SysRole role); + + /** + * 通过角色ID删除角色 + * + * @param roleId 角色ID + * @return 结果 + */ + public int deleteRoleById(Long roleId); + + /** + * 批量删除角色信息 + * + * @param roleIds 需要删除的角色ID + * @return 结果 + */ + public int deleteRoleByIds(Long[] roleIds); + + /** + * 取消授权用户角色 + * + * @param userRole 用户和角色关联信息 + * @return 结果 + */ + public int deleteAuthUser(SysUserRole userRole); + + /** + * 批量取消授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要取消授权的用户数据ID + * @return 结果 + */ + public int deleteAuthUsers(Long roleId, Long[] userIds); + + /** + * 批量选择授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要删除的用户数据ID + * @return 结果 + */ + public int insertAuthUsers(Long roleId, Long[] userIds); +} diff --git a/boyue-system/src/main/java/com/boyue/system/service/ISysUserOnlineService.java b/boyue-system/src/main/java/com/boyue/system/service/ISysUserOnlineService.java new file mode 100644 index 0000000..9c765dc --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/service/ISysUserOnlineService.java @@ -0,0 +1,48 @@ +package com.boyue.system.service; + +import com.boyue.common.core.domain.model.LoginUser; +import com.boyue.system.domain.SysUserOnline; + +/** + * 在线用户 服务层 + * + * @author boyue + */ +public interface ISysUserOnlineService +{ + /** + * 通过登录地址查询信息 + * + * @param ipaddr 登录地址 + * @param user 用户信息 + * @return 在线用户信息 + */ + public SysUserOnline selectOnlineByIpaddr(String ipaddr, LoginUser user); + + /** + * 通过用户名称查询信息 + * + * @param userName 用户名称 + * @param user 用户信息 + * @return 在线用户信息 + */ + public SysUserOnline selectOnlineByUserName(String userName, LoginUser user); + + /** + * 通过登录地址/用户名称查询信息 + * + * @param ipaddr 登录地址 + * @param userName 用户名称 + * @param user 用户信息 + * @return 在线用户信息 + */ + public SysUserOnline selectOnlineByInfo(String ipaddr, String userName, LoginUser user); + + /** + * 设置在线用户信息 + * + * @param user 用户信息 + * @return 在线用户 + */ + public SysUserOnline loginUserToUserOnline(LoginUser user); +} diff --git a/boyue-system/src/main/java/com/boyue/system/service/ISysUserService.java b/boyue-system/src/main/java/com/boyue/system/service/ISysUserService.java new file mode 100644 index 0000000..6fcf8ff --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/service/ISysUserService.java @@ -0,0 +1,222 @@ +package com.boyue.system.service; + +import java.util.List; + +import com.boyue.common.core.domain.entity.SysUser; + +/** + * 用户 业务层 + * + * @author boyue + */ +public interface ISysUserService { + /** + * 根据条件分页查询用户列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectUserList(SysUser user); + + /** + * 根据条件分页查询已分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectAllocatedList(SysUser user); + + /** + * 根据条件分页查询未分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectUnallocatedList(SysUser user); + + /** + * 通过用户名查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + public SysUser selectUserByUserName(String userName); + + /** + * 通过用户ID查询用户 + * + * @param userId 用户ID + * @return 用户对象信息 + */ + public SysUser selectUserById(Long userId); + + /** + * 通过手机号查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + public SysUser selectUserByPhone(String phone); + + /** + * 通过邮箱查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + public SysUser selectUserByEmail(String email); + + /** + * 根据用户ID查询用户所属角色组 + * + * @param userName 用户名 + * @return 结果 + */ + public String selectUserRoleGroup(String userName); + + /** + * 根据用户ID查询用户所属岗位组 + * + * @param userName 用户名 + * @return 结果 + */ + public String selectUserPostGroup(String userName); + + /** + * 校验用户名称是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + public boolean checkUserNameUnique(SysUser user); + + /** + * 校验手机号码是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + public boolean checkPhoneUnique(SysUser user); + + /** + * 校验email是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + public boolean checkEmailUnique(SysUser user); + + /** + * 校验用户是否允许操作 + * + * @param user 用户信息 + */ + public void checkUserAllowed(SysUser user); + + /** + * 校验用户是否有数据权限 + * + * @param userId 用户id + */ + public void checkUserDataScope(Long userId); + + /** + * 新增用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int insertUser(SysUser user); + + /** + * 注册用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public boolean registerUser(SysUser user); + + /** + * 修改用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUser(SysUser user); + + /** + * 用户授权角色 + * + * @param userId 用户ID + * @param roleIds 角色组 + */ + public void insertUserAuth(Long userId, Long[] roleIds); + + /** + * 修改用户状态 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUserStatus(SysUser user); + + /** + * 修改用户基本信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUserProfile(SysUser user); + + /** + * 修改用户头像 + * + * @param userName 用户名 + * @param avatar 头像地址 + * @return 结果 + */ + public boolean updateUserAvatar(String userName, String avatar); + + /** + * 重置用户密码 + * + * @param user 用户信息 + * @return 结果 + */ + public int resetPwd(SysUser user); + + /** + * 重置用户密码 + * + * @param userName 用户名 + * @param password 密码 + * @return 结果 + */ + public int resetUserPwd(String userName, String password); + + /** + * 通过用户ID删除用户 + * + * @param userId 用户ID + * @return 结果 + */ + public int deleteUserById(Long userId); + + /** + * 批量删除用户信息 + * + * @param userIds 需要删除的用户ID + * @return 结果 + */ + public int deleteUserByIds(Long[] userIds); + + /** + * 导入用户数据 + * + * @param userList 用户数据列表 + * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据 + * @param operName 操作用户 + * @return 结果 + */ + public String importUser(List userList, Boolean isUpdateSupport, String operName); +} diff --git a/boyue-system/src/main/java/com/boyue/system/service/impl/SysConfigServiceImpl.java b/boyue-system/src/main/java/com/boyue/system/service/impl/SysConfigServiceImpl.java new file mode 100644 index 0000000..43e5139 --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/service/impl/SysConfigServiceImpl.java @@ -0,0 +1,226 @@ +package com.boyue.system.service.impl; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.Cache; +import org.springframework.stereotype.Service; + +import com.boyue.common.annotation.DataSource; +import com.boyue.common.constant.CacheConstants; +import com.boyue.common.constant.UserConstants; +import com.boyue.common.core.text.Convert; +import com.boyue.common.enums.DataSourceType; +import com.boyue.common.exception.ServiceException; +import com.boyue.common.utils.CacheUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.system.domain.SysConfig; +import com.boyue.system.mapper.SysConfigMapper; +import com.boyue.system.service.ISysConfigService; + +import jakarta.annotation.PostConstruct; + +/** + * 参数配置 服务层实现 + * + * @author boyue + */ +@Service +public class SysConfigServiceImpl implements ISysConfigService +{ + @Autowired + private SysConfigMapper configMapper; + + /** + * 项目启动时,初始化参数到缓存 + */ + @PostConstruct + public void init() + { + loadingConfigCache(); + } + + /** + * 查询参数配置信息 + * + * @param configId 参数配置ID + * @return 参数配置信息 + */ + @Override + @DataSource(DataSourceType.MASTER) + public SysConfig selectConfigById(Long configId) + { + SysConfig config = new SysConfig(); + config.setConfigId(configId); + return configMapper.selectConfig(config); + } + + /** + * 根据键名查询参数配置信息 + * + * @param configKey 参数key + * @return 参数键值 + */ + @Override + public String selectConfigByKey(String configKey) + { + String configValue = Convert.toStr(getCache().get(configKey, String.class)); + if (StringUtils.isNotEmpty(configValue)) + { + return configValue; + } + SysConfig config = new SysConfig(); + config.setConfigKey(configKey); + SysConfig retConfig = configMapper.selectConfig(config); + if (StringUtils.isNotNull(retConfig)) + { + CacheUtils.put(CacheConstants.SYS_CONFIG_KEY, configKey, retConfig.getConfigValue()); + return retConfig.getConfigValue(); + } + return StringUtils.EMPTY; + } + + /** + * 获取验证码开关 + * + * @return true开启,false关闭 + */ + @Override + public boolean selectCaptchaEnabled() + { + String captchaEnabled = selectConfigByKey("sys.account.captchaEnabled"); + return Convert.toBool(captchaEnabled,true); + } + + /** + * 查询参数配置列表 + * + * @param config 参数配置信息 + * @return 参数配置集合 + */ + @Override + public List selectConfigList(SysConfig config) + { + return configMapper.selectConfigList(config); + } + + /** + * 新增参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + @Override + public int insertConfig(SysConfig config) + { + int row = configMapper.insertConfig(config); + if (row > 0) + { + CacheUtils.put(CacheConstants.SYS_CONFIG_KEY, config.getConfigKey(), config.getConfigValue()); + } + return row; + } + + /** + * 修改参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + @Override + public int updateConfig(SysConfig config) + { + SysConfig temp = configMapper.selectConfigById(config.getConfigId()); + if (!StringUtils.equals(temp.getConfigKey(), config.getConfigKey())) + { + CacheUtils.removeIfPresent(CacheConstants.SYS_CONFIG_KEY, temp.getConfigKey()); + } + + int row = configMapper.updateConfig(config); + if (row > 0) + { + CacheUtils.put(CacheConstants.SYS_CONFIG_KEY, config.getConfigKey(), config.getConfigValue()); + } + return row; + } + + /** + * 批量删除参数信息 + * + * @param configIds 需要删除的参数ID + */ + @Override + public void deleteConfigByIds(Long[] configIds) + { + for (Long configId : configIds) + { + SysConfig config = selectConfigById(configId); + if (StringUtils.equals(UserConstants.YES, config.getConfigType())) + { + throw new ServiceException(String.format("内置参数【%1$s】不能删除 ", config.getConfigKey())); + } + configMapper.deleteConfigById(configId); + getCache().evict(config.getConfigKey()); + } + } + + /** + * 加载参数缓存数据 + */ + @Override + public void loadingConfigCache() + { + List configsList = configMapper.selectConfigList(new SysConfig()); + for (SysConfig config : configsList) + { + getCache().put(config.getConfigKey(), config.getConfigValue()); + } + } + + /** + * 清空参数缓存数据 + */ + @Override + public void clearConfigCache() + { + CacheUtils.getCache(CacheConstants.SYS_CONFIG_KEY).clear(); + } + + /** + * 重置参数缓存数据 + */ + @Override + public void resetConfigCache() + { + clearConfigCache(); + loadingConfigCache(); + } + + /** + * 校验参数键名是否唯一 + * + * @param config 参数配置信息 + * @return 结果 + */ + @Override + public boolean checkConfigKeyUnique(SysConfig config) + { + Long configId = StringUtils.isNull(config.getConfigId()) ? -1L : config.getConfigId(); + SysConfig info = configMapper.checkConfigKeyUnique(config.getConfigKey()); + if (StringUtils.isNotNull(info) && info.getConfigId().longValue() != configId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 获取config缓存 + * + * @return + */ + private Cache getCache() + { + return CacheUtils.getCache(CacheConstants.SYS_CONFIG_KEY); + } +} diff --git a/boyue-system/src/main/java/com/boyue/system/service/impl/SysDeptServiceImpl.java b/boyue-system/src/main/java/com/boyue/system/service/impl/SysDeptServiceImpl.java new file mode 100644 index 0000000..00e4e65 --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/service/impl/SysDeptServiceImpl.java @@ -0,0 +1,338 @@ +package com.boyue.system.service.impl; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.boyue.common.annotation.DataScope; +import com.boyue.common.constant.UserConstants; +import com.boyue.common.core.domain.TreeSelect; +import com.boyue.common.core.domain.entity.SysDept; +import com.boyue.common.core.domain.entity.SysRole; +import com.boyue.common.core.domain.entity.SysUser; +import com.boyue.common.core.text.Convert; +import com.boyue.common.exception.ServiceException; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.spring.SpringUtils; +import com.boyue.system.mapper.SysDeptMapper; +import com.boyue.system.mapper.SysRoleMapper; +import com.boyue.system.service.ISysDeptService; + +/** + * 部门管理 服务实现 + * + * @author boyue + */ +@Service +public class SysDeptServiceImpl implements ISysDeptService +{ + @Autowired + private SysDeptMapper deptMapper; + + @Autowired + private SysRoleMapper roleMapper; + + /** + * 查询部门管理数据 + * + * @param dept 部门信息 + * @return 部门信息集合 + */ + @Override + @DataScope(deptAlias = "d") + public List selectDeptList(SysDept dept) + { + return deptMapper.selectDeptList(dept); + } + + /** + * 查询部门树结构信息 + * + * @param dept 部门信息 + * @return 部门树信息集合 + */ + @Override + public List selectDeptTreeList(SysDept dept) + { + List depts = SpringUtils.getAopProxy(this).selectDeptList(dept); + return buildDeptTreeSelect(depts); + } + + /** + * 构建前端所需要树结构 + * + * @param depts 部门列表 + * @return 树结构列表 + */ + @Override + public List buildDeptTree(List depts) + { + List returnList = new ArrayList(); + List tempList = depts.stream().map(SysDept::getDeptId).collect(Collectors.toList()); + for (SysDept dept : depts) + { + // 如果是顶级节点, 遍历该父节点的所有子节点 + if (!tempList.contains(dept.getParentId())) + { + recursionFn(depts, dept); + returnList.add(dept); + } + } + if (returnList.isEmpty()) + { + returnList = depts; + } + return returnList; + } + + /** + * 构建前端所需要下拉树结构 + * + * @param depts 部门列表 + * @return 下拉树结构列表 + */ + @Override + public List buildDeptTreeSelect(List depts) + { + List deptTrees = buildDeptTree(depts); + return deptTrees.stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + /** + * 根据角色ID查询部门树信息 + * + * @param roleId 角色ID + * @return 选中部门列表 + */ + @Override + public List selectDeptListByRoleId(Long roleId) + { + SysRole role = roleMapper.selectRoleById(roleId); + return deptMapper.selectDeptListByRoleId(roleId, role.isDeptCheckStrictly()); + } + + /** + * 根据部门ID查询信息 + * + * @param deptId 部门ID + * @return 部门信息 + */ + @Override + public SysDept selectDeptById(Long deptId) + { + return deptMapper.selectDeptById(deptId); + } + + /** + * 根据ID查询所有子部门(正常状态) + * + * @param deptId 部门ID + * @return 子部门数 + */ + @Override + public int selectNormalChildrenDeptById(Long deptId) + { + return deptMapper.selectNormalChildrenDeptById(deptId); + } + + /** + * 是否存在子节点 + * + * @param deptId 部门ID + * @return 结果 + */ + @Override + public boolean hasChildByDeptId(Long deptId) + { + int result = deptMapper.hasChildByDeptId(deptId); + return result > 0; + } + + /** + * 查询部门是否存在用户 + * + * @param deptId 部门ID + * @return 结果 true 存在 false 不存在 + */ + @Override + public boolean checkDeptExistUser(Long deptId) + { + int result = deptMapper.checkDeptExistUser(deptId); + return result > 0; + } + + /** + * 校验部门名称是否唯一 + * + * @param dept 部门信息 + * @return 结果 + */ + @Override + public boolean checkDeptNameUnique(SysDept dept) + { + Long deptId = StringUtils.isNull(dept.getDeptId()) ? -1L : dept.getDeptId(); + SysDept info = deptMapper.checkDeptNameUnique(dept.getDeptName(), dept.getParentId()); + if (StringUtils.isNotNull(info) && info.getDeptId().longValue() != deptId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验部门是否有数据权限 + * + * @param deptId 部门id + */ + @Override + public void checkDeptDataScope(Long deptId) + { + if (!SysUser.isAdmin(SecurityUtils.getUserId())) + { + SysDept dept = new SysDept(); + dept.setDeptId(deptId); + List depts = SpringUtils.getAopProxy(this).selectDeptList(dept); + if (StringUtils.isEmpty(depts)) + { + throw new ServiceException("没有权限访问部门数据!"); + } + } + } + + /** + * 新增保存部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + @Override + public int insertDept(SysDept dept) + { + SysDept info = deptMapper.selectDeptById(dept.getParentId()); + // 如果父节点不为正常状态,则不允许新增子节点 + if (!UserConstants.DEPT_NORMAL.equals(info.getStatus())) + { + throw new ServiceException("部门停用,不允许新增"); + } + dept.setAncestors(info.getAncestors() + "," + dept.getParentId()); + return deptMapper.insertDept(dept); + } + + /** + * 修改保存部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + @Override + public int updateDept(SysDept dept) + { + SysDept newParentDept = deptMapper.selectDeptById(dept.getParentId()); + SysDept oldDept = deptMapper.selectDeptById(dept.getDeptId()); + if (StringUtils.isNotNull(newParentDept) && StringUtils.isNotNull(oldDept)) + { + String newAncestors = newParentDept.getAncestors() + "," + newParentDept.getDeptId(); + String oldAncestors = oldDept.getAncestors(); + dept.setAncestors(newAncestors); + updateDeptChildren(dept.getDeptId(), newAncestors, oldAncestors); + } + int result = deptMapper.updateDept(dept); + if (UserConstants.DEPT_NORMAL.equals(dept.getStatus()) && StringUtils.isNotEmpty(dept.getAncestors()) + && !StringUtils.equals("0", dept.getAncestors())) + { + // 如果该部门是启用状态,则启用该部门的所有上级部门 + updateParentDeptStatusNormal(dept); + } + return result; + } + + /** + * 修改该部门的父级部门状态 + * + * @param dept 当前部门 + */ + private void updateParentDeptStatusNormal(SysDept dept) + { + String ancestors = dept.getAncestors(); + Long[] deptIds = Convert.toLongArray(ancestors); + deptMapper.updateDeptStatusNormal(deptIds); + } + + /** + * 修改子元素关系 + * + * @param deptId 被修改的部门ID + * @param newAncestors 新的父ID集合 + * @param oldAncestors 旧的父ID集合 + */ + public void updateDeptChildren(Long deptId, String newAncestors, String oldAncestors) + { + List children = deptMapper.selectChildrenDeptById(deptId); + for (SysDept child : children) + { + child.setAncestors(child.getAncestors().replaceFirst(oldAncestors, newAncestors)); + } + if (children.size() > 0) + { + deptMapper.updateDeptChildren(children); + } + } + + /** + * 删除部门管理信息 + * + * @param deptId 部门ID + * @return 结果 + */ + @Override + public int deleteDeptById(Long deptId) + { + return deptMapper.deleteDeptById(deptId); + } + + /** + * 递归列表 + */ + private void recursionFn(List list, SysDept t) + { + // 得到子节点列表 + List childList = getChildList(list, t); + t.setChildren(childList); + for (SysDept tChild : childList) + { + if (hasChild(list, tChild)) + { + recursionFn(list, tChild); + } + } + } + + /** + * 得到子节点列表 + */ + private List getChildList(List list, SysDept t) + { + List tlist = new ArrayList(); + Iterator it = list.iterator(); + while (it.hasNext()) + { + SysDept n = (SysDept) it.next(); + if (StringUtils.isNotNull(n.getParentId()) && n.getParentId().longValue() == t.getDeptId().longValue()) + { + tlist.add(n); + } + } + return tlist; + } + + /** + * 判断是否有子节点 + */ + private boolean hasChild(List list, SysDept t) + { + return getChildList(list, t).size() > 0; + } +} diff --git a/boyue-system/src/main/java/com/boyue/system/service/impl/SysDictDataServiceImpl.java b/boyue-system/src/main/java/com/boyue/system/service/impl/SysDictDataServiceImpl.java new file mode 100644 index 0000000..e89cbb4 --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/service/impl/SysDictDataServiceImpl.java @@ -0,0 +1,111 @@ +package com.boyue.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.boyue.common.core.domain.entity.SysDictData; +import com.boyue.common.utils.DictUtils; +import com.boyue.system.mapper.SysDictDataMapper; +import com.boyue.system.service.ISysDictDataService; + +/** + * 字典 业务层处理 + * + * @author boyue + */ +@Service +public class SysDictDataServiceImpl implements ISysDictDataService +{ + @Autowired + private SysDictDataMapper dictDataMapper; + + /** + * 根据条件分页查询字典数据 + * + * @param dictData 字典数据信息 + * @return 字典数据集合信息 + */ + @Override + public List selectDictDataList(SysDictData dictData) + { + return dictDataMapper.selectDictDataList(dictData); + } + + /** + * 根据字典类型和字典键值查询字典数据信息 + * + * @param dictType 字典类型 + * @param dictValue 字典键值 + * @return 字典标签 + */ + @Override + public String selectDictLabel(String dictType, String dictValue) + { + return dictDataMapper.selectDictLabel(dictType, dictValue); + } + + /** + * 根据字典数据ID查询信息 + * + * @param dictCode 字典数据ID + * @return 字典数据 + */ + @Override + public SysDictData selectDictDataById(Long dictCode) + { + return dictDataMapper.selectDictDataById(dictCode); + } + + /** + * 批量删除字典数据信息 + * + * @param dictCodes 需要删除的字典数据ID + */ + @Override + public void deleteDictDataByIds(Long[] dictCodes) + { + for (Long dictCode : dictCodes) + { + SysDictData data = selectDictDataById(dictCode); + dictDataMapper.deleteDictDataById(dictCode); + List dictDatas = dictDataMapper.selectDictDataByType(data.getDictType()); + DictUtils.setDictCache(data.getDictType(), dictDatas); + } + } + + /** + * 新增保存字典数据信息 + * + * @param data 字典数据信息 + * @return 结果 + */ + @Override + public int insertDictData(SysDictData data) + { + int row = dictDataMapper.insertDictData(data); + if (row > 0) + { + List dictDatas = dictDataMapper.selectDictDataByType(data.getDictType()); + DictUtils.setDictCache(data.getDictType(), dictDatas); + } + return row; + } + + /** + * 修改保存字典数据信息 + * + * @param data 字典数据信息 + * @return 结果 + */ + @Override + public int updateDictData(SysDictData data) + { + int row = dictDataMapper.updateDictData(data); + if (row > 0) + { + List dictDatas = dictDataMapper.selectDictDataByType(data.getDictType()); + DictUtils.setDictCache(data.getDictType(), dictDatas); + } + return row; + } +} diff --git a/boyue-system/src/main/java/com/boyue/system/service/impl/SysDictTypeServiceImpl.java b/boyue-system/src/main/java/com/boyue/system/service/impl/SysDictTypeServiceImpl.java new file mode 100644 index 0000000..732141d --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/service/impl/SysDictTypeServiceImpl.java @@ -0,0 +1,226 @@ +package com.boyue.system.service.impl; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.boyue.common.constant.UserConstants; +import com.boyue.common.core.domain.entity.SysDictData; +import com.boyue.common.core.domain.entity.SysDictType; +import com.boyue.common.exception.ServiceException; +import com.boyue.common.utils.DictUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.system.mapper.SysDictDataMapper; +import com.boyue.system.mapper.SysDictTypeMapper; +import com.boyue.system.service.ISysDictTypeService; + +import jakarta.annotation.PostConstruct; + +/** + * 字典 业务层处理 + * + * @author boyue + */ +@Service +public class SysDictTypeServiceImpl implements ISysDictTypeService +{ + @Autowired + private SysDictTypeMapper dictTypeMapper; + + @Autowired + private SysDictDataMapper dictDataMapper; + + /** + * 项目启动时,初始化字典到缓存 + */ + @PostConstruct + public void init() + { + loadingDictCache(); + } + + /** + * 根据条件分页查询字典类型 + * + * @param dictType 字典类型信息 + * @return 字典类型集合信息 + */ + @Override + public List selectDictTypeList(SysDictType dictType) + { + return dictTypeMapper.selectDictTypeList(dictType); + } + + /** + * 根据所有字典类型 + * + * @return 字典类型集合信息 + */ + @Override + public List selectDictTypeAll() + { + return dictTypeMapper.selectDictTypeAll(); + } + + /** + * 根据字典类型查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据集合信息 + */ + @Override + public List selectDictDataByType(String dictType) + { + List dictDatas = DictUtils.getDictCache(dictType); + if (StringUtils.isNotEmpty(dictDatas)) + { + return dictDatas; + } + dictDatas = dictDataMapper.selectDictDataByType(dictType); + if (StringUtils.isNotEmpty(dictDatas)) + { + DictUtils.setDictCache(dictType, dictDatas); + return dictDatas; + } + return null; + } + + /** + * 根据字典类型ID查询信息 + * + * @param dictId 字典类型ID + * @return 字典类型 + */ + @Override + public SysDictType selectDictTypeById(Long dictId) + { + return dictTypeMapper.selectDictTypeById(dictId); + } + + /** + * 根据字典类型查询信息 + * + * @param dictType 字典类型 + * @return 字典类型 + */ + @Override + public SysDictType selectDictTypeByType(String dictType) + { + return dictTypeMapper.selectDictTypeByType(dictType); + } + + /** + * 批量删除字典类型信息 + * + * @param dictIds 需要删除的字典ID + */ + @Override + public void deleteDictTypeByIds(Long[] dictIds) + { + for (Long dictId : dictIds) + { + SysDictType dictType = selectDictTypeById(dictId); + if (dictDataMapper.countDictDataByType(dictType.getDictType()) > 0) + { + throw new ServiceException("%1$s已分配,不能删除".formatted(dictType.getDictName())); + } + dictTypeMapper.deleteDictTypeById(dictId); + DictUtils.removeDictCache(dictType.getDictType()); + } + } + + /** + * 加载字典缓存数据 + */ + @Override + public void loadingDictCache() + { + SysDictData dictData = new SysDictData(); + dictData.setStatus("0"); + Map> dictDataMap = dictDataMapper.selectDictDataList(dictData).stream().collect(Collectors.groupingBy(SysDictData::getDictType)); + for (Map.Entry> entry : dictDataMap.entrySet()) + { + DictUtils.setDictCache(entry.getKey(), entry.getValue().stream().sorted(Comparator.comparing(SysDictData::getDictSort)).collect(Collectors.toList())); + } + } + + /** + * 清空字典缓存数据 + */ + @Override + public void clearDictCache() + { + DictUtils.clearDictCache(); + } + + /** + * 重置字典缓存数据 + */ + @Override + public void resetDictCache() + { + clearDictCache(); + loadingDictCache(); + } + + /** + * 新增保存字典类型信息 + * + * @param dict 字典类型信息 + * @return 结果 + */ + @Override + public int insertDictType(SysDictType dict) + { + int row = dictTypeMapper.insertDictType(dict); + if (row > 0) + { + DictUtils.removeDictCache(dict.getDictType()); + } + return row; + } + + /** + * 修改保存字典类型信息 + * + * @param dict 字典类型信息 + * @return 结果 + */ + @Override + @Transactional + public int updateDictType(SysDictType dict) + { + SysDictType oldDict = dictTypeMapper.selectDictTypeById(dict.getDictId()); + dictDataMapper.updateDictDataType(oldDict.getDictType(), dict.getDictType()); + int row = dictTypeMapper.updateDictType(dict); + if (row > 0) + { + List dictDatas = dictDataMapper.selectDictDataByType(dict.getDictType()); + DictUtils.setDictCache(dict.getDictType(), dictDatas); + } + return row; + } + + /** + * 校验字典类型称是否唯一 + * + * @param dict 字典类型 + * @return 结果 + */ + @Override + public boolean checkDictTypeUnique(SysDictType dict) + { + Long dictId = StringUtils.isNull(dict.getDictId()) ? -1L : dict.getDictId(); + SysDictType dictType = dictTypeMapper.checkDictTypeUnique(dict.getDictType()); + if (StringUtils.isNotNull(dictType) && dictType.getDictId().longValue() != dictId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } +} diff --git a/boyue-system/src/main/java/com/boyue/system/service/impl/SysLogininforServiceImpl.java b/boyue-system/src/main/java/com/boyue/system/service/impl/SysLogininforServiceImpl.java new file mode 100644 index 0000000..20f0be3 --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/service/impl/SysLogininforServiceImpl.java @@ -0,0 +1,65 @@ +package com.boyue.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.boyue.system.domain.SysLogininfor; +import com.boyue.system.mapper.SysLogininforMapper; +import com.boyue.system.service.ISysLogininforService; + +/** + * 系统访问日志情况信息 服务层处理 + * + * @author boyue + */ +@Service +public class SysLogininforServiceImpl implements ISysLogininforService +{ + + @Autowired + private SysLogininforMapper logininforMapper; + + /** + * 新增系统登录日志 + * + * @param logininfor 访问日志对象 + */ + @Override + public void insertLogininfor(SysLogininfor logininfor) + { + logininforMapper.insertLogininfor(logininfor); + } + + /** + * 查询系统登录日志集合 + * + * @param logininfor 访问日志对象 + * @return 登录记录集合 + */ + @Override + public List selectLogininforList(SysLogininfor logininfor) + { + return logininforMapper.selectLogininforList(logininfor); + } + + /** + * 批量删除系统登录日志 + * + * @param infoIds 需要删除的登录日志ID + * @return 结果 + */ + @Override + public int deleteLogininforByIds(Long[] infoIds) + { + return logininforMapper.deleteLogininforByIds(infoIds); + } + + /** + * 清空系统登录日志 + */ + @Override + public void cleanLogininfor() + { + logininforMapper.cleanLogininfor(); + } +} diff --git a/boyue-system/src/main/java/com/boyue/system/service/impl/SysMenuServiceImpl.java b/boyue-system/src/main/java/com/boyue/system/service/impl/SysMenuServiceImpl.java new file mode 100644 index 0000000..55cd742 --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/service/impl/SysMenuServiceImpl.java @@ -0,0 +1,484 @@ +package com.boyue.system.service.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.boyue.common.constant.Constants; +import com.boyue.common.constant.UserConstants; +import com.boyue.common.core.domain.TreeSelect; +import com.boyue.common.core.domain.entity.SysMenu; +import com.boyue.common.core.domain.entity.SysRole; +import com.boyue.common.core.domain.entity.SysUser; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.system.domain.vo.MetaVo; +import com.boyue.system.domain.vo.RouterVo; +import com.boyue.system.mapper.SysMenuMapper; +import com.boyue.system.mapper.SysRoleMapper; +import com.boyue.system.mapper.SysRoleMenuMapper; +import com.boyue.system.service.ISysMenuService; + +/** + * 菜单 业务层处理 + * + * @author boyue + */ +@Service +public class SysMenuServiceImpl implements ISysMenuService { + public static final String PREMISSION_STRING = "perms[\"{0}\"]"; + + @Autowired + private SysMenuMapper menuMapper; + + @Autowired + private SysRoleMapper roleMapper; + + @Autowired + private SysRoleMenuMapper roleMenuMapper; + + /** + * 根据用户查询系统菜单列表 + * + * @param userId 用户ID + * @return 菜单列表 + */ + @Override + public List selectMenuList(Long userId) { + return selectMenuList(new SysMenu(), userId); + } + + /** + * 查询系统菜单列表 + * + * @param menu 菜单信息 + * @return 菜单列表 + */ + @Override + public List selectMenuList(SysMenu menu, Long userId) { + List menuList = null; + // 管理员显示所有菜单信息 + if (SysUser.isAdmin(userId)) { + menuList = menuMapper.selectMenuList(menu); + } else { + menu.getParams().put("userId", userId); + menuList = menuMapper.selectMenuListByUserId(menu); + } + return menuList; + } + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + @Override + public Set selectMenuPermsByUserId(Long userId) { + List perms = menuMapper.selectMenuPermsByUserId(userId); + Set permsSet = new HashSet<>(); + for (String perm : perms) { + if (StringUtils.isNotEmpty(perm)) { + permsSet.addAll(Arrays.asList(perm.trim().split(","))); + } + } + return permsSet; + } + + /** + * 根据角色ID查询权限 + * + * @param roleId 角色ID + * @return 权限列表 + */ + @Override + public Set selectMenuPermsByRoleId(Long roleId) { + List perms = menuMapper.selectMenuPermsByRoleId(roleId); + Set permsSet = new HashSet<>(); + for (String perm : perms) { + if (StringUtils.isNotEmpty(perm)) { + permsSet.addAll(Arrays.asList(perm.trim().split(","))); + } + } + return permsSet; + } + + /** + * 根据用户ID查询菜单 + * + * @param userId 用户名称 + * @return 菜单列表 + */ + @Override + public List selectMenuTreeByUserId(Long userId) { + List menus = null; + if (SecurityUtils.isAdmin(userId)) { + menus = menuMapper.selectMenuTreeAll(); + } else { + menus = menuMapper.selectMenuTreeByUserId(userId); + } + return getChildPerms(menus, 0); + } + + /** + * 根据角色ID查询菜单树信息 + * + * @param roleId 角色ID + * @return 选中菜单列表 + */ + @Override + public List selectMenuListByRoleId(Long roleId) { + SysRole role = roleMapper.selectRoleById(roleId); + return menuMapper.selectMenuListByRoleId(roleId, role.isMenuCheckStrictly()); + } + + /** + * 构建前端路由所需要的菜单 + * + * @param menus 菜单列表 + * @return 路由列表 + */ + @Override + public List buildMenus(List menus) { + List routers = new LinkedList(); + for (SysMenu menu : menus) { + RouterVo router = new RouterVo(); + router.setHidden("1".equals(menu.getVisible())); + router.setName(getRouteName(menu)); + router.setPath(getRouterPath(menu)); + router.setComponent(getComponent(menu)); + router.setQuery(menu.getQuery()); + router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), + menu.getPath())); + List cMenus = menu.getChildren(); + if (StringUtils.isNotEmpty(cMenus) && UserConstants.TYPE_DIR.equals(menu.getMenuType())) { + router.setAlwaysShow(true); + router.setRedirect("noRedirect"); + router.setChildren(buildMenus(cMenus)); + } else if (isMenuFrame(menu)) { + router.setMeta(null); + List childrenList = new ArrayList(); + RouterVo children = new RouterVo(); + children.setPath(menu.getPath()); + children.setComponent(menu.getComponent()); + children.setName(getRouteName(menu.getRouteName(), menu.getPath())); + children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), + StringUtils.equals("1", menu.getIsCache()), menu.getPath())); + children.setQuery(menu.getQuery()); + childrenList.add(children); + router.setChildren(childrenList); + } else if (menu.getParentId().intValue() == 0 && isInnerLink(menu)) { + router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon())); + router.setPath("/"); + List childrenList = new ArrayList(); + RouterVo children = new RouterVo(); + String routerPath = innerLinkReplaceEach(menu.getPath()); + children.setPath(routerPath); + children.setComponent(UserConstants.INNER_LINK); + children.setName(getRouteName(menu.getRouteName(), routerPath)); + children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), menu.getPath())); + childrenList.add(children); + router.setChildren(childrenList); + } + routers.add(router); + } + return routers; + } + + /** + * 构建前端所需要树结构 + * + * @param menus 菜单列表 + * @return 树结构列表 + */ + @Override + public List buildMenuTree(List menus) { + List returnList = new ArrayList(); + List tempList = menus.stream().map(SysMenu::getMenuId).collect(Collectors.toList()); + for (Iterator iterator = menus.iterator(); iterator.hasNext();) { + SysMenu menu = (SysMenu) iterator.next(); + // 如果是顶级节点, 遍历该父节点的所有子节点 + if (!tempList.contains(menu.getParentId())) { + recursionFn(menus, menu); + returnList.add(menu); + } + } + if (returnList.isEmpty()) { + returnList = menus; + } + return returnList; + } + + /** + * 构建前端所需要下拉树结构 + * + * @param menus 菜单列表 + * @return 下拉树结构列表 + */ + @Override + public List buildMenuTreeSelect(List menus) { + List menuTrees = buildMenuTree(menus); + return menuTrees.stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + /** + * 根据菜单ID查询信息 + * + * @param menuId 菜单ID + * @return 菜单信息 + */ + @Override + public SysMenu selectMenuById(Long menuId) { + return menuMapper.selectMenuById(menuId); + } + + /** + * 是否存在菜单子节点 + * + * @param menuId 菜单ID + * @return 结果 + */ + @Override + public boolean hasChildByMenuId(Long menuId) { + int result = menuMapper.hasChildByMenuId(menuId); + return result > 0; + } + + /** + * 查询菜单使用数量 + * + * @param menuId 菜单ID + * @return 结果 + */ + @Override + public boolean checkMenuExistRole(Long menuId) { + int result = roleMenuMapper.checkMenuExistRole(menuId); + return result > 0; + } + + /** + * 新增保存菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + @Override + public int insertMenu(SysMenu menu) { + return menuMapper.insertMenu(menu); + } + + /** + * 修改保存菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + @Override + public int updateMenu(SysMenu menu) { + return menuMapper.updateMenu(menu); + } + + /** + * 删除菜单管理信息 + * + * @param menuId 菜单ID + * @return 结果 + */ + @Override + public int deleteMenuById(Long menuId) { + return menuMapper.deleteMenuById(menuId); + } + + /** + * 校验菜单名称是否唯一 + * + * @param menu 菜单信息 + * @return 结果 + */ + @Override + public boolean checkMenuNameUnique(SysMenu menu) { + Long menuId = StringUtils.isNull(menu.getMenuId()) ? -1L : menu.getMenuId(); + SysMenu info = menuMapper.checkMenuNameUnique(menu.getMenuName(), menu.getParentId()); + if (StringUtils.isNotNull(info) && info.getMenuId().longValue() != menuId.longValue()) { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 获取路由名称 + * + * @param menu 菜单信息 + * @return 路由名称 + */ + public String getRouteName(SysMenu menu) { + // 非外链并且是一级目录(类型为目录) + if (isMenuFrame(menu)) { + return StringUtils.EMPTY; + } + return getRouteName(menu.getRouteName(), menu.getPath()); + } + + /** + * 获取路由名称,如没有配置路由名称则取路由地址 + * + * @param routerName 路由名称 + * @param path 路由地址 + * @return 路由名称(驼峰格式) + */ + public String getRouteName(String name, String path) { + String routerName = StringUtils.isNotEmpty(name) ? name : path; + return StringUtils.capitalize(routerName); + } + + /** + * 获取路由地址 + * + * @param menu 菜单信息 + * @return 路由地址 + */ + public String getRouterPath(SysMenu menu) { + String routerPath = menu.getPath(); + // 内链打开外网方式 + if (menu.getParentId().intValue() != 0 && isInnerLink(menu)) { + routerPath = innerLinkReplaceEach(routerPath); + } + // 非外链并且是一级目录(类型为目录) + if (0 == menu.getParentId().intValue() && UserConstants.TYPE_DIR.equals(menu.getMenuType()) + && UserConstants.NO_FRAME.equals(menu.getIsFrame())) { + routerPath = "/" + menu.getPath(); + } + // 非外链并且是一级目录(类型为菜单) + else if (isMenuFrame(menu)) { + routerPath = "/"; + } + return routerPath; + } + + /** + * 获取组件信息 + * + * @param menu 菜单信息 + * @return 组件信息 + */ + public String getComponent(SysMenu menu) { + String component = UserConstants.LAYOUT; + if (StringUtils.isNotEmpty(menu.getComponent()) && !isMenuFrame(menu)) { + component = menu.getComponent(); + } else if (StringUtils.isEmpty(menu.getComponent()) && menu.getParentId().intValue() != 0 + && isInnerLink(menu)) { + component = UserConstants.INNER_LINK; + } else if (StringUtils.isEmpty(menu.getComponent()) && isParentView(menu)) { + component = UserConstants.PARENT_VIEW; + } + return component; + } + + /** + * 是否为菜单内部跳转 + * + * @param menu 菜单信息 + * @return 结果 + */ + public boolean isMenuFrame(SysMenu menu) { + return menu.getParentId().intValue() == 0 && UserConstants.TYPE_MENU.equals(menu.getMenuType()) + && menu.getIsFrame().equals(UserConstants.NO_FRAME); + } + + /** + * 是否为内链组件 + * + * @param menu 菜单信息 + * @return 结果 + */ + public boolean isInnerLink(SysMenu menu) { + return menu.getIsFrame().equals(UserConstants.NO_FRAME) && StringUtils.ishttp(menu.getPath()); + } + + /** + * 是否为parent_view组件 + * + * @param menu 菜单信息 + * @return 结果 + */ + public boolean isParentView(SysMenu menu) { + return menu.getParentId().intValue() != 0 && UserConstants.TYPE_DIR.equals(menu.getMenuType()); + } + + /** + * 根据父节点的ID获取所有子节点 + * + * @param list 分类表 + * @param parentId 传入的父节点ID + * @return String + */ + public List getChildPerms(List list, int parentId) { + List returnList = new ArrayList(); + for (Iterator iterator = list.iterator(); iterator.hasNext();) { + SysMenu t = (SysMenu) iterator.next(); + // 一、根据传入的某个父节点ID,遍历该父节点的所有子节点 + if (t.getParentId() == parentId) { + recursionFn(list, t); + returnList.add(t); + } + } + return returnList; + } + + /** + * 递归列表 + * + * @param list 分类表 + * @param t 子节点 + */ + private void recursionFn(List list, SysMenu t) { + // 得到子节点列表 + List childList = getChildList(list, t); + t.setChildren(childList); + for (SysMenu tChild : childList) { + if (hasChild(list, tChild)) { + recursionFn(list, tChild); + } + } + } + + /** + * 得到子节点列表 + */ + private List getChildList(List list, SysMenu t) { + List tlist = new ArrayList(); + Iterator it = list.iterator(); + while (it.hasNext()) { + SysMenu n = (SysMenu) it.next(); + if (n.getParentId().longValue() == t.getMenuId().longValue()) { + tlist.add(n); + } + } + return tlist; + } + + /** + * 判断是否有子节点 + */ + private boolean hasChild(List list, SysMenu t) { + return getChildList(list, t).size() > 0; + } + + /** + * 内链域名特殊字符替换 + * + * @return 替换后的内链域名 + */ + public String innerLinkReplaceEach(String path) { + return StringUtils.replaceEach(path, new String[] { Constants.HTTP, Constants.HTTPS, Constants.WWW, "." }, + new String[] { "", "", "", "/" }); + } +} diff --git a/boyue-system/src/main/java/com/boyue/system/service/impl/SysNoticeServiceImpl.java b/boyue-system/src/main/java/com/boyue/system/service/impl/SysNoticeServiceImpl.java new file mode 100644 index 0000000..f45df0e --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/service/impl/SysNoticeServiceImpl.java @@ -0,0 +1,92 @@ +package com.boyue.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.boyue.system.domain.SysNotice; +import com.boyue.system.mapper.SysNoticeMapper; +import com.boyue.system.service.ISysNoticeService; + +/** + * 公告 服务层实现 + * + * @author boyue + */ +@Service +public class SysNoticeServiceImpl implements ISysNoticeService +{ + @Autowired + private SysNoticeMapper noticeMapper; + + /** + * 查询公告信息 + * + * @param noticeId 公告ID + * @return 公告信息 + */ + @Override + public SysNotice selectNoticeById(Long noticeId) + { + return noticeMapper.selectNoticeById(noticeId); + } + + /** + * 查询公告列表 + * + * @param notice 公告信息 + * @return 公告集合 + */ + @Override + public List selectNoticeList(SysNotice notice) + { + return noticeMapper.selectNoticeList(notice); + } + + /** + * 新增公告 + * + * @param notice 公告信息 + * @return 结果 + */ + @Override + public int insertNotice(SysNotice notice) + { + return noticeMapper.insertNotice(notice); + } + + /** + * 修改公告 + * + * @param notice 公告信息 + * @return 结果 + */ + @Override + public int updateNotice(SysNotice notice) + { + return noticeMapper.updateNotice(notice); + } + + /** + * 删除公告对象 + * + * @param noticeId 公告ID + * @return 结果 + */ + @Override + public int deleteNoticeById(Long noticeId) + { + return noticeMapper.deleteNoticeById(noticeId); + } + + /** + * 批量删除公告信息 + * + * @param noticeIds 需要删除的公告ID + * @return 结果 + */ + @Override + public int deleteNoticeByIds(Long[] noticeIds) + { + return noticeMapper.deleteNoticeByIds(noticeIds); + } +} diff --git a/boyue-system/src/main/java/com/boyue/system/service/impl/SysOperLogServiceImpl.java b/boyue-system/src/main/java/com/boyue/system/service/impl/SysOperLogServiceImpl.java new file mode 100644 index 0000000..f9a027f --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/service/impl/SysOperLogServiceImpl.java @@ -0,0 +1,99 @@ +package com.boyue.system.service.impl; + +import java.util.List; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.boyue.system.domain.SysOperLog; +import com.boyue.system.mapper.SysOperLogMapper; +import com.boyue.system.service.ISysOperLogService; + +/** + * 操作日志 服务层处理 + * + * @author boyue + */ +@Service +public class SysOperLogServiceImpl implements ISysOperLogService +{ + @Autowired + private SysOperLogMapper operLogMapper; + + /** + * 新增操作日志 + * + * @param operLog 操作日志对象 + */ + @Override + public void insertOperlog(SysOperLog operLog) + { + operLogMapper.insertOperlog(operLog); + } + + /** + * 查询系统操作日志集合 + * + * @param operLog 操作日志对象 + * @return 操作日志集合 + */ + @Override + public List selectOperLogList(SysOperLog operLog) + { + return operLogMapper.selectOperLogList(operLog); + } + + /** + * 批量删除系统操作日志 + * + * @param operIds 需要删除的操作日志ID + * @return 结果 + */ + @Override + public int deleteOperLogByIds(Long[] operIds) + { + return operLogMapper.deleteOperLogByIds(operIds); + } + + /** + * 查询操作日志详细 + * + * @param operId 操作ID + * @return 操作日志对象 + */ + @Override + public SysOperLog selectOperLogById(Long operId) + { + return operLogMapper.selectOperLogById(operId); + } + + /** + * 清空操作日志 + */ + @Override + public void cleanOperLog() + { + operLogMapper.cleanOperLog(); + } + + @Override + public List> getSuccessOperationStats(SysOperLog operLog) { + return operLogMapper.getSuccessOperationStats(operLog); + } + + @Override + public List> getFailureOperationStats(SysOperLog operLog) { + return operLogMapper.getFailureOperationStats(operLog); + } + + @Override + public List> getStatusStats(SysOperLog operLog) { + return operLogMapper.getStatusStats(operLog); + } + + @Override + public List> getModuleOperationStats(SysOperLog operLog) { + return operLogMapper.getModuleOperationStats(operLog); + } +} diff --git a/boyue-system/src/main/java/com/boyue/system/service/impl/SysPostServiceImpl.java b/boyue-system/src/main/java/com/boyue/system/service/impl/SysPostServiceImpl.java new file mode 100644 index 0000000..170c99a --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/service/impl/SysPostServiceImpl.java @@ -0,0 +1,178 @@ +package com.boyue.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.boyue.common.constant.UserConstants; +import com.boyue.common.exception.ServiceException; +import com.boyue.common.utils.StringUtils; +import com.boyue.system.domain.SysPost; +import com.boyue.system.mapper.SysPostMapper; +import com.boyue.system.mapper.SysUserPostMapper; +import com.boyue.system.service.ISysPostService; + +/** + * 岗位信息 服务层处理 + * + * @author boyue + */ +@Service +public class SysPostServiceImpl implements ISysPostService +{ + @Autowired + private SysPostMapper postMapper; + + @Autowired + private SysUserPostMapper userPostMapper; + + /** + * 查询岗位信息集合 + * + * @param post 岗位信息 + * @return 岗位信息集合 + */ + @Override + public List selectPostList(SysPost post) + { + return postMapper.selectPostList(post); + } + + /** + * 查询所有岗位 + * + * @return 岗位列表 + */ + @Override + public List selectPostAll() + { + return postMapper.selectPostAll(); + } + + /** + * 通过岗位ID查询岗位信息 + * + * @param postId 岗位ID + * @return 角色对象信息 + */ + @Override + public SysPost selectPostById(Long postId) + { + return postMapper.selectPostById(postId); + } + + /** + * 根据用户ID获取岗位选择框列表 + * + * @param userId 用户ID + * @return 选中岗位ID列表 + */ + @Override + public List selectPostListByUserId(Long userId) + { + return postMapper.selectPostListByUserId(userId); + } + + /** + * 校验岗位名称是否唯一 + * + * @param post 岗位信息 + * @return 结果 + */ + @Override + public boolean checkPostNameUnique(SysPost post) + { + Long postId = StringUtils.isNull(post.getPostId()) ? -1L : post.getPostId(); + SysPost info = postMapper.checkPostNameUnique(post.getPostName()); + if (StringUtils.isNotNull(info) && info.getPostId().longValue() != postId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验岗位编码是否唯一 + * + * @param post 岗位信息 + * @return 结果 + */ + @Override + public boolean checkPostCodeUnique(SysPost post) + { + Long postId = StringUtils.isNull(post.getPostId()) ? -1L : post.getPostId(); + SysPost info = postMapper.checkPostCodeUnique(post.getPostCode()); + if (StringUtils.isNotNull(info) && info.getPostId().longValue() != postId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 通过岗位ID查询岗位使用数量 + * + * @param postId 岗位ID + * @return 结果 + */ + @Override + public int countUserPostById(Long postId) + { + return userPostMapper.countUserPostById(postId); + } + + /** + * 删除岗位信息 + * + * @param postId 岗位ID + * @return 结果 + */ + @Override + public int deletePostById(Long postId) + { + return postMapper.deletePostById(postId); + } + + /** + * 批量删除岗位信息 + * + * @param postIds 需要删除的岗位ID + * @return 结果 + */ + @Override + public int deletePostByIds(Long[] postIds) + { + for (Long postId : postIds) + { + SysPost post = selectPostById(postId); + if (countUserPostById(postId) > 0) + { + throw new ServiceException("%1$s已分配,不能删除".formatted(post.getPostName())); + } + } + return postMapper.deletePostByIds(postIds); + } + + /** + * 新增保存岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + @Override + public int insertPost(SysPost post) + { + return postMapper.insertPost(post); + } + + /** + * 修改保存岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + @Override + public int updatePost(SysPost post) + { + return postMapper.updatePost(post); + } +} diff --git a/boyue-system/src/main/java/com/boyue/system/service/impl/SysRoleServiceImpl.java b/boyue-system/src/main/java/com/boyue/system/service/impl/SysRoleServiceImpl.java new file mode 100644 index 0000000..a1adbc9 --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/service/impl/SysRoleServiceImpl.java @@ -0,0 +1,424 @@ +package com.boyue.system.service.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.boyue.common.annotation.DataScope; +import com.boyue.common.constant.UserConstants; +import com.boyue.common.core.domain.entity.SysRole; +import com.boyue.common.core.domain.entity.SysUser; +import com.boyue.common.exception.ServiceException; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.spring.SpringUtils; +import com.boyue.system.domain.SysRoleDept; +import com.boyue.system.domain.SysRoleMenu; +import com.boyue.system.domain.SysUserRole; +import com.boyue.system.mapper.SysRoleDeptMapper; +import com.boyue.system.mapper.SysRoleMapper; +import com.boyue.system.mapper.SysRoleMenuMapper; +import com.boyue.system.mapper.SysUserRoleMapper; +import com.boyue.system.service.ISysRoleService; + +/** + * 角色 业务层处理 + * + * @author boyue + */ +@Service +public class SysRoleServiceImpl implements ISysRoleService +{ + @Autowired + private SysRoleMapper roleMapper; + + @Autowired + private SysRoleMenuMapper roleMenuMapper; + + @Autowired + private SysUserRoleMapper userRoleMapper; + + @Autowired + private SysRoleDeptMapper roleDeptMapper; + + /** + * 根据条件分页查询角色数据 + * + * @param role 角色信息 + * @return 角色数据集合信息 + */ + @Override + @DataScope(deptAlias = "d") + public List selectRoleList(SysRole role) + { + return roleMapper.selectRoleList(role); + } + + /** + * 根据用户ID查询角色 + * + * @param userId 用户ID + * @return 角色列表 + */ + @Override + public List selectRolesByUserId(Long userId) + { + List userRoles = roleMapper.selectRolePermissionByUserId(userId); + List roles = selectRoleAll(); + for (SysRole role : roles) + { + for (SysRole userRole : userRoles) + { + if (role.getRoleId().longValue() == userRole.getRoleId().longValue()) + { + role.setFlag(true); + break; + } + } + } + return roles; + } + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + @Override + public Set selectRolePermissionByUserId(Long userId) + { + List perms = roleMapper.selectRolePermissionByUserId(userId); + Set permsSet = new HashSet<>(); + for (SysRole perm : perms) + { + if (StringUtils.isNotNull(perm)) + { + permsSet.addAll(Arrays.asList(perm.getRoleKey().trim().split(","))); + } + } + return permsSet; + } + + /** + * 查询所有角色 + * + * @return 角色列表 + */ + @Override + public List selectRoleAll() + { + return SpringUtils.getAopProxy(this).selectRoleList(new SysRole()); + } + + /** + * 根据用户ID获取角色选择框列表 + * + * @param userId 用户ID + * @return 选中角色ID列表 + */ + @Override + public List selectRoleListByUserId(Long userId) + { + return roleMapper.selectRoleListByUserId(userId); + } + + /** + * 通过角色ID查询角色 + * + * @param roleId 角色ID + * @return 角色对象信息 + */ + @Override + public SysRole selectRoleById(Long roleId) + { + return roleMapper.selectRoleById(roleId); + } + + /** + * 校验角色名称是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + public boolean checkRoleNameUnique(SysRole role) + { + Long roleId = StringUtils.isNull(role.getRoleId()) ? -1L : role.getRoleId(); + SysRole info = roleMapper.checkRoleNameUnique(role.getRoleName()); + if (StringUtils.isNotNull(info) && info.getRoleId().longValue() != roleId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验角色权限是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + public boolean checkRoleKeyUnique(SysRole role) + { + Long roleId = StringUtils.isNull(role.getRoleId()) ? -1L : role.getRoleId(); + SysRole info = roleMapper.checkRoleKeyUnique(role.getRoleKey()); + if (StringUtils.isNotNull(info) && info.getRoleId().longValue() != roleId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验角色是否允许操作 + * + * @param role 角色信息 + */ + @Override + public void checkRoleAllowed(SysRole role) + { + if (StringUtils.isNotNull(role.getRoleId()) && role.isAdmin()) + { + throw new ServiceException("不允许操作超级管理员角色"); + } + } + + /** + * 校验角色是否有数据权限 + * + * @param roleId 角色id + */ + @Override + public void checkRoleDataScope(Long roleId) + { + if (!SysUser.isAdmin(SecurityUtils.getUserId())) + { + SysRole role = new SysRole(); + role.setRoleId(roleId); + List roles = SpringUtils.getAopProxy(this).selectRoleList(role); + if (StringUtils.isEmpty(roles)) + { + throw new ServiceException("没有权限访问角色数据!"); + } + } + } + + /** + * 通过角色ID查询角色使用数量 + * + * @param roleId 角色ID + * @return 结果 + */ + @Override + public int countUserRoleByRoleId(Long roleId) + { + return userRoleMapper.countUserRoleByRoleId(roleId); + } + + /** + * 新增保存角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + @Transactional + public int insertRole(SysRole role) + { + // 新增角色信息 + roleMapper.insertRole(role); + return insertRoleMenu(role); + } + + /** + * 修改保存角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + @Transactional + public int updateRole(SysRole role) + { + // 修改角色信息 + roleMapper.updateRole(role); + // 删除角色与菜单关联 + roleMenuMapper.deleteRoleMenuByRoleId(role.getRoleId()); + return insertRoleMenu(role); + } + + /** + * 修改角色状态 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + public int updateRoleStatus(SysRole role) + { + return roleMapper.updateRole(role); + } + + /** + * 修改数据权限信息 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + @Transactional + public int authDataScope(SysRole role) + { + // 修改角色信息 + roleMapper.updateRole(role); + // 删除角色与部门关联 + roleDeptMapper.deleteRoleDeptByRoleId(role.getRoleId()); + // 新增角色和部门信息(数据权限) + return insertRoleDept(role); + } + + /** + * 新增角色菜单信息 + * + * @param role 角色对象 + */ + public int insertRoleMenu(SysRole role) + { + int rows = 1; + // 新增用户与角色管理 + List list = new ArrayList(); + for (Long menuId : role.getMenuIds()) + { + SysRoleMenu rm = new SysRoleMenu(); + rm.setRoleId(role.getRoleId()); + rm.setMenuId(menuId); + list.add(rm); + } + if (list.size() > 0) + { + rows = roleMenuMapper.batchRoleMenu(list); + } + return rows; + } + + /** + * 新增角色部门信息(数据权限) + * + * @param role 角色对象 + */ + public int insertRoleDept(SysRole role) + { + int rows = 1; + // 新增角色与部门(数据权限)管理 + List list = new ArrayList(); + for (Long deptId : role.getDeptIds()) + { + SysRoleDept rd = new SysRoleDept(); + rd.setRoleId(role.getRoleId()); + rd.setDeptId(deptId); + list.add(rd); + } + if (list.size() > 0) + { + rows = roleDeptMapper.batchRoleDept(list); + } + return rows; + } + + /** + * 通过角色ID删除角色 + * + * @param roleId 角色ID + * @return 结果 + */ + @Override + @Transactional + public int deleteRoleById(Long roleId) + { + // 删除角色与菜单关联 + roleMenuMapper.deleteRoleMenuByRoleId(roleId); + // 删除角色与部门关联 + roleDeptMapper.deleteRoleDeptByRoleId(roleId); + return roleMapper.deleteRoleById(roleId); + } + + /** + * 批量删除角色信息 + * + * @param roleIds 需要删除的角色ID + * @return 结果 + */ + @Override + @Transactional + public int deleteRoleByIds(Long[] roleIds) + { + for (Long roleId : roleIds) + { + checkRoleAllowed(new SysRole(roleId)); + checkRoleDataScope(roleId); + SysRole role = selectRoleById(roleId); + if (countUserRoleByRoleId(roleId) > 0) + { + throw new ServiceException("%1$s已分配,不能删除".formatted(role.getRoleName())); + } + } + // 删除角色与菜单关联 + roleMenuMapper.deleteRoleMenu(roleIds); + // 删除角色与部门关联 + roleDeptMapper.deleteRoleDept(roleIds); + return roleMapper.deleteRoleByIds(roleIds); + } + + /** + * 取消授权用户角色 + * + * @param userRole 用户和角色关联信息 + * @return 结果 + */ + @Override + public int deleteAuthUser(SysUserRole userRole) + { + return userRoleMapper.deleteUserRoleInfo(userRole); + } + + /** + * 批量取消授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要取消授权的用户数据ID + * @return 结果 + */ + @Override + public int deleteAuthUsers(Long roleId, Long[] userIds) + { + return userRoleMapper.deleteUserRoleInfos(roleId, userIds); + } + + /** + * 批量选择授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要授权的用户数据ID + * @return 结果 + */ + @Override + public int insertAuthUsers(Long roleId, Long[] userIds) + { + // 新增用户与角色管理 + List list = new ArrayList(); + for (Long userId : userIds) + { + SysUserRole ur = new SysUserRole(); + ur.setUserId(userId); + ur.setRoleId(roleId); + list.add(ur); + } + return userRoleMapper.batchUserRole(list); + } +} diff --git a/boyue-system/src/main/java/com/boyue/system/service/impl/SysUserOnlineServiceImpl.java b/boyue-system/src/main/java/com/boyue/system/service/impl/SysUserOnlineServiceImpl.java new file mode 100644 index 0000000..2c7c22c --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/service/impl/SysUserOnlineServiceImpl.java @@ -0,0 +1,96 @@ +package com.boyue.system.service.impl; + +import org.springframework.stereotype.Service; +import com.boyue.common.core.domain.model.LoginUser; +import com.boyue.common.utils.StringUtils; +import com.boyue.system.domain.SysUserOnline; +import com.boyue.system.service.ISysUserOnlineService; + +/** + * 在线用户 服务层处理 + * + * @author boyue + */ +@Service +public class SysUserOnlineServiceImpl implements ISysUserOnlineService +{ + /** + * 通过登录地址查询信息 + * + * @param ipaddr 登录地址 + * @param user 用户信息 + * @return 在线用户信息 + */ + @Override + public SysUserOnline selectOnlineByIpaddr(String ipaddr, LoginUser user) + { + if (StringUtils.equals(ipaddr, user.getIpaddr())) + { + return loginUserToUserOnline(user); + } + return null; + } + + /** + * 通过用户名称查询信息 + * + * @param userName 用户名称 + * @param user 用户信息 + * @return 在线用户信息 + */ + @Override + public SysUserOnline selectOnlineByUserName(String userName, LoginUser user) + { + if (StringUtils.equals(userName, user.getUsername())) + { + return loginUserToUserOnline(user); + } + return null; + } + + /** + * 通过登录地址/用户名称查询信息 + * + * @param ipaddr 登录地址 + * @param userName 用户名称 + * @param user 用户信息 + * @return 在线用户信息 + */ + @Override + public SysUserOnline selectOnlineByInfo(String ipaddr, String userName, LoginUser user) + { + if (StringUtils.equals(ipaddr, user.getIpaddr()) && StringUtils.equals(userName, user.getUsername())) + { + return loginUserToUserOnline(user); + } + return null; + } + + /** + * 设置在线用户信息 + * + * @param user 用户信息 + * @return 在线用户 + */ + @Override + public SysUserOnline loginUserToUserOnline(LoginUser user) + { + if (StringUtils.isNull(user) || StringUtils.isNull(user.getUser())) + { + return null; + } + SysUserOnline sysUserOnline = new SysUserOnline(); + sysUserOnline.setTokenId(user.getToken()); + sysUserOnline.setUserName(user.getUsername()); + sysUserOnline.setIpaddr(user.getIpaddr()); + sysUserOnline.setLoginLocation(user.getLoginLocation()); + sysUserOnline.setBrowser(user.getBrowser()); + sysUserOnline.setOs(user.getOs()); + sysUserOnline.setLoginTime(user.getLoginTime()); + if (StringUtils.isNotNull(user.getUser().getDept())) + { + sysUserOnline.setDeptName(user.getUser().getDept().getDeptName()); + } + return sysUserOnline; + } +} diff --git a/boyue-system/src/main/java/com/boyue/system/service/impl/SysUserServiceImpl.java b/boyue-system/src/main/java/com/boyue/system/service/impl/SysUserServiceImpl.java new file mode 100644 index 0000000..cf9e1db --- /dev/null +++ b/boyue-system/src/main/java/com/boyue/system/service/impl/SysUserServiceImpl.java @@ -0,0 +1,515 @@ +package com.boyue.system.service.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import com.boyue.common.annotation.DataScope; +import com.boyue.common.constant.UserConstants; +import com.boyue.common.core.domain.entity.SysRole; +import com.boyue.common.core.domain.entity.SysUser; +import com.boyue.common.exception.ServiceException; +import com.boyue.common.utils.SecurityUtils; +import com.boyue.common.utils.StringUtils; +import com.boyue.common.utils.bean.BeanValidators; +import com.boyue.common.utils.spring.SpringUtils; +import com.boyue.system.domain.SysPost; +import com.boyue.system.domain.SysUserPost; +import com.boyue.system.domain.SysUserRole; +import com.boyue.system.mapper.SysPostMapper; +import com.boyue.system.mapper.SysRoleMapper; +import com.boyue.system.mapper.SysUserMapper; +import com.boyue.system.mapper.SysUserPostMapper; +import com.boyue.system.mapper.SysUserRoleMapper; +import com.boyue.system.service.ISysConfigService; +import com.boyue.system.service.ISysUserService; + +import jakarta.validation.Validator; + +/** + * 用户 业务层处理 + * + * @author boyue + */ +@Service +public class SysUserServiceImpl implements ISysUserService { + private static final Logger log = LoggerFactory.getLogger(SysUserServiceImpl.class); + + @Autowired + private SysUserMapper userMapper; + + @Autowired + private SysRoleMapper roleMapper; + + @Autowired + private SysPostMapper postMapper; + + @Autowired + private SysUserRoleMapper userRoleMapper; + + @Autowired + private SysUserPostMapper userPostMapper; + + @Autowired + private ISysConfigService configService; + + @Autowired + protected Validator validator; + + /** + * 根据条件分页查询用户列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + @Override + @DataScope(deptAlias = "d", userAlias = "u") + public List selectUserList(SysUser user) { + return userMapper.selectUserList(user); + } + + /** + * 根据条件分页查询已分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + @Override + @DataScope(deptAlias = "d", userAlias = "u") + public List selectAllocatedList(SysUser user) { + return userMapper.selectAllocatedList(user); + } + + /** + * 根据条件分页查询未分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + @Override + @DataScope(deptAlias = "d", userAlias = "u") + public List selectUnallocatedList(SysUser user) { + return userMapper.selectUnallocatedList(user); + } + + /** + * 通过用户名查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + @Override + public SysUser selectUserByUserName(String userName) { + return userMapper.selectUserByUserName(userName); + } + + /** + * 通过用户ID查询用户 + * + * @param userId 用户ID + * @return 用户对象信息 + */ + @Override + public SysUser selectUserById(Long userId) { + return userMapper.selectUserById(userId); + } + + /** + * 通过手机号查询用户 + * + * @param phone 用户ID + * @return 用户对象信息 + */ + @Override + public SysUser selectUserByPhone(String phone) { + return userMapper.selectUserByPhone(phone); + } + + /** + * 通过邮箱查询用户 + * + * @param email 用户名 + * @return 用户对象信息 + */ + @Override + public SysUser selectUserByEmail(String email){ + return userMapper.selectUserByEmail(email); + } + + /** + * 查询用户所属角色组 + * + * @param userName 用户名 + * @return 结果 + */ + @Override + public String selectUserRoleGroup(String userName) { + List list = roleMapper.selectRolesByUserName(userName); + if (CollectionUtils.isEmpty(list)) { + return StringUtils.EMPTY; + } + return list.stream().map(SysRole::getRoleName).collect(Collectors.joining(",")); + } + + /** + * 查询用户所属岗位组 + * + * @param userName 用户名 + * @return 结果 + */ + @Override + public String selectUserPostGroup(String userName) { + List list = postMapper.selectPostsByUserName(userName); + if (CollectionUtils.isEmpty(list)) { + return StringUtils.EMPTY; + } + return list.stream().map(SysPost::getPostName).collect(Collectors.joining(",")); + } + + /** + * 校验用户名称是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public boolean checkUserNameUnique(SysUser user) { + Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); + SysUser info = userMapper.checkUserNameUnique(user.getUserName()); + if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验手机号码是否唯一 + * + * @param user 用户信息 + * @return + */ + @Override + public boolean checkPhoneUnique(SysUser user) { + Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); + SysUser info = userMapper.checkPhoneUnique(user.getPhonenumber()); + if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验email是否唯一 + * + * @param user 用户信息 + * @return + */ + @Override + public boolean checkEmailUnique(SysUser user) { + Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); + SysUser info = userMapper.checkEmailUnique(user.getEmail()); + if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验用户是否允许操作 + * + * @param user 用户信息 + */ + @Override + public void checkUserAllowed(SysUser user) { + if (StringUtils.isNotNull(user.getUserId()) && user.isAdmin()) { + throw new ServiceException("不允许操作超级管理员用户"); + } + } + + /** + * 校验用户是否有数据权限 + * + * @param userId 用户id + */ + @Override + public void checkUserDataScope(Long userId) { + if (!SysUser.isAdmin(SecurityUtils.getUserId())) { + SysUser user = new SysUser(); + user.setUserId(userId); + List users = SpringUtils.getAopProxy(this).selectUserList(user); + if (StringUtils.isEmpty(users)) { + throw new ServiceException("没有权限访问用户数据!"); + } + } + } + + /** + * 新增保存用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + @Transactional + public int insertUser(SysUser user) { + // 新增用户信息 + int rows = userMapper.insertUser(user); + // 新增用户岗位关联 + insertUserPost(user); + // 新增用户与角色管理 + insertUserRole(user); + return rows; + } + + /** + * 注册用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public boolean registerUser(SysUser user) { + return userMapper.insertUser(user) > 0; + } + + /** + * 修改保存用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + @Transactional + public int updateUser(SysUser user) { + Long userId = user.getUserId(); + // 删除用户与角色关联 + userRoleMapper.deleteUserRoleByUserId(userId); + // 新增用户与角色管理 + insertUserRole(user); + // 删除用户与岗位关联 + userPostMapper.deleteUserPostByUserId(userId); + // 新增用户与岗位管理 + insertUserPost(user); + return userMapper.updateUser(user); + } + + /** + * 用户授权角色 + * + * @param userId 用户ID + * @param roleIds 角色组 + */ + @Override + @Transactional + public void insertUserAuth(Long userId, Long[] roleIds) { + userRoleMapper.deleteUserRoleByUserId(userId); + insertUserRole(userId, roleIds); + } + + /** + * 修改用户状态 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public int updateUserStatus(SysUser user) { + return userMapper.updateUser(user); + } + + /** + * 修改用户基本信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public int updateUserProfile(SysUser user) { + return userMapper.updateUser(user); + } + + /** + * 修改用户头像 + * + * @param userName 用户名 + * @param avatar 头像地址 + * @return 结果 + */ + @Override + public boolean updateUserAvatar(String userName, String avatar) { + return userMapper.updateUserAvatar(userName, avatar) > 0; + } + + /** + * 重置用户密码 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public int resetPwd(SysUser user) { + return userMapper.updateUser(user); + } + + /** + * 重置用户密码 + * + * @param userName 用户名 + * @param password 密码 + * @return 结果 + */ + @Override + public int resetUserPwd(String userName, String password) { + return userMapper.resetUserPwd(userName, password); + } + + /** + * 新增用户角色信息 + * + * @param user 用户对象 + */ + public void insertUserRole(SysUser user) { + this.insertUserRole(user.getUserId(), user.getRoleIds()); + } + + /** + * 新增用户岗位信息 + * + * @param user 用户对象 + */ + public void insertUserPost(SysUser user) { + Long[] posts = user.getPostIds(); + if (StringUtils.isNotEmpty(posts)) { + // 新增用户与岗位管理 + List list = new ArrayList(posts.length); + for (Long postId : posts) { + SysUserPost up = new SysUserPost(); + up.setUserId(user.getUserId()); + up.setPostId(postId); + list.add(up); + } + userPostMapper.batchUserPost(list); + } + } + + /** + * 新增用户角色信息 + * + * @param userId 用户ID + * @param roleIds 角色组 + */ + public void insertUserRole(Long userId, Long[] roleIds) { + if (StringUtils.isNotEmpty(roleIds)) { + // 新增用户与角色管理 + List list = new ArrayList(roleIds.length); + for (Long roleId : roleIds) { + SysUserRole ur = new SysUserRole(); + ur.setUserId(userId); + ur.setRoleId(roleId); + list.add(ur); + } + userRoleMapper.batchUserRole(list); + } + } + + /** + * 通过用户ID删除用户 + * + * @param userId 用户ID + * @return 结果 + */ + @Override + @Transactional + public int deleteUserById(Long userId) { + // 删除用户与角色关联 + userRoleMapper.deleteUserRoleByUserId(userId); + // 删除用户与岗位表 + userPostMapper.deleteUserPostByUserId(userId); + return userMapper.deleteUserById(userId); + } + + /** + * 批量删除用户信息 + * + * @param userIds 需要删除的用户ID + * @return 结果 + */ + @Override + @Transactional + public int deleteUserByIds(Long[] userIds) { + for (Long userId : userIds) { + checkUserAllowed(new SysUser(userId)); + checkUserDataScope(userId); + } + // 删除用户与角色关联 + userRoleMapper.deleteUserRole(userIds); + // 删除用户与岗位关联 + userPostMapper.deleteUserPost(userIds); + return userMapper.deleteUserByIds(userIds); + } + + /** + * 导入用户数据 + * + * @param userList 用户数据列表 + * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据 + * @param operName 操作用户 + * @return 结果 + */ + @Override + public String importUser(List userList, Boolean isUpdateSupport, String operName) { + if (StringUtils.isNull(userList) || userList.size() == 0) { + throw new ServiceException("导入用户数据不能为空!"); + } + int successNum = 0; + int failureNum = 0; + StringBuilder successMsg = new StringBuilder(); + StringBuilder failureMsg = new StringBuilder(); + String password = configService.selectConfigByKey("sys.user.initPassword"); + for (SysUser user : userList) { + try { + // 验证是否存在这个用户 + SysUser u = userMapper.selectUserByUserName(user.getUserName()); + if (StringUtils.isNull(u)) { + BeanValidators.validateWithException(validator, user); + user.setPassword(SecurityUtils.encryptPassword(password)); + user.setCreateBy(operName); + userMapper.insertUser(user); + successNum++; + successMsg.append("
" + successNum + "、账号 " + user.getUserName() + " 导入成功"); + } else if (isUpdateSupport) { + BeanValidators.validateWithException(validator, user); + checkUserAllowed(u); + checkUserDataScope(u.getUserId()); + user.setUserId(u.getUserId()); + user.setUpdateBy(operName); + userMapper.updateUser(user); + successNum++; + successMsg.append("
" + successNum + "、账号 " + user.getUserName() + " 更新成功"); + } else { + failureNum++; + failureMsg.append("
" + failureNum + "、账号 " + user.getUserName() + " 已存在"); + } + } catch (Exception e) { + failureNum++; + String msg = "
" + failureNum + "、账号 " + user.getUserName() + " 导入失败:"; + failureMsg.append(msg + e.getMessage()); + log.error(msg, e); + } + } + if (failureNum > 0) { + failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:"); + throw new ServiceException(failureMsg.toString()); + } else { + successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:"); + } + return successMsg.toString(); + } +} diff --git a/boyue-system/src/main/resources/mapper/system/SysConfigMapper.xml b/boyue-system/src/main/resources/mapper/system/SysConfigMapper.xml new file mode 100644 index 0000000..80300c3 --- /dev/null +++ b/boyue-system/src/main/resources/mapper/system/SysConfigMapper.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + select config_id, config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark + from sys_config + + + + + + + and config_id = #{configId} + + + and config_key = #{configKey} + + + + + + + + + + + + + + insert into sys_config ( + config_name, + config_key, + config_value, + config_type, + create_by, + remark, + create_time + )values( + #{configName}, + #{configKey}, + #{configValue}, + #{configType}, + #{createBy}, + #{remark}, + sysdate() + ) + + + + update sys_config + + config_name = #{configName}, + config_key = #{configKey}, + config_value = #{configValue}, + config_type = #{configType}, + update_by = #{updateBy}, + remark = #{remark}, + update_time = sysdate() + + where config_id = #{configId} + + + + delete from sys_config where config_id = #{configId} + + + + delete from sys_config where config_id in + + #{configId} + + + + \ No newline at end of file diff --git a/boyue-system/src/main/resources/mapper/system/SysDeptMapper.xml b/boyue-system/src/main/resources/mapper/system/SysDeptMapper.xml new file mode 100644 index 0000000..c42daef --- /dev/null +++ b/boyue-system/src/main/resources/mapper/system/SysDeptMapper.xml @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.create_by, d.create_time + from sys_dept d + + + + + + + + + + + + + + + + + + + + insert into sys_dept( + dept_id, + parent_id, + dept_name, + ancestors, + order_num, + leader, + phone, + email, + status, + create_by, + create_time + )values( + #{deptId}, + #{parentId}, + #{deptName}, + #{ancestors}, + #{orderNum}, + #{leader}, + #{phone}, + #{email}, + #{status}, + #{createBy}, + sysdate() + ) + + + + update sys_dept + + parent_id = #{parentId}, + dept_name = #{deptName}, + ancestors = #{ancestors}, + order_num = #{orderNum}, + leader = #{leader}, + phone = #{phone}, + email = #{email}, + status = #{status}, + update_by = #{updateBy}, + update_time = sysdate() + + where dept_id = #{deptId} + + + + update sys_dept set ancestors = + + when #{item.deptId} then #{item.ancestors} + + where dept_id in + + #{item.deptId} + + + + + update sys_dept set status = '0' where dept_id in + + #{deptId} + + + + + update sys_dept set del_flag = '2' where dept_id = #{deptId} + + + \ No newline at end of file diff --git a/boyue-system/src/main/resources/mapper/system/SysDictDataMapper.xml b/boyue-system/src/main/resources/mapper/system/SysDictDataMapper.xml new file mode 100644 index 0000000..989a5cf --- /dev/null +++ b/boyue-system/src/main/resources/mapper/system/SysDictDataMapper.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + select dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark + from sys_dict_data + + + + + + + + + + + + + + delete from sys_dict_data where dict_code = #{dictCode} + + + + delete from sys_dict_data where dict_code in + + #{dictCode} + + + + + update sys_dict_data + + dict_sort = #{dictSort}, + dict_label = #{dictLabel}, + dict_value = #{dictValue}, + dict_type = #{dictType}, + css_class = #{cssClass}, + list_class = #{listClass}, + is_default = #{isDefault}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where dict_code = #{dictCode} + + + + update sys_dict_data set dict_type = #{newDictType} where dict_type = #{oldDictType} + + + + insert into sys_dict_data( + dict_sort, + dict_label, + dict_value, + dict_type, + css_class, + list_class, + is_default, + status, + remark, + create_by, + create_time + )values( + #{dictSort}, + #{dictLabel}, + #{dictValue}, + #{dictType}, + #{cssClass}, + #{listClass}, + #{isDefault}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + \ No newline at end of file diff --git a/boyue-system/src/main/resources/mapper/system/SysDictTypeMapper.xml b/boyue-system/src/main/resources/mapper/system/SysDictTypeMapper.xml new file mode 100644 index 0000000..a62b081 --- /dev/null +++ b/boyue-system/src/main/resources/mapper/system/SysDictTypeMapper.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + select dict_id, dict_name, dict_type, status, create_by, create_time, remark + from sys_dict_type + + + + + + + + + + + + + + delete from sys_dict_type where dict_id = #{dictId} + + + + delete from sys_dict_type where dict_id in + + #{dictId} + + + + + update sys_dict_type + + dict_name = #{dictName}, + dict_type = #{dictType}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where dict_id = #{dictId} + + + + insert into sys_dict_type( + dict_name, + dict_type, + status, + remark, + create_by, + create_time + )values( + #{dictName}, + #{dictType}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + \ No newline at end of file diff --git a/boyue-system/src/main/resources/mapper/system/SysLogininforMapper.xml b/boyue-system/src/main/resources/mapper/system/SysLogininforMapper.xml new file mode 100644 index 0000000..c68531e --- /dev/null +++ b/boyue-system/src/main/resources/mapper/system/SysLogininforMapper.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + insert into sys_logininfor (user_name, status, ipaddr, login_location, browser, os, msg, login_time) + values (#{userName}, #{status}, #{ipaddr}, #{loginLocation}, #{browser}, #{os}, #{msg}, sysdate()) + + + + + + delete from sys_logininfor where info_id in + + #{infoId} + + + + + truncate table sys_logininfor + + + \ No newline at end of file diff --git a/boyue-system/src/main/resources/mapper/system/SysMenuMapper.xml b/boyue-system/src/main/resources/mapper/system/SysMenuMapper.xml new file mode 100644 index 0000000..0c27399 --- /dev/null +++ b/boyue-system/src/main/resources/mapper/system/SysMenuMapper.xml @@ -0,0 +1,206 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select menu_id, menu_name, parent_id, order_num, path, component, sys_menu.query, route_name, is_frame, is_cache, menu_type, visible, status, COALESCE(perms,'') as perms, icon, create_time + from sys_menu + + + + + + + + + + + + + + + + + + + + + + + + + + update sys_menu + + menu_name = #{menuName}, + parent_id = #{parentId}, + order_num = #{orderNum}, + path = #{path}, + component = #{component}, + query = #{query}, + route_name = #{routeName}, + is_frame = #{isFrame}, + is_cache = #{isCache}, + menu_type = #{menuType}, + visible = #{visible}, + status = #{status}, + perms = #{perms}, + icon = #{icon}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where menu_id = #{menuId} + + + + insert into sys_menu( + menu_id, + parent_id, + menu_name, + order_num, + path, + component, + query, + route_name, + is_frame, + is_cache, + menu_type, + visible, + status, + perms, + icon, + remark, + create_by, + create_time + )values( + #{menuId}, + #{parentId}, + #{menuName}, + #{orderNum}, + #{path}, + #{component}, + #{query}, + #{routeName}, + #{isFrame}, + #{isCache}, + #{menuType}, + #{visible}, + #{status}, + #{perms}, + #{icon}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + delete from sys_menu where menu_id = #{menuId} + + + \ No newline at end of file diff --git a/boyue-system/src/main/resources/mapper/system/SysNoticeMapper.xml b/boyue-system/src/main/resources/mapper/system/SysNoticeMapper.xml new file mode 100644 index 0000000..71e2d5e --- /dev/null +++ b/boyue-system/src/main/resources/mapper/system/SysNoticeMapper.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + select notice_id, notice_title, notice_type, cast(notice_content as char) as notice_content, status, create_by, create_time, update_by, update_time, remark + from sys_notice + + + + + + + + insert into sys_notice ( + notice_title, + notice_type, + notice_content, + status, + remark, + create_by, + create_time + )values( + #{noticeTitle}, + #{noticeType}, + #{noticeContent}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + update sys_notice + + notice_title = #{noticeTitle}, + notice_type = #{noticeType}, + notice_content = #{noticeContent}, + status = #{status}, + update_by = #{updateBy}, + update_time = sysdate() + + where notice_id = #{noticeId} + + + + delete from sys_notice where notice_id = #{noticeId} + + + + delete from sys_notice where notice_id in + + #{noticeId} + + + + \ No newline at end of file diff --git a/boyue-system/src/main/resources/mapper/system/SysOperLogMapper.xml b/boyue-system/src/main/resources/mapper/system/SysOperLogMapper.xml new file mode 100644 index 0000000..0e70e36 --- /dev/null +++ b/boyue-system/src/main/resources/mapper/system/SysOperLogMapper.xml @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + select oper_id, title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url, oper_ip, oper_location, oper_param, json_result, status, error_msg, oper_time, cost_time + from sys_oper_log + + + + insert into sys_oper_log(title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url, oper_ip, oper_location, oper_param, json_result, status, error_msg, cost_time, oper_time) + values (#{title}, #{businessType}, #{method}, #{requestMethod}, #{operatorType}, #{operName}, #{deptName}, #{operUrl}, #{operIp}, #{operLocation}, #{operParam}, #{jsonResult}, #{status}, #{errorMsg}, #{costTime}, sysdate()) + + + + + + delete from sys_oper_log where oper_id in + + #{operId} + + + + + + + truncate table sys_oper_log + + + + + + AND title like concat('%', #{title}, '%') + + + AND business_type = #{businessType} + + + AND request_method = #{requestMethod} + + + AND business_type in + + #{businessType} + + + + AND status = #{status} + + + AND oper_time >= #{params.beginTime} + + + AND oper_time <= #{params.endTime} + + + + + + + + + + + + + + \ No newline at end of file diff --git a/boyue-system/src/main/resources/mapper/system/SysPostMapper.xml b/boyue-system/src/main/resources/mapper/system/SysPostMapper.xml new file mode 100644 index 0000000..a3b32f0 --- /dev/null +++ b/boyue-system/src/main/resources/mapper/system/SysPostMapper.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + select post_id, post_code, post_name, post_sort, status, create_by, create_time, remark + from sys_post + + + + + + + + + + + + + + + + + + update sys_post + + post_code = #{postCode}, + post_name = #{postName}, + post_sort = #{postSort}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where post_id = #{postId} + + + + insert into sys_post( + post_id, + post_code, + post_name, + post_sort, + status, + remark, + create_by, + create_time + )values( + #{postId}, + #{postCode}, + #{postName}, + #{postSort}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + delete from sys_post where post_id = #{postId} + + + + delete from sys_post where post_id in + + #{postId} + + + + \ No newline at end of file diff --git a/boyue-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml b/boyue-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml new file mode 100644 index 0000000..114ddf8 --- /dev/null +++ b/boyue-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + delete from sys_role_dept where role_id=#{roleId} + + + + + + delete from sys_role_dept where role_id in + + #{roleId} + + + + + insert into sys_role_dept(role_id, dept_id) values + + (#{item.roleId},#{item.deptId}) + + + + \ No newline at end of file diff --git a/boyue-system/src/main/resources/mapper/system/SysRoleMapper.xml b/boyue-system/src/main/resources/mapper/system/SysRoleMapper.xml new file mode 100644 index 0000000..6d7f72f --- /dev/null +++ b/boyue-system/src/main/resources/mapper/system/SysRoleMapper.xml @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + select distinct r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.menu_check_strictly, r.dept_check_strictly, + r.status, r.del_flag, r.create_time, r.remark + from sys_role r + left join sys_user_role ur on ur.role_id = r.role_id + left join sys_user u on u.user_id = ur.user_id + left join sys_dept d on u.dept_id = d.dept_id + + + + + + + + + + + + + + + + + + + + insert into sys_role( + role_id, + role_name, + role_key, + role_sort, + data_scope, + menu_check_strictly, + dept_check_strictly, + status, + remark, + create_by, + create_time + )values( + #{roleId}, + #{roleName}, + #{roleKey}, + #{roleSort}, + #{dataScope}, + #{menuCheckStrictly}, + #{deptCheckStrictly}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + update sys_role + + role_name = #{roleName}, + role_key = #{roleKey}, + role_sort = #{roleSort}, + data_scope = #{dataScope}, + menu_check_strictly = #{menuCheckStrictly}, + dept_check_strictly = #{deptCheckStrictly}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where role_id = #{roleId} + + + + update sys_role set del_flag = '2' where role_id = #{roleId} + + + + update sys_role set del_flag = '2' where role_id in + + #{roleId} + + + + \ No newline at end of file diff --git a/boyue-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml b/boyue-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml new file mode 100644 index 0000000..0093f76 --- /dev/null +++ b/boyue-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + delete from sys_role_menu where role_id=#{roleId} + + + + delete from sys_role_menu where role_id in + + #{roleId} + + + + + insert into sys_role_menu(role_id, menu_id) values + + (#{item.roleId},#{item.menuId}) + + + + \ No newline at end of file diff --git a/boyue-system/src/main/resources/mapper/system/SysUserMapper.xml b/boyue-system/src/main/resources/mapper/system/SysUserMapper.xml new file mode 100644 index 0000000..8e618fa --- /dev/null +++ b/boyue-system/src/main/resources/mapper/system/SysUserMapper.xml @@ -0,0 +1,231 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, + d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status, + r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status + from sys_user u + left join sys_dept d on u.dept_id = d.dept_id + left join sys_user_role ur on u.user_id = ur.user_id + left join sys_role r on r.role_id = ur.role_id + + + + + + + + + + + + + + + + + + + + + + + + insert into sys_user( + user_id, + dept_id, + user_name, + nick_name, + email, + avatar, + phonenumber, + sex, + password, + status, + create_by, + remark, + create_time + )values( + #{userId}, + #{deptId}, + #{userName}, + #{nickName}, + #{email}, + #{avatar}, + #{phonenumber}, + #{sex}, + #{password}, + #{status}, + #{createBy}, + #{remark}, + sysdate() + ) + + + + update sys_user + + dept_id = #{deptId}, + user_name = #{userName}, + nick_name = #{nickName}, + email = #{email}, + phonenumber = #{phonenumber}, + sex = #{sex}, + avatar = #{avatar}, + password = #{password}, + status = #{status}, + login_ip = #{loginIp}, + login_date = #{loginDate}, + update_by = #{updateBy}, + remark = #{remark}, + update_time = sysdate() + + where user_id = #{userId} + + + + update sys_user set status = #{status} where user_id = #{userId} + + + + update sys_user set avatar = #{avatar} where user_name = #{userName} + + + + update sys_user set password = #{password} where user_name = #{userName} + + + + update sys_user set del_flag = '2' where user_id = #{userId} + + + + update sys_user set del_flag = '2' where user_id in + + #{userId} + + + + \ No newline at end of file diff --git a/boyue-system/src/main/resources/mapper/system/SysUserPostMapper.xml b/boyue-system/src/main/resources/mapper/system/SysUserPostMapper.xml new file mode 100644 index 0000000..0578872 --- /dev/null +++ b/boyue-system/src/main/resources/mapper/system/SysUserPostMapper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + delete from sys_user_post where user_id=#{userId} + + + + + + delete from sys_user_post where user_id in + + #{userId} + + + + + insert into sys_user_post(user_id, post_id) values + + (#{item.userId},#{item.postId}) + + + + \ No newline at end of file diff --git a/boyue-system/src/main/resources/mapper/system/SysUserRoleMapper.xml b/boyue-system/src/main/resources/mapper/system/SysUserRoleMapper.xml new file mode 100644 index 0000000..d5e62b0 --- /dev/null +++ b/boyue-system/src/main/resources/mapper/system/SysUserRoleMapper.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + delete from sys_user_role where user_id=#{userId} + + + + + + delete from sys_user_role where user_id in + + #{userId} + + + + + insert into sys_user_role(user_id, role_id) values + + (#{item.userId},#{item.roleId}) + + + + + delete from sys_user_role where user_id=#{userId} and role_id=#{roleId} + + + + delete from sys_user_role where role_id=#{roleId} and user_id in + + #{userId} + + + \ No newline at end of file diff --git a/doc/image/code-edit.png b/doc/image/code-edit.png new file mode 100644 index 0000000000000000000000000000000000000000..5fc972bb8efea327416eead31b463e99b540670e GIT binary patch literal 180320 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 D<***P literal 0 HcmV?d00001 diff --git a/doc/image/code-show.png b/doc/image/code-show.png new file mode 100644 index 0000000000000000000000000000000000000000..24378e8ef0c97f396bf52716fef2fdd11f5be9a5 GIT binary patch literal 170060 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 z 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 P;GeFR!QniOQ=$I@U<(`t literal 0 HcmV?d00001 diff --git a/doc/image/logo.png b/doc/image/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..dab35ca83c55015dbdb3d29a4df296c3c2ffb5f3 GIT binary patch literal 4879 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 UMm%;fgNgx+^iB235l&J60dJiM9{>OV literal 0 HcmV?d00001 diff --git a/doc/image/online-mb-code.png b/doc/image/online-mb-code.png new file mode 100644 index 0000000000000000000000000000000000000000..45a4973f560a076ab8335fd7b30dcb28b54fdbf8 GIT binary patch literal 149041 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
  • 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 XUiQ)Q_BiPr;1e2z3B0g9?)!fLIg5PB literal 0 HcmV?d00001 diff --git a/doc/image/online-mb-edit.png b/doc/image/online-mb-edit.png new file mode 100644 index 0000000000000000000000000000000000000000..6f3b3782323c451c31fcc7ba526f0363d6302763 GIT binary patch literal 185794 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

      (0AG)e2bfuEXY==&8IK#ps~XIpfWui1MN>~ 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 literal 0 HcmV?d00001 diff --git a/doc/image/online-mb-list.png b/doc/image/online-mb-list.png new file mode 100644 index 0000000000000000000000000000000000000000..0e1c03201a15e9f1cbfb243cbd5db8a6fd1cb196 GIT binary patch literal 149588 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�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 wL{{IQH4VdB1-H_&z_>5}0n@R#@c;k- literal 0 HcmV?d00001 diff --git a/doc/代码生成.drawio b/doc/代码生成.drawio new file mode 100644 index 0000000..c892fbe --- /dev/null +++ b/doc/代码生成.drawio @@ -0,0 +1,311 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/关于多数据源与分布式事务.drawio b/doc/关于多数据源与分布式事务.drawio new file mode 100644 index 0000000..54ac704 --- /dev/null +++ b/doc/关于多数据源与分布式事务.drawio @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/微信登录.drawio b/doc/微信登录.drawio new file mode 100644 index 0000000..509b195 --- /dev/null +++ b/doc/微信登录.drawio @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/支付流程图.drawio b/doc/支付流程图.drawio new file mode 100644 index 0000000..184cb9f --- /dev/null +++ b/doc/支付流程图.drawio @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/支付系统订单状态图.drawio b/doc/支付系统订单状态图.drawio new file mode 100644 index 0000000..ce41fa8 --- /dev/null +++ b/doc/支付系统订单状态图.drawio @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/权限控制.md b/doc/权限控制.md new file mode 100644 index 0000000..cb0b187 --- /dev/null +++ b/doc/权限控制.md @@ -0,0 +1,68 @@ +### 与权限有关的注解 + +`@Anonymous`注解用于配置公开接口 + +`@PreAuthorize`注解用于配置接口要求用户拥有某些权限才可访问,它拥有如下方法 + +| 方法 | 参数 | 描述 | +| ----------- | ------ | ---------------------------------------------- | +| hasPermi | String | 验证用户是否具备某权限 | +| lacksPermi | String | 验证用户是否不具备某权限,与 hasPermi逻辑相反 | +| hasAnyPermi | String | 验证用户是否具有以下任意一个权限 | +| hasRole | String | 判断用户是否拥有某个角色 | +| lacksRole | String | 验证用户是否不具备某角色,与 isRole逻辑相反 | +| hasAnyRoles | String | 验证用户是否具有以下任意一个角色,多个逗号分隔 | + +```java +@PreAuthorize("@ss.hasPermi('system:user:list')") +@PreAuthorize("@ss.lacksPermi('system:user:list')") +@PreAuthorize("@ss.hasAnyPermi('system:user:add,system:user:edit')") +``` + +`@DataScope`注解用于配置接口数据权限 + +* `deptAlias`用于指定部门表的别名; +* `userAlias`用于指定用户表的别名; +* 实体需要继承BaseEntity类; +* `全部数据权限`、`自定数据权限`、`部门数据权限`、`部门及以下数据权限`、`仅本人数据权限`五种权限模式在后台角色管理界面配置数据权限 + +```java +// 部门数据权限注解 +@DataScope(deptAlias = "d") +// 部门及用户权限注解 +@DataScope(deptAlias = "d", userAlias = "u") +``` + +1. 使用注解 + +```java + +@DataScope(deptAlias = "d", userAlias = "u") +public List<...> select(...) +{ + return mapper.select(...); +} +``` + +2. 配置mybatis的xml + +```xml + +``` + +### 安全工具类 + +utils.com.boyue.common.SecurityUtils + +| 方法 | 参数 | 返回 | 描述 | +| ------------ | ------ | ---------- | ------------------------ | +| getUserId | 无 | Long | 获取当前用户ID | +| getDeptId | 无 | Long | 获取当前部门ID | +| getUsername | 无 | String | 获取当前用户账户 | +| getLoginUser | 无 | LonginUser | 获取当前登录用户代理 | +| hasPermi | String | boolean | 验证用户是否具备某权限 | +| hasRole | String | boolean | 验证用户是否拥有某个角色 | diff --git a/doc/模块依赖关系.drawio b/doc/模块依赖关系.drawio new file mode 100644 index 0000000..ee8771f --- /dev/null +++ b/doc/模块依赖关系.drawio @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/登录+JWT逻辑.drawio b/doc/登录+JWT逻辑.drawio new file mode 100644 index 0000000..9d05d20 --- /dev/null +++ b/doc/登录+JWT逻辑.drawio @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/若依环境使用手册.docx b/doc/若依环境使用手册.docx new file mode 100644 index 0000000000000000000000000000000000000000..9e4daef4d9be2e445419109a02eaf321cd4d537e GIT binary patch literal 428430 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

      K#u)l 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

      )q;tSdIrAwYxlSM|Hl};Y` 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 literal 0 HcmV?d00001 diff --git a/doc/邮箱或短信验证码登录注册重置流程图.drawio b/doc/邮箱或短信验证码登录注册重置流程图.drawio new file mode 100644 index 0000000..7e623e9 --- /dev/null +++ b/doc/邮箱或短信验证码登录注册重置流程图.drawio @@ -0,0 +1,260 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/限流逻辑.drawio b/doc/限流逻辑.drawio new file mode 100644 index 0000000..7ad47d0 --- /dev/null +++ b/doc/限流逻辑.drawio @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..8b48ad0 --- /dev/null +++ b/pom.xml @@ -0,0 +1,368 @@ + + + 4.0.0 + + com.boyue + boyuehasfj-java + 3.8.9-G + + boyue + 管理系统 + + + 3.8.9-G + UTF-8 + UTF-8 + 21 + 3.1.1 + 3.5.0 + 1.2.24 + 1.1 + 1.21 + 3.0.0 + 2.3.3 + 2.1.0 + 2.0.53 + 2.18.0 + 6.6.6 + 2.19.0 + 4.4 + 5.4.1 + 4.5.14 + 2.4.1 + 0.12.6 + 4.5.0 + 3.0.4 + 3.5.16 + + 8.3.0 + 42.7.4 + 4.0.2 + 4.0.5 + 2.8.4 + + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + com.mysql + mysql-connector-j + ${mysql.version} + runtime + + + + + org.postgresql + postgresql + ${postgresql.version} + runtime + + + + + com.alibaba + druid-spring-boot-3-starter + ${druid.version} + + + + javax.transaction + jta + ${jta.version} + + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + ${mybatis-spring-boot.version} + + + + + com.github.pagehelper + pagehelper-spring-boot-starter + ${pagehelper.boot.version} + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + + + + + + + com.github.oshi + oshi-core + ${oshi.version} + + + + + commons-io + commons-io + ${commons.io.version} + + + + + org.apache.poi + poi-ooxml + ${poi.version} + + + + + org.apache.velocity + velocity-engine-core + ${velocity.version} + + + + + org.apache.httpcomponents + httpclient + ${httpclient.version} + + + commons-logging + commons-logging + + + + + + + org.apache.commons + commons-collections4 + ${commons.collections.version} + + + + + com.alibaba.fastjson2 + fastjson2 + ${fastjson.version} + + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson.version} + + + + + io.jsonwebtoken + jjwt-api + ${jwt.version} + + + + io.jsonwebtoken + jjwt-impl + ${jwt.version} + + + + io.jsonwebtoken + jjwt-jackson + ${jwt.version} + + + + + jakarta.xml.bind + jakarta.xml.bind-api + ${jaxb-api.version} + + + com.sun.xml.bind + jaxb-impl + ${jaxb.version} + + + com.sun.xml.bind + jaxb-core + ${jaxb.version} + + + + + eu.bitwalker + UserAgentUtils + ${bitwalker.version} + + + + + pro.fessional + kaptcha + ${kaptcha.version} + + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + ${springdoc.version} + + + + + com.github.xiaoymin + knife4j-openapi3-jakarta-spring-boot-starter + ${knife4j.version} + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + + + + + + + com.boyue + boyue-framework + ${boyue.version} + + + + + com.boyue + boyue-system + ${boyue.version} + + + + + com.boyue + boyue-common + ${boyue.version} + + + + + com.boyue + boyue-auth-starter + ${boyue.version} + + + + + com.boyue + boyue-pay-starter + ${boyue.version} + + + + + com.boyue + boyue-plugins-starter + ${boyue.version} + + + + + com.boyue + boyue-file-starter + ${boyue.version} + + + + + com.boyue + boyue-middleware-starter + ${boyue.version} + + + + + com.boyue + boyue-models-starter + ${boyue.version} + + + + + + boyue-admin + boyue-framework + boyue-system + boyue-common + boyue-auth + boyue-pay + boyue-file + boyue-middleware + boyue-plugins + boyue-models + + pom + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + true + ${java.version} + ${java.version} + ${project.build.sourceEncoding} + + + + + + + + + public + aliyun nexus + https://maven.aliyun.com/repository/public + + true + + + + + + + + public + aliyun nexus + https://maven.aliyun.com/repository/public + + true + + + false + + + + + \ No newline at end of file diff --git a/sql/boyuehasfj.sql b/sql/boyuehasfj.sql new file mode 100644 index 0000000..d05606b --- /dev/null +++ b/sql/boyuehasfj.sql @@ -0,0 +1,2362 @@ +/* + Navicat Premium Dump SQL + + Source Server : 222.184.49.22测试 + Source Server Type : MySQL + Source Server Version : 80405 (8.4.5) + Source Host : 222.184.49.22:3307 + Source Schema : boyuehasfj + + Target Server Type : MySQL + Target Server Version : 80405 (8.4.5) + File Encoding : 65001 + + Date: 02/06/2025 21:22:28 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for ACT_APP_APPDEF +-- ---------------------------- +DROP TABLE IF EXISTS `ACT_APP_APPDEF`; +CREATE TABLE `ACT_APP_APPDEF` ( + `ID_` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `REV_` int NOT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `KEY_` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `VERSION_` int NOT NULL, + `CATEGORY_` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `DEPLOYMENT_ID_` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `RESOURCE_NAME_` varchar(4000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `DESCRIPTION_` varchar(4000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '', + PRIMARY KEY (`ID_`) USING BTREE, + UNIQUE INDEX `ACT_IDX_APP_DEF_UNIQ`(`KEY_` ASC, `VERSION_` ASC, `TENANT_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_APP_DEF_DPLY`(`DEPLOYMENT_ID_` ASC) USING BTREE, + CONSTRAINT `ACT_FK_APP_DEF_DPLY` FOREIGN KEY (`DEPLOYMENT_ID_`) REFERENCES `ACT_APP_DEPLOYMENT` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of ACT_APP_APPDEF +-- ---------------------------- + +-- ---------------------------- +-- Table structure for ACT_APP_DEPLOYMENT +-- ---------------------------- +DROP TABLE IF EXISTS `ACT_APP_DEPLOYMENT`; +CREATE TABLE `ACT_APP_DEPLOYMENT` ( + `ID_` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `CATEGORY_` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `KEY_` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `DEPLOY_TIME_` datetime(3) NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '', + PRIMARY KEY (`ID_`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of ACT_APP_DEPLOYMENT +-- ---------------------------- + +-- ---------------------------- +-- Table structure for ACT_APP_DEPLOYMENT_RESOURCE +-- ---------------------------- +DROP TABLE IF EXISTS `ACT_APP_DEPLOYMENT_RESOURCE`; +CREATE TABLE `ACT_APP_DEPLOYMENT_RESOURCE` ( + `ID_` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `DEPLOYMENT_ID_` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `RESOURCE_BYTES_` longblob NULL, + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_APP_RSRC_DPL`(`DEPLOYMENT_ID_` ASC) USING BTREE, + CONSTRAINT `ACT_FK_APP_RSRC_DPL` FOREIGN KEY (`DEPLOYMENT_ID_`) REFERENCES `ACT_APP_DEPLOYMENT` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of ACT_APP_DEPLOYMENT_RESOURCE +-- ---------------------------- + +-- ---------------------------- +-- Table structure for ACT_EVT_LOG +-- ---------------------------- +DROP TABLE IF EXISTS `ACT_EVT_LOG`; +CREATE TABLE `ACT_EVT_LOG` ( + `LOG_NR_` bigint NOT NULL AUTO_INCREMENT, + `TYPE_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROC_DEF_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROC_INST_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `EXECUTION_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TASK_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TIME_STAMP_` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `USER_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DATA_` longblob NULL, + `LOCK_OWNER_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `LOCK_TIME_` timestamp(3) NULL DEFAULT NULL, + `IS_PROCESSED_` tinyint NULL DEFAULT 0, + PRIMARY KEY (`LOG_NR_`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of ACT_EVT_LOG +-- ---------------------------- + +-- ---------------------------- +-- Table structure for ACT_PROCDEF_INFO +-- ---------------------------- +DROP TABLE IF EXISTS `ACT_PROCDEF_INFO`; +CREATE TABLE `ACT_PROCDEF_INFO` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `PROC_DEF_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT NULL, + `INFO_JSON_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE, + UNIQUE INDEX `ACT_UNIQ_INFO_PROCDEF`(`PROC_DEF_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_INFO_PROCDEF`(`PROC_DEF_ID_` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of ACT_PROCDEF_INFO +-- ---------------------------- + +-- ---------------------------- +-- Table structure for ACT_RE_DEPLOYMENT +-- ---------------------------- +DROP TABLE IF EXISTS `ACT_RE_DEPLOYMENT`; +CREATE TABLE `ACT_RE_DEPLOYMENT` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CATEGORY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `KEY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT '', + `DEPLOY_TIME_` timestamp(3) NULL DEFAULT NULL, + `DERIVED_FROM_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DERIVED_FROM_ROOT_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PARENT_DEPLOYMENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ENGINE_VERSION_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of ACT_RE_DEPLOYMENT +-- ---------------------------- + +-- ---------------------------- +-- Table structure for ACT_RE_MODEL +-- ---------------------------- +DROP TABLE IF EXISTS `ACT_RE_MODEL`; +CREATE TABLE `ACT_RE_MODEL` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `KEY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CATEGORY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CREATE_TIME_` timestamp(3) NULL DEFAULT NULL, + `LAST_UPDATE_TIME_` timestamp(3) NULL DEFAULT NULL, + `VERSION_` int NULL DEFAULT NULL, + `META_INFO_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DEPLOYMENT_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `EDITOR_SOURCE_VALUE_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `EDITOR_SOURCE_EXTRA_VALUE_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT '', + PRIMARY KEY (`ID_`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of ACT_RE_MODEL +-- ---------------------------- + +-- ---------------------------- +-- Table structure for ACT_RE_PROCDEF +-- ---------------------------- +DROP TABLE IF EXISTS `ACT_RE_PROCDEF`; +CREATE TABLE `ACT_RE_PROCDEF` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT NULL, + `CATEGORY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `KEY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `VERSION_` int NOT NULL, + `DEPLOYMENT_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `RESOURCE_NAME_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DGRM_RESOURCE_NAME_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DESCRIPTION_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `HAS_START_FORM_KEY_` tinyint NULL DEFAULT NULL, + `HAS_GRAPHICAL_NOTATION_` tinyint NULL DEFAULT NULL, + `SUSPENSION_STATE_` int NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT '', + `ENGINE_VERSION_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DERIVED_FROM_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DERIVED_FROM_ROOT_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DERIVED_VERSION_` int NOT NULL DEFAULT 0, + PRIMARY KEY (`ID_`) USING BTREE, + UNIQUE INDEX `ACT_UNIQ_PROCDEF`(`KEY_` ASC, `VERSION_` ASC, `DERIVED_VERSION_` ASC, `TENANT_ID_` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of ACT_RE_PROCDEF +-- ---------------------------- + +-- ---------------------------- +-- Table structure for ACT_RU_ACTINST +-- ---------------------------- +DROP TABLE IF EXISTS `ACT_RU_ACTINST`; +CREATE TABLE `ACT_RU_ACTINST` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT 1, + `PROC_DEF_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `PROC_INST_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `EXECUTION_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `ACT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `TASK_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CALL_PROC_INST_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ACT_NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ACT_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `ASSIGNEE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `START_TIME_` datetime(3) NOT NULL, + `END_TIME_` datetime(3) NULL DEFAULT NULL, + `DURATION_` bigint NULL DEFAULT NULL, + `TRANSACTION_ORDER_` int NULL DEFAULT NULL, + `DELETE_REASON_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT '', + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_RU_ACTI_START`(`START_TIME_` ASC) USING BTREE, + INDEX `ACT_IDX_RU_ACTI_END`(`END_TIME_` ASC) USING BTREE, + INDEX `ACT_IDX_RU_ACTI_PROC`(`PROC_INST_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_RU_ACTI_PROC_ACT`(`PROC_INST_ID_` ASC, `ACT_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_RU_ACTI_EXEC`(`EXECUTION_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_RU_ACTI_EXEC_ACT`(`EXECUTION_ID_` ASC, `ACT_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_RU_ACTI_TASK`(`TASK_ID_` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of ACT_RU_ACTINST +-- ---------------------------- + +-- ---------------------------- +-- Table structure for ACT_RU_EXECUTION +-- ---------------------------- +DROP TABLE IF EXISTS `ACT_RU_EXECUTION`; +CREATE TABLE `ACT_RU_EXECUTION` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT NULL, + `PROC_INST_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `BUSINESS_KEY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PARENT_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROC_DEF_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SUPER_EXEC_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ROOT_PROC_INST_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ACT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `IS_ACTIVE_` tinyint NULL DEFAULT NULL, + `IS_CONCURRENT_` tinyint NULL DEFAULT NULL, + `IS_SCOPE_` tinyint NULL DEFAULT NULL, + `IS_EVENT_SCOPE_` tinyint NULL DEFAULT NULL, + `IS_MI_ROOT_` tinyint NULL DEFAULT NULL, + `SUSPENSION_STATE_` int NULL DEFAULT NULL, + `CACHED_ENT_STATE_` int NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT '', + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `START_ACT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `START_TIME_` datetime(3) NULL DEFAULT NULL, + `START_USER_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `LOCK_TIME_` timestamp(3) NULL DEFAULT NULL, + `LOCK_OWNER_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `IS_COUNT_ENABLED_` tinyint NULL DEFAULT NULL, + `EVT_SUBSCR_COUNT_` int NULL DEFAULT NULL, + `TASK_COUNT_` int NULL DEFAULT NULL, + `JOB_COUNT_` int NULL DEFAULT NULL, + `TIMER_JOB_COUNT_` int NULL DEFAULT NULL, + `SUSP_JOB_COUNT_` int NULL DEFAULT NULL, + `DEADLETTER_JOB_COUNT_` int NULL DEFAULT NULL, + `EXTERNAL_WORKER_JOB_COUNT_` int NULL DEFAULT NULL, + `VAR_COUNT_` int NULL DEFAULT NULL, + `ID_LINK_COUNT_` int NULL DEFAULT NULL, + `CALLBACK_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CALLBACK_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `REFERENCE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `REFERENCE_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROPAGATED_STAGE_INST_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `BUSINESS_STATUS_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_EXEC_BUSKEY`(`BUSINESS_KEY_` ASC) USING BTREE, + INDEX `ACT_IDC_EXEC_ROOT`(`ROOT_PROC_INST_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_EXEC_REF_ID_`(`REFERENCE_ID_` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of ACT_RU_EXECUTION +-- ---------------------------- + +-- ---------------------------- +-- Table structure for form_data +-- ---------------------------- +DROP TABLE IF EXISTS `form_data`; +CREATE TABLE `form_data` ( + `data_id` bigint NOT NULL AUTO_INCREMENT COMMENT '数据ID', + `form_id` bigint NOT NULL COMMENT '关联的表单ID', + `form_version` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '表单版本(与模板表版本一致)', + `data_content` json NOT NULL COMMENT '表单数据内容(JSON格式)', + `status` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'draft' COMMENT '数据状态(draft, submitted, approved, rejected)', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + PRIMARY KEY (`data_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '表单数据表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of form_data +-- ---------------------------- + +-- ---------------------------- +-- Table structure for form_template +-- ---------------------------- +DROP TABLE IF EXISTS `form_template`; +CREATE TABLE `form_template` ( + `form_id` bigint NOT NULL AUTO_INCREMENT COMMENT '表单ID', + `form_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '表单名称', + `form_schema` json NULL COMMENT '表单JSON Schema(vForm配置)', + `form_version` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '1.0.0' COMMENT '表单版本(语义化版本)', + `form_status` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '0' COMMENT '发布状态(0: 草稿, 1: 已发布, 2: 已停用)', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + PRIMARY KEY (`form_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '表单模板表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of form_template +-- ---------------------------- + +-- ---------------------------- +-- Table structure for gen_join_table +-- ---------------------------- +DROP TABLE IF EXISTS `gen_join_table`; +CREATE TABLE `gen_join_table` ( + `table_id` bigint NOT NULL COMMENT '表编号', + `left_table_id` bigint NOT NULL COMMENT '左表名称', + `right_table_id` bigint NOT NULL COMMENT '右表编号', + `left_table_alias` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '左表别名', + `right_table_alias` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '右表别名', + `left_table_fk` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '左表关联键', + `right_table_fk` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '右表关联键', + `join_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '关联类型', + `join_columns` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '关联字段', + `order_num` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '序号', + `new_table_id` bigint NOT NULL COMMENT '新表编号', + PRIMARY KEY (`table_id`, `right_table_id`, `left_table_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成关联表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of gen_join_table +-- ---------------------------- + +-- ---------------------------- +-- Table structure for gen_table +-- ---------------------------- +DROP TABLE IF EXISTS `gen_table`; +CREATE TABLE `gen_table` ( + `table_id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `table_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '表名称', + `table_alias` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '表别名', + `table_comment` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '表描述', + `have_sub_column` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '0' COMMENT '是否含有关联字段', + `sub_table_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '关联子表的表名', + `sub_table_fk_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '子表关联的外键名', + `class_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '实体类名称', + `tpl_category` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'crud' COMMENT '使用的模板(crud单表操作 tree树表操作)', + `tpl_web_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'element-plus' COMMENT '使用的模板类型', + `package_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '生成包路径', + `module_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '生成模块名', + `business_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '生成业务名', + `function_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '生成功能名', + `function_author` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '生成功能作者', + `gen_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '0' COMMENT '生成代码方式(0zip压缩包 1自定义路径)', + `gen_path` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '/' COMMENT '生成路径(不填默认项目路径)', + `options` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '其它生成选项', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`table_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成业务表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of gen_table +-- ---------------------------- +INSERT INTO `gen_table` VALUES (1, 'html_pages', 'hp', '司法局法律规定、典型案例、表单下载表', '0', NULL, NULL, 'HtmlPages', 'crud', 'element-plus', 'com.boyue.hasfj', 'hasfj', 'hasfjpages', '司法局法律规定、典型案例、单下载', 'boyue', '0', '/', '{\"parentMenuId\":2087}', 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02', NULL); + +-- ---------------------------- +-- Table structure for gen_table_column +-- ---------------------------- +DROP TABLE IF EXISTS `gen_table_column`; +CREATE TABLE `gen_table_column` ( + `column_id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `table_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '归属表编号', + `column_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '列名称', + `column_comment` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '列描述', + `column_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '列类型', + `java_type` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'JAVA类型', + `java_field` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'JAVA字段名', + `is_pk` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '是否主键(1是)', + `is_increment` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '是否自增(1是)', + `is_required` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '是否必填(1是)', + `is_insert` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '是否为插入字段(1是)', + `is_edit` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '是否编辑字段(1是)', + `is_list` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '是否列表字段(1是)', + `is_query` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '是否查询字段(1是)', + `query_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'EQ' COMMENT '查询方式(等于、不等于、大于、小于、范围)', + `html_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)', + `dict_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '字典类型', + `sort` int NULL DEFAULT NULL COMMENT '排序', + `sub_column_table_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '关联表名称', + `sub_column_fk_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '关联字段名称', + `sub_column_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '映射字段名称', + `sub_column_java_field` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '映射字段JAVA字段名', + `sub_column_java_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '映射字段JAVA类型', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`column_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 15 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成业务表字段' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of gen_table_column +-- ---------------------------- +INSERT INTO `gen_table_column` VALUES (1, '1', 'id', '序号', 'int(11)', 'Long', 'id', '1', '0', '0', '0', NULL, '1', NULL, 'EQ', 'input', '', 1, NULL, NULL, NULL, NULL, NULL, 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02'); +INSERT INTO `gen_table_column` VALUES (2, '1', 'format_id', '访问id', 'varchar(6)', 'String', 'formatId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 2, NULL, NULL, NULL, NULL, NULL, 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02'); +INSERT INTO `gen_table_column` VALUES (3, '1', 'title', '页面标题', 'varchar(200)', 'String', 'title', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 3, NULL, NULL, NULL, NULL, NULL, 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02'); +INSERT INTO `gen_table_column` VALUES (4, '1', 'content', 'HTML内容', 'text', 'String', 'content', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'editor', '', 4, NULL, NULL, NULL, NULL, NULL, 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02'); +INSERT INTO `gen_table_column` VALUES (5, '1', 'page_type', '页面类型:法律规定 law、典型案例 case、表单下载from', 'enum(\'law\',\'case\',\'form\')', 'String', 'pageType', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'select', '', 5, NULL, NULL, NULL, NULL, NULL, 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02'); +INSERT INTO `gen_table_column` VALUES (6, '1', 'page_url', '页面访问URL', 'varchar(255)', 'String', 'pageUrl', '0', '0', '1', '1', '1', '1', '0', 'EQ', 'input', '', 6, NULL, NULL, NULL, NULL, NULL, 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02'); +INSERT INTO `gen_table_column` VALUES (7, '1', 'create_time', '创建时间', 'datetime', 'LocalDate', 'createTime', '0', '0', '1', '1', NULL, '1', NULL, 'EQ', 'datetime', '', 7, NULL, NULL, NULL, NULL, NULL, 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02'); +INSERT INTO `gen_table_column` VALUES (8, '1', 'update_time', '更新时间', 'datetime', 'LocalDate', 'updateTime', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'datetime', '', 8, NULL, NULL, NULL, NULL, NULL, 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02'); +INSERT INTO `gen_table_column` VALUES (9, '1', 'status', '状态:1-启用,0-禁用', 'tinyint(4)', 'Integer', 'status', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'radio', '', 9, NULL, NULL, NULL, NULL, NULL, 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02'); +INSERT INTO `gen_table_column` VALUES (10, '1', 'sort_order', '排序序号,数值越小排序越靠前,用于自定义显示顺序', 'int(11)', 'Long', 'sortOrder', '0', '0', '1', '1', '1', '1', '0', 'EQ', 'input', '', 10, NULL, NULL, NULL, NULL, NULL, 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02'); +INSERT INTO `gen_table_column` VALUES (11, '1', 'view_count', '浏览次数,记录页面被访问的总次数', 'int(11)', 'Long', 'viewCount', '0', '0', '1', '1', '1', '1', '0', 'EQ', 'input', '', 11, NULL, NULL, NULL, NULL, NULL, 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02'); +INSERT INTO `gen_table_column` VALUES (12, '1', 'author', '作者/负责人,记录页面的创建者或负责维护的人员', 'varchar(50)', 'String', 'author', '0', '0', '0', '1', '1', '1', '0', 'EQ', 'input', '', 12, NULL, NULL, NULL, NULL, NULL, 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02'); +INSERT INTO `gen_table_column` VALUES (13, '1', 'keywords', 'SEO关键词,用于搜索引擎优化', 'varchar(200)', 'String', 'keywords', '0', '0', '0', '1', '1', '1', '0', 'EQ', 'input', '', 13, NULL, NULL, NULL, NULL, NULL, 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02'); +INSERT INTO `gen_table_column` VALUES (14, '1', 'description', 'SEO描述,用于搜索引擎结果展示的页面摘要', 'varchar(500)', 'String', 'description', '0', '0', '0', '1', '1', '1', '0', 'EQ', 'textarea', '', 14, NULL, NULL, NULL, NULL, NULL, 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02'); + +-- ---------------------------- +-- Table structure for html_pages +-- ---------------------------- +DROP TABLE IF EXISTS `html_pages`; +CREATE TABLE `html_pages` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `format_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `page_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `page_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL, + `attachment_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, + `author` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, + `status` int NULL DEFAULT 1, + `sort_order` bigint NULL DEFAULT 100, + `view_count` bigint NULL DEFAULT 0, + `keywords` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, + `description` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + `multi_attachments` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '多附件URLs,JSON格式存储多个附件URL及名称', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_html_pages_format_id`(`format_id` ASC) USING BTREE, + INDEX `idx_html_pages_page_type`(`page_type` ASC) USING BTREE, + INDEX `idx_html_pages_status`(`status` ASC) USING BTREE, + INDEX `idx_html_pages_sort_order`(`sort_order` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 451 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '页面内容' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of html_pages +-- ---------------------------- +INSERT INTO `html_pages` VALUES (1, '000001', 'law', '/show.html?Id=000001', '如何确定企业形式', '

      《公司法》《个人独资企业法》《合伙企业法》

      ', '', '法律法规编辑部', 1, 1, 223, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-06-02 21:21:10', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (2, '000001', 'case', '/showcase.html?Id=000001', '如何确定企业形式', '

      李某与王某成立“普通合伙企业”经营餐饮,后因亏损负债100万元,法院判令李某、王某承担无限连带责任。

      ', NULL, '案例分析组', 1, 2, 4, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-29 15:08:06', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (3, '000001', 'form', '/table.html?Id=000001', '如何确定企业形式', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', '', '表单管理员', 1, 3, 11, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-06-01 11:03:49', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (4, '000002', 'law', '/show.html?Id=000002', '有限责任公司和股份有限公司的区别是什么?', '

      《证券法》第七十八条,发行人及法律、行政法规和国务院证券监督管理机构规定的其他信息披露义务人,应当及时依法履行信息披露义务。信息披露义务人披露的信息,应当真实、准确、完整,简明清晰,通俗易懂,不得有虚假记载、误导性陈述或者重大遗漏。

      ', NULL, '法律法规编辑部', 1, 4, 17, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-29 15:11:13', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (5, '000002', 'case', '/showcase.html?Id=000002', '有限责任公司和股份有限公司的区别是什么?', '

      某科技创业团队初期仅3名股东,误以为股份公司“更规范易融资”,直接注册为股份公司。因股份公司需设股东大会、董事会、监事会,团队人数不足,决策效率低下;同时需定期披露年报、重大事项,合规成本高。天使投资机构因其结构复杂、股权转让受限(需股东大会批准)放弃投资,最终公司因资金链断裂解散清算。

      ', NULL, '案例分析组', 1, 5, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-29 15:11:33', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (6, '000002', 'form', '/table.html?Id=000002', '有限责任公司和股份有限公司的区别是什么?', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 6, 1, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-29 15:09:11', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (7, '000003', 'law', '/show.html?Id=000003', '如何确定企业名称?', '

      《企业名称登记管理规定》《企业名称登记管理规定实施办法》。

      ', NULL, '法律法规编辑部', 1, 7, 15, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-29 15:11:54', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (8, '000003', 'case', '/showcase.html?Id=000003', '如何确定企业名称?', '

      甲公司注册名称为“上海特斯拉新能源科技有限公司”,未经特斯拉公司授权。法院认定其名称攀附知名品牌商誉,构成不正当竞争,判令更名并赔偿20万元。

      ', NULL, '案例分析组', 1, 8, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-29 15:12:12', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (9, '000003', 'form', '/table.html?Id=000003', '如何确定企业名称?', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 9, 2, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-29 15:09:50', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (10, '000004', 'law', '/show.html?Id=000004', '法定代表人的要求有哪些?', '

      《公司法》第十条,公司的法定代表人按照公司章程的规定,由代表公司执行公司事务的董事或者经理担任。担任法定代表人的董事或者经理辞任的,视为同时辞去法定代表人。法定代表人辞任的,公司应当在法定代表人辞任之日起三十日内确定新的法定代表人。第十一条:法定代表人以公司名义从事的民事活动,其法律后果由公司承受。公司章程或者股东会对法定代表人职权的限制,不得对抗善意相对人。法定代表人因执行职务造成他人损害的,由公司承担民事责任。公司承担民事责任后,依照法律或者公司章程的规定,可以向有过错的法定代表人追偿。

      ', NULL, '法律法规编辑部', 1, 10, 7, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-29 15:12:43', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (11, '000004', 'case', '/showcase.html?Id=000004', '法定代表人的要求有哪些?', '

      某公司法定代表人李某因个人债务被列为失信被执行人,法院限制其高消费。公司向银行申请贷款时,因李某信用瑕疵遭拒,资金链断裂导致停产。市场监管部门责令更换法定代表人,但因股东争议拖延,最终公司破产清算。

      ', NULL, '案例分析组', 1, 11, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-29 15:13:17', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (12, '000004', 'form', '/table.html?Id=000004', '法定代表人的要求有哪些?', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 12, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-29 15:13:35', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (13, '000005', 'law', '/show.html?Id=000005', '董事、监事、高级管理人员的资格限制有哪些?', '

      《公司法》第一百七十八条,有下列情形之一的,不得担任公司的董事、监事、高级管理人员:(一)无民事行为能力或者限制民事行为能力;(二)因贪污、贿赂、侵占财产、挪用财产或者破坏社会主义市场经济秩序,被判处刑罚,或者因犯罪被剥夺政治权利,执行期满未逾五年,被宣告缓刑的,自缓刑考验期满之日起未逾二年;(三)担任破产清算的公司、企业的董事或者厂长、经理,对该公司、企业的破产负有个人责任的,自该公司、企业破产清算完结之日起未逾三年;(四)担任因违法被吊销营业执照、责令关闭的公司、企业的法定代表人,并负有个人责任的,自该公司、企业被吊销营业执照、责令关闭之日起未逾三年;(五)个人因所负数额较大债务到期未清偿被人民法院列为失信被执行人。

      ', NULL, '法律法规编辑部', 1, 13, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-29 15:14:05', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (14, '000005', 'case', '/showcase.html?Id=000005', '董事、监事、高级管理人员的资格限制有哪些?', '

      某公司股东李某(公务员)通过亲属代持股权,后因公司债务纠纷被债权人揭发。法院认定代持协议无效,李某需退还全部股权收益,公司被处罚款10万元。

      ', NULL, '案例分析组', 1, 14, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-29 15:14:42', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (15, '000005', 'form', '/table.html?Id=000005', '董事、监事、高级管理人员的资格限制有哪些?', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 15, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-29 15:14:52', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (16, '000006', 'law', '/show.html?Id=000006', '隐名股东有哪些常见法律风险?', '

      最高人民法院关于适用《中华人民共和国公司法》若干问题的规定(三)第二十四条,有限责任公司的实际出资人与名义出资人订立合同,约定由实际出资人出资并享有投资权益,以名义出资人为名义股东,实际出资人与名义股东对该合同效力发生争议的,如无法律规定的无效情形,人民法院应当认定该合同有效。前款规定的实际出资人与名义股东因投资权益的归属发生争议,实际出资人以其实际履行了出资义务为由向名义股东主张权利的,人民法院应予支持。名义股东以公司股东名册记载、公司登记机关登记为由否认实际出资人权利的,人民法院不予支持。实际出资人未经公司其他股东半数以上同意,请求公司变更股东、签发出资证明书、记载于股东名册、记载于公司章程并办理公司登记机关登记的,人民法院不予支持。

      ', NULL, '法律法规编辑部', 1, 16, 1, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-29 15:15:42', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (17, '000006', 'case', '/showcase.html?Id=000006', '隐名股东有哪些常见法律风险?', '

      隐名股东李某委托张某代持某公司10%股权,张某未经同意将股权转让给王某,王某不知情且支付合理价款。王某善意取得股权,李某仅能要求张某赔偿损失。

      ', NULL, '案例分析组', 1, 17, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-29 15:16:04', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (18, '000006', 'form', '/table.html?Id=000006', '隐名股东有哪些常见法律风险?', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 18, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-29 15:16:19', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (19, '000007', 'law', '/show.html?Id=000007', '如何保持创始人在公司的控制权?', '

      《公司法》第六十五条,股东会会议由股东按照出资比例行使表决权;但是,公司章程另有规定的除外。

      ', NULL, '法律法规编辑部', 1, 19, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-29 15:16:52', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (20, '000007', 'case', '/showcase.html?Id=000007', '如何保持创始人在公司的控制权?', '

      某公司创始人通过“AB股”架构(1股10票投票权),即使持股比例仅15%,仍掌控董事会80%席位。公司上市后,其投票权超80%,确保战略决策权。

      ', NULL, '案例分析组', 1, 20, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-29 15:17:15', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (21, '000007', 'form', '/table.html?Id=000007', '如何保持创始人在公司的控制权?', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 21, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-29 15:17:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (22, '000008', 'law', '/show.html?Id=000008', '如何防止出现公司僵局?', '

      《公司法》第二百三十一条,公司经营管理发生严重困难,继续存续会使股东利益受到重大损失,通过其他途径不能解决的,持有公司百分之十以上表决权的股东,可以请求人民法院解散公司。

      最高人民法院关于适用《中华人民共和国公司法》若干问题的规定(二)第一条,单独或者合计持有公司全部股东表决权百分之十以上的股东,以下列事由之一提起解散公司诉讼,并符合公司法第一百八十二条规定的,人民法院应予受理:(一)公司持续两年以上无法召开股东会或者股东大会,公司经营管理发生严重困难的;(二)股东表决时无法达到法定或者公司章程规定的比例,持续两年以上不能做出有效的股东会或者股东大会决议,公司经营管理发生严重困难的;(三)公司董事长期冲突,且无法通过股东会或者股东大会解决,公司经营管理发生严重困难的;


      ', NULL, '法律法规编辑部', 1, 22, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-29 15:18:00', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (23, '000008', 'case', '/showcase.html?Id=000008', '如何防止出现公司僵局?', '

      某零售企业两位股东各持50%股权,因经营理念严重冲突,股东会无法通过重大决策(如拓展新店、引入投资),公司现金流枯竭,员工离职,被法院裁定解散。

      ', NULL, '案例分析组', 1, 23, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-29 15:18:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (24, '000008', 'form', '/table.html?Id=000008', '如何防止出现公司僵局?', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 24, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-29 15:18:40', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (25, '000009', 'law', '/show.html?Id=000009', '如何合理确定注册资本?', '

      《公司法》第四条,有限责任公司的股东以其认缴的出资额为限对公司承担责任;股份有限公司的股东以其认购的股份为限对公司承担责任。

      ', NULL, '法律法规编辑部', 1, 25, 1, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-29 15:19:12', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (26, '000009', 'case', '/showcase.html?Id=000009', '如何合理确定注册资本?', '

      某公司注册资本8000万元(认缴期30年),实际运营资金仅200万元。后因合同纠纷负债500万元,法院认定股东恶意设置超长认缴期,判令股东立即补缴。


      ', NULL, '案例分析组', 1, 26, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-29 15:19:32', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (27, '000009', 'form', '/table.html?Id=000009', '如何合理确定注册资本?', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 27, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-29 15:19:44', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (28, '000010', 'law', '/show.html?Id=000010', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 28, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (29, '000010', 'case', '/showcase.html?Id=000010', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 29, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (30, '000010', 'form', '/table.html?Id=000010', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 30, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (31, '000011', 'law', '/show.html?Id=000011', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 31, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (32, '000011', 'case', '/showcase.html?Id=000011', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 32, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (33, '000011', 'form', '/table.html?Id=000011', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 33, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (34, '000012', 'law', '/show.html?Id=000012', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 34, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (35, '000012', 'case', '/showcase.html?Id=000012', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 35, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (36, '000012', 'form', '/table.html?Id=000012', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 36, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (37, '000013', 'law', '/show.html?Id=000013', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 37, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (38, '000013', 'case', '/showcase.html?Id=000013', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 38, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (39, '000013', 'form', '/table.html?Id=000013', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 39, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (40, '000014', 'law', '/show.html?Id=000014', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 40, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (41, '000014', 'case', '/showcase.html?Id=000014', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 41, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (42, '000014', 'form', '/table.html?Id=000014', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 42, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (43, '000015', 'law', '/show.html?Id=000015', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 43, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (44, '000015', 'case', '/showcase.html?Id=000015', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 44, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (45, '000015', 'form', '/table.html?Id=000015', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 45, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (46, '000016', 'law', '/show.html?Id=000016', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 46, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (47, '000016', 'case', '/showcase.html?Id=000016', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 47, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (48, '000016', 'form', '/table.html?Id=000016', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 48, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (49, '000017', 'law', '/show.html?Id=000017', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 49, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (50, '000017', 'case', '/showcase.html?Id=000017', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 50, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (51, '000017', 'form', '/table.html?Id=000017', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 51, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (52, '000018', 'law', '/show.html?Id=000018', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 52, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (53, '000018', 'case', '/showcase.html?Id=000018', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 53, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (54, '000018', 'form', '/table.html?Id=000018', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 54, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (55, '000019', 'law', '/show.html?Id=000019', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 55, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (56, '000019', 'case', '/showcase.html?Id=000019', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 56, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (57, '000019', 'form', '/table.html?Id=000019', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 57, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (58, '000020', 'law', '/show.html?Id=000020', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 58, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (59, '000020', 'case', '/showcase.html?Id=000020', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 59, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (60, '000020', 'form', '/table.html?Id=000020', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 60, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (61, '000021', 'law', '/show.html?Id=000021', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 61, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (62, '000021', 'case', '/showcase.html?Id=000021', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 62, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (63, '000021', 'form', '/table.html?Id=000021', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 63, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (64, '000022', 'law', '/show.html?Id=000022', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 64, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (65, '000022', 'case', '/showcase.html?Id=000022', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 65, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (66, '000022', 'form', '/table.html?Id=000022', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 66, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (67, '000023', 'law', '/show.html?Id=000023', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 67, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (68, '000023', 'case', '/showcase.html?Id=000023', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 68, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (69, '000023', 'form', '/table.html?Id=000023', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 69, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (70, '000024', 'law', '/show.html?Id=000024', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 70, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (71, '000024', 'case', '/showcase.html?Id=000024', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 71, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (72, '000024', 'form', '/table.html?Id=000024', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 72, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (73, '000025', 'law', '/show.html?Id=000025', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 73, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (74, '000025', 'case', '/showcase.html?Id=000025', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 74, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (75, '000025', 'form', '/table.html?Id=000025', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 75, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (76, '000026', 'law', '/show.html?Id=000026', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 76, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (77, '000026', 'case', '/showcase.html?Id=000026', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 77, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (78, '000026', 'form', '/table.html?Id=000026', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 78, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (79, '000027', 'law', '/show.html?Id=000027', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 79, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (80, '000027', 'case', '/showcase.html?Id=000027', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 80, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (81, '000027', 'form', '/table.html?Id=000027', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 81, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (82, '000028', 'law', '/show.html?Id=000028', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 82, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (83, '000028', 'case', '/showcase.html?Id=000028', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 83, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (84, '000028', 'form', '/table.html?Id=000028', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 84, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (85, '000029', 'law', '/show.html?Id=000029', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 85, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (86, '000029', 'case', '/showcase.html?Id=000029', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 86, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (87, '000029', 'form', '/table.html?Id=000029', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 87, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (88, '000030', 'law', '/show.html?Id=000030', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 88, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (89, '000030', 'case', '/showcase.html?Id=000030', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 89, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (90, '000030', 'form', '/table.html?Id=000030', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 90, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (91, '000031', 'law', '/show.html?Id=000031', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 91, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (92, '000031', 'case', '/showcase.html?Id=000031', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 92, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (93, '000031', 'form', '/table.html?Id=000031', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 93, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (94, '000032', 'law', '/show.html?Id=000032', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 94, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (95, '000032', 'case', '/showcase.html?Id=000032', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 95, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (96, '000032', 'form', '/table.html?Id=000032', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 96, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (97, '000033', 'law', '/show.html?Id=000033', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 97, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (98, '000033', 'case', '/showcase.html?Id=000033', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 98, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (99, '000033', 'form', '/table.html?Id=000033', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 99, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (100, '000034', 'law', '/show.html?Id=000034', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 100, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (101, '000034', 'case', '/showcase.html?Id=000034', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 101, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (102, '000034', 'form', '/table.html?Id=000034', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 102, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (103, '000035', 'law', '/show.html?Id=000035', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 103, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (104, '000035', 'case', '/showcase.html?Id=000035', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 104, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (105, '000035', 'form', '/table.html?Id=000035', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 105, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (106, '000036', 'law', '/show.html?Id=000036', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 106, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (107, '000036', 'case', '/showcase.html?Id=000036', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 107, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (108, '000036', 'form', '/table.html?Id=000036', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 108, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (109, '000037', 'law', '/show.html?Id=000037', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 109, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (110, '000037', 'case', '/showcase.html?Id=000037', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 110, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (111, '000037', 'form', '/table.html?Id=000037', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 111, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (112, '000038', 'law', '/show.html?Id=000038', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 112, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (113, '000038', 'case', '/showcase.html?Id=000038', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 113, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (114, '000038', 'form', '/table.html?Id=000038', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 114, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (115, '000039', 'law', '/show.html?Id=000039', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 115, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (116, '000039', 'case', '/showcase.html?Id=000039', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 116, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (117, '000039', 'form', '/table.html?Id=000039', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 117, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (118, '000040', 'law', '/show.html?Id=000040', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 118, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (119, '000040', 'case', '/showcase.html?Id=000040', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 119, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (120, '000040', 'form', '/table.html?Id=000040', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 120, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (121, '000041', 'law', '/show.html?Id=000041', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 121, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (122, '000041', 'case', '/showcase.html?Id=000041', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 122, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (123, '000041', 'form', '/table.html?Id=000041', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 123, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (124, '000042', 'law', '/show.html?Id=000042', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 124, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (125, '000042', 'case', '/showcase.html?Id=000042', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 125, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (126, '000042', 'form', '/table.html?Id=000042', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 126, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (127, '000043', 'law', '/show.html?Id=000043', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 127, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (128, '000043', 'case', '/showcase.html?Id=000043', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 128, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (129, '000043', 'form', '/table.html?Id=000043', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 129, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (130, '000044', 'law', '/show.html?Id=000044', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 130, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (131, '000044', 'case', '/showcase.html?Id=000044', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 131, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (132, '000044', 'form', '/table.html?Id=000044', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 132, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (133, '000045', 'law', '/show.html?Id=000045', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 133, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (134, '000045', 'case', '/showcase.html?Id=000045', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 134, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (135, '000045', 'form', '/table.html?Id=000045', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 135, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (136, '000046', 'law', '/show.html?Id=000046', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 136, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (137, '000046', 'case', '/showcase.html?Id=000046', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 137, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (138, '000046', 'form', '/table.html?Id=000046', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 138, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (139, '000047', 'law', '/show.html?Id=000047', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 139, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (140, '000047', 'case', '/showcase.html?Id=000047', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 140, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (141, '000047', 'form', '/table.html?Id=000047', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 141, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (142, '000048', 'law', '/show.html?Id=000048', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 142, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (143, '000048', 'case', '/showcase.html?Id=000048', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 143, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (144, '000048', 'form', '/table.html?Id=000048', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 144, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (145, '000049', 'law', '/show.html?Id=000049', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 145, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (146, '000049', 'case', '/showcase.html?Id=000049', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 146, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (147, '000049', 'form', '/table.html?Id=000049', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 147, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (148, '000050', 'law', '/show.html?Id=000050', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 148, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (149, '000050', 'case', '/showcase.html?Id=000050', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 149, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (150, '000050', 'form', '/table.html?Id=000050', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 150, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (151, '000051', 'law', '/show.html?Id=000051', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 151, 1, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:11:40', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (152, '000051', 'case', '/showcase.html?Id=000051', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 152, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (153, '000051', 'form', '/table.html?Id=000051', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 153, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (154, '000052', 'law', '/show.html?Id=000052', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 154, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (155, '000052', 'case', '/showcase.html?Id=000052', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 155, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (156, '000052', 'form', '/table.html?Id=000052', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 156, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (157, '000053', 'law', '/show.html?Id=000053', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 157, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (158, '000053', 'case', '/showcase.html?Id=000053', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 158, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (159, '000053', 'form', '/table.html?Id=000053', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 159, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (160, '000054', 'law', '/show.html?Id=000054', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 160, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (161, '000054', 'case', '/showcase.html?Id=000054', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 161, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (162, '000054', 'form', '/table.html?Id=000054', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 162, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (163, '000055', 'law', '/show.html?Id=000055', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 163, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (164, '000055', 'case', '/showcase.html?Id=000055', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 164, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (165, '000055', 'form', '/table.html?Id=000055', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 165, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (166, '000056', 'law', '/show.html?Id=000056', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 166, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (167, '000056', 'case', '/showcase.html?Id=000056', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 167, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (168, '000056', 'form', '/table.html?Id=000056', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 168, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (169, '000057', 'law', '/show.html?Id=000057', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 169, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (170, '000057', 'case', '/showcase.html?Id=000057', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 170, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (171, '000057', 'form', '/table.html?Id=000057', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 171, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (172, '000058', 'law', '/show.html?Id=000058', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 172, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (173, '000058', 'case', '/showcase.html?Id=000058', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 173, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (174, '000058', 'form', '/table.html?Id=000058', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 174, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (175, '000059', 'law', '/show.html?Id=000059', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 175, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (176, '000059', 'case', '/showcase.html?Id=000059', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 176, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (177, '000059', 'form', '/table.html?Id=000059', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 177, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (178, '000060', 'law', '/show.html?Id=000060', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 178, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (179, '000060', 'case', '/showcase.html?Id=000060', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 179, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (180, '000060', 'form', '/table.html?Id=000060', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 180, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (181, '000061', 'law', '/show.html?Id=000061', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 181, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (182, '000061', 'case', '/showcase.html?Id=000061', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 182, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (183, '000061', 'form', '/table.html?Id=000061', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 183, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (184, '000062', 'law', '/show.html?Id=000062', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 184, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (185, '000062', 'case', '/showcase.html?Id=000062', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 185, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (186, '000062', 'form', '/table.html?Id=000062', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 186, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (187, '000063', 'law', '/show.html?Id=000063', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 187, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (188, '000063', 'case', '/showcase.html?Id=000063', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 188, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (189, '000063', 'form', '/table.html?Id=000063', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 189, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (190, '000064', 'law', '/show.html?Id=000064', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 190, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (191, '000064', 'case', '/showcase.html?Id=000064', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 191, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (192, '000064', 'form', '/table.html?Id=000064', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 192, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (193, '000065', 'law', '/show.html?Id=000065', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 193, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (194, '000065', 'case', '/showcase.html?Id=000065', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 194, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (195, '000065', 'form', '/table.html?Id=000065', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 195, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (196, '000066', 'law', '/show.html?Id=000066', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 196, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (197, '000066', 'case', '/showcase.html?Id=000066', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 197, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (198, '000066', 'form', '/table.html?Id=000066', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 198, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (199, '000067', 'law', '/show.html?Id=000067', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 199, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (200, '000067', 'case', '/showcase.html?Id=000067', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 200, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (201, '000067', 'form', '/table.html?Id=000067', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 201, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (202, '000068', 'law', '/show.html?Id=000068', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 202, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (203, '000068', 'case', '/showcase.html?Id=000068', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 203, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (204, '000068', 'form', '/table.html?Id=000068', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 204, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (205, '000069', 'law', '/show.html?Id=000069', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 205, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (206, '000069', 'case', '/showcase.html?Id=000069', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 206, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (207, '000069', 'form', '/table.html?Id=000069', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 207, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (208, '000070', 'law', '/show.html?Id=000070', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 208, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (209, '000070', 'case', '/showcase.html?Id=000070', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 209, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (210, '000070', 'form', '/table.html?Id=000070', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 210, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (211, '000071', 'law', '/show.html?Id=000071', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 211, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (212, '000071', 'case', '/showcase.html?Id=000071', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 212, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (213, '000071', 'form', '/table.html?Id=000071', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 213, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (214, '000072', 'law', '/show.html?Id=000072', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 214, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (215, '000072', 'case', '/showcase.html?Id=000072', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 215, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (216, '000072', 'form', '/table.html?Id=000072', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 216, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (217, '000073', 'law', '/show.html?Id=000073', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 217, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (218, '000073', 'case', '/showcase.html?Id=000073', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 218, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (219, '000073', 'form', '/table.html?Id=000073', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 219, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (220, '000074', 'law', '/show.html?Id=000074', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 220, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (221, '000074', 'case', '/showcase.html?Id=000074', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 221, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (222, '000074', 'form', '/table.html?Id=000074', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 222, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (223, '000075', 'law', '/show.html?Id=000075', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 223, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (224, '000075', 'case', '/showcase.html?Id=000075', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 224, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (225, '000075', 'form', '/table.html?Id=000075', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 225, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (226, '000076', 'law', '/show.html?Id=000076', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 226, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (227, '000076', 'case', '/showcase.html?Id=000076', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 227, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (228, '000076', 'form', '/table.html?Id=000076', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 228, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (229, '000077', 'law', '/show.html?Id=000077', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 229, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (230, '000077', 'case', '/showcase.html?Id=000077', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 230, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (231, '000077', 'form', '/table.html?Id=000077', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 231, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (232, '000078', 'law', '/show.html?Id=000078', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 232, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (233, '000078', 'case', '/showcase.html?Id=000078', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 233, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (234, '000078', 'form', '/table.html?Id=000078', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 234, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (235, '000079', 'law', '/show.html?Id=000079', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 235, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (236, '000079', 'case', '/showcase.html?Id=000079', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 236, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (237, '000079', 'form', '/table.html?Id=000079', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 237, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (238, '000080', 'law', '/show.html?Id=000080', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 238, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (239, '000080', 'case', '/showcase.html?Id=000080', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 239, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (240, '000080', 'form', '/table.html?Id=000080', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 240, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (241, '000081', 'law', '/show.html?Id=000081', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 241, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (242, '000081', 'case', '/showcase.html?Id=000081', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 242, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (243, '000081', 'form', '/table.html?Id=000081', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 243, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (244, '000082', 'law', '/show.html?Id=000082', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 244, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (245, '000082', 'case', '/showcase.html?Id=000082', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 245, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (246, '000082', 'form', '/table.html?Id=000082', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 246, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (247, '000083', 'law', '/show.html?Id=000083', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 247, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (248, '000083', 'case', '/showcase.html?Id=000083', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 248, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (249, '000083', 'form', '/table.html?Id=000083', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 249, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (250, '000084', 'law', '/show.html?Id=000084', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 250, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (251, '000084', 'case', '/showcase.html?Id=000084', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 251, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (252, '000084', 'form', '/table.html?Id=000084', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 252, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (253, '000085', 'law', '/show.html?Id=000085', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 253, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (254, '000085', 'case', '/showcase.html?Id=000085', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 254, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (255, '000085', 'form', '/table.html?Id=000085', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 255, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (256, '000086', 'law', '/show.html?Id=000086', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 256, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (257, '000086', 'case', '/showcase.html?Id=000086', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 257, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (258, '000086', 'form', '/table.html?Id=000086', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 258, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (259, '000087', 'law', '/show.html?Id=000087', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 259, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (260, '000087', 'case', '/showcase.html?Id=000087', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 260, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (261, '000087', 'form', '/table.html?Id=000087', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 261, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (262, '000088', 'law', '/show.html?Id=000088', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 262, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (263, '000088', 'case', '/showcase.html?Id=000088', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 263, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (264, '000088', 'form', '/table.html?Id=000088', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 264, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (265, '000089', 'law', '/show.html?Id=000089', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 265, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (266, '000089', 'case', '/showcase.html?Id=000089', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 266, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (267, '000089', 'form', '/table.html?Id=000089', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 267, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (268, '000090', 'law', '/show.html?Id=000090', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 268, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (269, '000090', 'case', '/showcase.html?Id=000090', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 269, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (270, '000090', 'form', '/table.html?Id=000090', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 270, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (271, '000091', 'law', '/show.html?Id=000091', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 271, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (272, '000091', 'case', '/showcase.html?Id=000091', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 272, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (273, '000091', 'form', '/table.html?Id=000091', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 273, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (274, '000092', 'law', '/show.html?Id=000092', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 274, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (275, '000092', 'case', '/showcase.html?Id=000092', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 275, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (276, '000092', 'form', '/table.html?Id=000092', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 276, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (277, '000093', 'law', '/show.html?Id=000093', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 277, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (278, '000093', 'case', '/showcase.html?Id=000093', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 278, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (279, '000093', 'form', '/table.html?Id=000093', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 279, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (280, '000094', 'law', '/show.html?Id=000094', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 280, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (281, '000094', 'case', '/showcase.html?Id=000094', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 281, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (282, '000094', 'form', '/table.html?Id=000094', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 282, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (283, '000095', 'law', '/show.html?Id=000095', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 283, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (284, '000095', 'case', '/showcase.html?Id=000095', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 284, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (285, '000095', 'form', '/table.html?Id=000095', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 285, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (286, '000096', 'law', '/show.html?Id=000096', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 286, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (287, '000096', 'case', '/showcase.html?Id=000096', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 287, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (288, '000096', 'form', '/table.html?Id=000096', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 288, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (289, '000097', 'law', '/show.html?Id=000097', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 289, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (290, '000097', 'case', '/showcase.html?Id=000097', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 290, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (291, '000097', 'form', '/table.html?Id=000097', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 291, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (292, '000098', 'law', '/show.html?Id=000098', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 292, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (293, '000098', 'case', '/showcase.html?Id=000098', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 293, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (294, '000098', 'form', '/table.html?Id=000098', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 294, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (295, '000099', 'law', '/show.html?Id=000099', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 295, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (296, '000099', 'case', '/showcase.html?Id=000099', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 296, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (297, '000099', 'form', '/table.html?Id=000099', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 297, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (298, '000100', 'law', '/show.html?Id=000100', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 298, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (299, '000100', 'case', '/showcase.html?Id=000100', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 299, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (300, '000100', 'form', '/table.html?Id=000100', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 300, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (301, '000101', 'law', '/show.html?Id=000101', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 301, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (302, '000101', 'case', '/showcase.html?Id=000101', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 302, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (303, '000101', 'form', '/table.html?Id=000101', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 303, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (304, '000102', 'law', '/show.html?Id=000102', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 304, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (305, '000102', 'case', '/showcase.html?Id=000102', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 305, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (306, '000102', 'form', '/table.html?Id=000102', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 306, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (307, '000103', 'law', '/show.html?Id=000103', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 307, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (308, '000103', 'case', '/showcase.html?Id=000103', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 308, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (309, '000103', 'form', '/table.html?Id=000103', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 309, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (310, '000104', 'law', '/show.html?Id=000104', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 310, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (311, '000104', 'case', '/showcase.html?Id=000104', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 311, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (312, '000104', 'form', '/table.html?Id=000104', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 312, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (313, '000105', 'law', '/show.html?Id=000105', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 313, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (314, '000105', 'case', '/showcase.html?Id=000105', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 314, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (315, '000105', 'form', '/table.html?Id=000105', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 315, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (316, '000106', 'law', '/show.html?Id=000106', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 316, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (317, '000106', 'case', '/showcase.html?Id=000106', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 317, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (318, '000106', 'form', '/table.html?Id=000106', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 318, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (319, '000107', 'law', '/show.html?Id=000107', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 319, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (320, '000107', 'case', '/showcase.html?Id=000107', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 320, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (321, '000107', 'form', '/table.html?Id=000107', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 321, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (322, '000108', 'law', '/show.html?Id=000108', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 322, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (323, '000108', 'case', '/showcase.html?Id=000108', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 323, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (324, '000108', 'form', '/table.html?Id=000108', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 324, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (325, '000109', 'law', '/show.html?Id=000109', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 325, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (326, '000109', 'case', '/showcase.html?Id=000109', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 326, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (327, '000109', 'form', '/table.html?Id=000109', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 327, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (328, '000110', 'law', '/show.html?Id=000110', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 328, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (329, '000110', 'case', '/showcase.html?Id=000110', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 329, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (330, '000110', 'form', '/table.html?Id=000110', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 330, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (331, '000111', 'law', '/show.html?Id=000111', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 331, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (332, '000111', 'case', '/showcase.html?Id=000111', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 332, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (333, '000111', 'form', '/table.html?Id=000111', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 333, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (334, '000112', 'law', '/show.html?Id=000112', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 334, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (335, '000112', 'case', '/showcase.html?Id=000112', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 335, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (336, '000112', 'form', '/table.html?Id=000112', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 336, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (337, '000113', 'law', '/show.html?Id=000113', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 337, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (338, '000113', 'case', '/showcase.html?Id=000113', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 338, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (339, '000113', 'form', '/table.html?Id=000113', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 339, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (340, '000114', 'law', '/show.html?Id=000114', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 340, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (341, '000114', 'case', '/showcase.html?Id=000114', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 341, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (342, '000114', 'form', '/table.html?Id=000114', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 342, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (343, '000115', 'law', '/show.html?Id=000115', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 343, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (344, '000115', 'case', '/showcase.html?Id=000115', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 344, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (345, '000115', 'form', '/table.html?Id=000115', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 345, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (346, '000116', 'law', '/show.html?Id=000116', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 346, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (347, '000116', 'case', '/showcase.html?Id=000116', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 347, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (348, '000116', 'form', '/table.html?Id=000116', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 348, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (349, '000117', 'law', '/show.html?Id=000117', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 349, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (350, '000117', 'case', '/showcase.html?Id=000117', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 350, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (351, '000117', 'form', '/table.html?Id=000117', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 351, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (352, '000118', 'law', '/show.html?Id=000118', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 352, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (353, '000118', 'case', '/showcase.html?Id=000118', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 353, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (354, '000118', 'form', '/table.html?Id=000118', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 354, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (355, '000119', 'law', '/show.html?Id=000119', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 355, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (356, '000119', 'case', '/showcase.html?Id=000119', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 356, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (357, '000119', 'form', '/table.html?Id=000119', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 357, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (358, '000120', 'law', '/show.html?Id=000120', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 358, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (359, '000120', 'case', '/showcase.html?Id=000120', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 359, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (360, '000120', 'form', '/table.html?Id=000120', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 360, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (361, '000121', 'law', '/show.html?Id=000121', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 361, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (362, '000121', 'case', '/showcase.html?Id=000121', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 362, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (363, '000121', 'form', '/table.html?Id=000121', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 363, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (364, '000122', 'law', '/show.html?Id=000122', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 364, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (365, '000122', 'case', '/showcase.html?Id=000122', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 365, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (366, '000122', 'form', '/table.html?Id=000122', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 366, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (367, '000123', 'law', '/show.html?Id=000123', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 367, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (368, '000123', 'case', '/showcase.html?Id=000123', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 368, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (369, '000123', 'form', '/table.html?Id=000123', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 369, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (370, '000124', 'law', '/show.html?Id=000124', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 370, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (371, '000124', 'case', '/showcase.html?Id=000124', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 371, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (372, '000124', 'form', '/table.html?Id=000124', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 372, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (373, '000125', 'law', '/show.html?Id=000125', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 373, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (374, '000125', 'case', '/showcase.html?Id=000125', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 374, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (375, '000125', 'form', '/table.html?Id=000125', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 375, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (376, '000126', 'law', '/show.html?Id=000126', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 376, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (377, '000126', 'case', '/showcase.html?Id=000126', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 377, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (378, '000126', 'form', '/table.html?Id=000126', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 378, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (379, '000127', 'law', '/show.html?Id=000127', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 379, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (380, '000127', 'case', '/showcase.html?Id=000127', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 380, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (381, '000127', 'form', '/table.html?Id=000127', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 381, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (382, '000128', 'law', '/show.html?Id=000128', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 382, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (383, '000128', 'case', '/showcase.html?Id=000128', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 383, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (384, '000128', 'form', '/table.html?Id=000128', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 384, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (385, '000129', 'law', '/show.html?Id=000129', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 385, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (386, '000129', 'case', '/showcase.html?Id=000129', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 386, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (387, '000129', 'form', '/table.html?Id=000129', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 387, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (388, '000130', 'law', '/show.html?Id=000130', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 388, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (389, '000130', 'case', '/showcase.html?Id=000130', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 389, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (390, '000130', 'form', '/table.html?Id=000130', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 390, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (391, '000131', 'law', '/show.html?Id=000131', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 391, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (392, '000131', 'case', '/showcase.html?Id=000131', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 392, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (393, '000131', 'form', '/table.html?Id=000131', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 393, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (394, '000132', 'law', '/show.html?Id=000132', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 394, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (395, '000132', 'case', '/showcase.html?Id=000132', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 395, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (396, '000132', 'form', '/table.html?Id=000132', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 396, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (397, '000133', 'law', '/show.html?Id=000133', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 397, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (398, '000133', 'case', '/showcase.html?Id=000133', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 398, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (399, '000133', 'form', '/table.html?Id=000133', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 399, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (400, '000134', 'law', '/show.html?Id=000134', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 400, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (401, '000134', 'case', '/showcase.html?Id=000134', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 401, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (402, '000134', 'form', '/table.html?Id=000134', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 402, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (403, '000135', 'law', '/show.html?Id=000135', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 403, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (404, '000135', 'case', '/showcase.html?Id=000135', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 404, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (405, '000135', 'form', '/table.html?Id=000135', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 405, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (406, '000136', 'law', '/show.html?Id=000136', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 406, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (407, '000136', 'case', '/showcase.html?Id=000136', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 407, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (408, '000136', 'form', '/table.html?Id=000136', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 408, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (409, '000137', 'law', '/show.html?Id=000137', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 409, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (410, '000137', 'case', '/showcase.html?Id=000137', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 410, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (411, '000137', 'form', '/table.html?Id=000137', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 411, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (412, '000138', 'law', '/show.html?Id=000138', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 412, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (413, '000138', 'case', '/showcase.html?Id=000138', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 413, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (414, '000138', 'form', '/table.html?Id=000138', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 414, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (415, '000139', 'law', '/show.html?Id=000139', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 415, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (416, '000139', 'case', '/showcase.html?Id=000139', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 416, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (417, '000139', 'form', '/table.html?Id=000139', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 417, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (418, '000140', 'law', '/show.html?Id=000140', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 418, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (419, '000140', 'case', '/showcase.html?Id=000140', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 419, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (420, '000140', 'form', '/table.html?Id=000140', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 420, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (421, '000141', 'law', '/show.html?Id=000141', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 421, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (422, '000141', 'case', '/showcase.html?Id=000141', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 422, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (423, '000141', 'form', '/table.html?Id=000141', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 423, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (424, '000142', 'law', '/show.html?Id=000142', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 424, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (425, '000142', 'case', '/showcase.html?Id=000142', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 425, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (426, '000142', 'form', '/table.html?Id=000142', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 426, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (427, '000143', 'law', '/show.html?Id=000143', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 427, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (428, '000143', 'case', '/showcase.html?Id=000143', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 428, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (429, '000143', 'form', '/table.html?Id=000143', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 429, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (430, '000144', 'law', '/show.html?Id=000144', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 430, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (431, '000144', 'case', '/showcase.html?Id=000144', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 431, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (432, '000144', 'form', '/table.html?Id=000144', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 432, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (433, '000145', 'law', '/show.html?Id=000145', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 433, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (434, '000145', 'case', '/showcase.html?Id=000145', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 434, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (435, '000145', 'form', '/table.html?Id=000145', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 435, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (436, '000146', 'law', '/show.html?Id=000146', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 436, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (437, '000146', 'case', '/showcase.html?Id=000146', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 437, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (438, '000146', 'form', '/table.html?Id=000146', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 438, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (439, '000147', 'law', '/show.html?Id=000147', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 439, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (440, '000147', 'case', '/showcase.html?Id=000147', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 440, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (441, '000147', 'form', '/table.html?Id=000147', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 441, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (442, '000148', 'law', '/show.html?Id=000148', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 442, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (443, '000148', 'case', '/showcase.html?Id=000148', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 443, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (444, '000148', 'form', '/table.html?Id=000148', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 444, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (445, '000149', 'law', '/show.html?Id=000149', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 445, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (446, '000149', 'case', '/showcase.html?Id=000149', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 446, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (447, '000149', 'form', '/table.html?Id=000149', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 447, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (448, '000150', 'law', '/show.html?Id=000150', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 448, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (449, '000150', 'case', '/showcase.html?Id=000150', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 449, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (450, '000150', 'form', '/table.html?Id=000150', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 450, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); + +-- ---------------------------- +-- Table structure for message_system +-- ---------------------------- +DROP TABLE IF EXISTS `message_system`; +CREATE TABLE `message_system` ( + `message_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `message_title` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '标题', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `send_mode` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '发送方式(0平台 1手机号 2 邮箱)', + `code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '号码', + `message_content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '消息内容', + `message_recipient` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '接收人', + `message_status` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '消息状态(0未读 1已读)', + `message_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '消息类型', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`message_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '消息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of message_system +-- ---------------------------- + +-- ---------------------------- +-- Table structure for message_template +-- ---------------------------- +DROP TABLE IF EXISTS `message_template`; +CREATE TABLE `message_template` ( + `template_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `template_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '模版名称', + `template_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '模版CODE', + `template_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '模版类型', + `template_content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '模版内容', + `template_variable` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '变量属性', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`template_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '模版表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of message_template +-- ---------------------------- + +-- ---------------------------- +-- Table structure for message_variable +-- ---------------------------- +DROP TABLE IF EXISTS `message_variable`; +CREATE TABLE `message_variable` ( + `variable_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `variable_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '变量名称', + `variable_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '变量类型', + `variable_content` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '变量内容', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`variable_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '变量表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of message_variable +-- ---------------------------- + +-- ---------------------------- +-- Table structure for oauth_user +-- ---------------------------- +DROP TABLE IF EXISTS `oauth_user`; +CREATE TABLE `oauth_user` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键', + `uuid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '第三方系统的唯一ID,详细解释请参考:名词解释', + `user_id` bigint NOT NULL COMMENT '用户ID', + `source` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '第三方用户来源,可选值:GITHUB、GITEE、QQ,更多请参考:AuthDefaultSource.java(opens new window)', + `access_token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户的授权令牌', + `expire_in` int NULL DEFAULT NULL COMMENT '第三方用户的授权令牌的有效期,部分平台可能没有', + `refresh_token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '刷新令牌,部分平台可能没有', + `open_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '第三方用户的 open id,部分平台可能没有', + `uid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '第三方用户的 ID,部分平台可能没有', + `access_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '个别平台的授权信息,部分平台可能没有', + `union_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '第三方用户的 union id,部分平台可能没有', + `scope` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '第三方用户授予的权限,部分平台可能没有', + `token_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '个别平台的授权信息,部分平台可能没有', + `id_token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'id token,部分平台可能没有', + `mac_algorithm` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '小米平台用户的附带属性,部分平台可能没有', + `mac_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '小米平台用户的附带属性,部分平台可能没有', + `code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '用户的授权code,部分平台可能没有', + `oauth_token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'Twitter平台用户的附带属性,部分平台可能没有', + `oauth_token_secret` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'Twitter平台用户的附带属性,部分平台可能没有', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '第三方登录' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of oauth_user +-- ---------------------------- + +-- ---------------------------- +-- Table structure for online_mb +-- ---------------------------- +DROP TABLE IF EXISTS `online_mb`; +CREATE TABLE `online_mb` ( + `mb_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `tag` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '标签名', + `tag_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '标签id', + `parameter_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '参数类型', + `result_map` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '结果类型', + `sql_text` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'sql语句', + `path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '请求路径', + `method` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '请求方式', + `result_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '响应类型', + `actuator` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '执行器', + `user_id` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '是否需要userId', + `dept_id` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '是否需要deptId', + `permission_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '许可类型', + `permission_value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '许可值', + `del_flag` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', + PRIMARY KEY (`mb_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '在线接口' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of online_mb +-- ---------------------------- + +-- ---------------------------- +-- Table structure for pay_invoice +-- ---------------------------- +DROP TABLE IF EXISTS `pay_invoice`; +CREATE TABLE `pay_invoice` ( + `invoice_id` bigint NOT NULL AUTO_INCREMENT COMMENT '发票id', + `order_number` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '订单号', + `invoice_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '发票类型', + `invoice_header` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '发票抬头', + `invoice_number` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '纳税人识别号', + `invoice_phone` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '收票人手机号', + `invoice_email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '收票人邮箱', + `invoice_remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '发票备注', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`invoice_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '发票' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of pay_invoice +-- ---------------------------- + +-- ---------------------------- +-- Table structure for pay_order +-- ---------------------------- +DROP TABLE IF EXISTS `pay_order`; +CREATE TABLE `pay_order` ( + `order_id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单id', + `order_number` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '订单号', + `third_number` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '第三方订单号', + `order_status` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '订单状态', + `total_amount` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '订单总金额', + `actual_amount` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '实际支付金额', + `order_content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '订单内容', + `order_message` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '负载信息', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`order_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '订单' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of pay_order +-- ---------------------------- + +-- ---------------------------- +-- Table structure for qrtz_blob_triggers +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_blob_triggers`; +CREATE TABLE `qrtz_blob_triggers` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '调度名称', + `trigger_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'qrtz_triggers表trigger_name的外键', + `trigger_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键', + `blob_data` blob NULL COMMENT '存放持久化Trigger对象', + PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE, + CONSTRAINT `qrtz_blob_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Blob类型的触发器表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_blob_triggers +-- ---------------------------- + +-- ---------------------------- +-- Table structure for qrtz_calendars +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_calendars`; +CREATE TABLE `qrtz_calendars` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '调度名称', + `calendar_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '日历名称', + `calendar` blob NOT NULL COMMENT '存放持久化calendar对象', + PRIMARY KEY (`sched_name`, `calendar_name`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '日历信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_calendars +-- ---------------------------- + +-- ---------------------------- +-- Table structure for qrtz_cron_triggers +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_cron_triggers`; +CREATE TABLE `qrtz_cron_triggers` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '调度名称', + `trigger_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'qrtz_triggers表trigger_name的外键', + `trigger_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键', + `cron_expression` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'cron表达式', + `time_zone_id` varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '时区', + PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE, + CONSTRAINT `qrtz_cron_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Cron类型的触发器表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_cron_triggers +-- ---------------------------- + +-- ---------------------------- +-- Table structure for qrtz_fired_triggers +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_fired_triggers`; +CREATE TABLE `qrtz_fired_triggers` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '调度名称', + `entry_id` varchar(95) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '调度器实例id', + `trigger_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'qrtz_triggers表trigger_name的外键', + `trigger_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键', + `instance_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '调度器实例名', + `fired_time` bigint NOT NULL COMMENT '触发的时间', + `sched_time` bigint NOT NULL COMMENT '定时器制定的时间', + `priority` int NOT NULL COMMENT '优先级', + `state` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '状态', + `job_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '任务名称', + `job_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '任务组名', + `is_nonconcurrent` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '是否并发', + `requests_recovery` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '是否接受恢复执行', + PRIMARY KEY (`sched_name`, `entry_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '已触发的触发器表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_fired_triggers +-- ---------------------------- + +-- ---------------------------- +-- Table structure for qrtz_job_details +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_job_details`; +CREATE TABLE `qrtz_job_details` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '调度名称', + `job_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '任务名称', + `job_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '任务组名', + `description` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '相关介绍', + `job_class_name` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '执行任务类名称', + `is_durable` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '是否持久化', + `is_nonconcurrent` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '是否并发', + `is_update_data` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '是否更新数据', + `requests_recovery` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '是否接受恢复执行', + `job_data` blob NULL COMMENT '存放持久化job对象', + PRIMARY KEY (`sched_name`, `job_name`, `job_group`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '任务详细信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_job_details +-- ---------------------------- + +-- ---------------------------- +-- Table structure for qrtz_locks +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_locks`; +CREATE TABLE `qrtz_locks` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '调度名称', + `lock_name` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '悲观锁名称', + PRIMARY KEY (`sched_name`, `lock_name`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '存储的悲观锁信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_locks +-- ---------------------------- + +-- ---------------------------- +-- Table structure for qrtz_paused_trigger_grps +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_paused_trigger_grps`; +CREATE TABLE `qrtz_paused_trigger_grps` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '调度名称', + `trigger_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键', + PRIMARY KEY (`sched_name`, `trigger_group`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '暂停的触发器表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_paused_trigger_grps +-- ---------------------------- + +-- ---------------------------- +-- Table structure for qrtz_scheduler_state +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_scheduler_state`; +CREATE TABLE `qrtz_scheduler_state` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '调度名称', + `instance_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '实例名称', + `last_checkin_time` bigint NOT NULL COMMENT '上次检查时间', + `checkin_interval` bigint NOT NULL COMMENT '检查间隔时间', + PRIMARY KEY (`sched_name`, `instance_name`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '调度器状态表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_scheduler_state +-- ---------------------------- + +-- ---------------------------- +-- Table structure for qrtz_simple_triggers +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_simple_triggers`; +CREATE TABLE `qrtz_simple_triggers` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '调度名称', + `trigger_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'qrtz_triggers表trigger_name的外键', + `trigger_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键', + `repeat_count` bigint NOT NULL COMMENT '重复的次数统计', + `repeat_interval` bigint NOT NULL COMMENT '重复的间隔时间', + `times_triggered` bigint NOT NULL COMMENT '已经触发的次数', + PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE, + CONSTRAINT `qrtz_simple_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '简单触发器的信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_simple_triggers +-- ---------------------------- + +-- ---------------------------- +-- Table structure for qrtz_simprop_triggers +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_simprop_triggers`; +CREATE TABLE `qrtz_simprop_triggers` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '调度名称', + `trigger_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'qrtz_triggers表trigger_name的外键', + `trigger_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键', + `str_prop_1` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'String类型的trigger的第一个参数', + `str_prop_2` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'String类型的trigger的第二个参数', + `str_prop_3` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'String类型的trigger的第三个参数', + `int_prop_1` int NULL DEFAULT NULL COMMENT 'int类型的trigger的第一个参数', + `int_prop_2` int NULL DEFAULT NULL COMMENT 'int类型的trigger的第二个参数', + `long_prop_1` bigint NULL DEFAULT NULL COMMENT 'long类型的trigger的第一个参数', + `long_prop_2` bigint NULL DEFAULT NULL COMMENT 'long类型的trigger的第二个参数', + `dec_prop_1` decimal(13, 4) NULL DEFAULT NULL COMMENT 'decimal类型的trigger的第一个参数', + `dec_prop_2` decimal(13, 4) NULL DEFAULT NULL COMMENT 'decimal类型的trigger的第二个参数', + `bool_prop_1` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'Boolean类型的trigger的第一个参数', + `bool_prop_2` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'Boolean类型的trigger的第二个参数', + PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE, + CONSTRAINT `qrtz_simprop_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '同步机制的行锁表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_simprop_triggers +-- ---------------------------- + +-- ---------------------------- +-- Table structure for qrtz_triggers +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_triggers`; +CREATE TABLE `qrtz_triggers` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '调度名称', + `trigger_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '触发器的名字', + `trigger_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '触发器所属组的名字', + `job_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'qrtz_job_details表job_name的外键', + `job_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'qrtz_job_details表job_group的外键', + `description` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '相关介绍', + `next_fire_time` bigint NULL DEFAULT NULL COMMENT '上一次触发时间(毫秒)', + `prev_fire_time` bigint NULL DEFAULT NULL COMMENT '下一次触发时间(默认为-1表示不触发)', + `priority` int NULL DEFAULT NULL COMMENT '优先级', + `trigger_state` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '触发器状态', + `trigger_type` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '触发器的类型', + `start_time` bigint NOT NULL COMMENT '开始时间', + `end_time` bigint NULL DEFAULT NULL COMMENT '结束时间', + `calendar_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '日程表名称', + `misfire_instr` smallint NULL DEFAULT NULL COMMENT '补偿执行的策略', + `job_data` blob NULL COMMENT '存放持久化job对象', + PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE, + INDEX `sched_name`(`sched_name` ASC, `job_name` ASC, `job_group` ASC) USING BTREE, + CONSTRAINT `qrtz_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `job_name`, `job_group`) REFERENCES `qrtz_job_details` (`sched_name`, `job_name`, `job_group`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '触发器详细信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_triggers +-- ---------------------------- + +-- ---------------------------- +-- Table structure for sys_config +-- ---------------------------- +DROP TABLE IF EXISTS `sys_config`; +CREATE TABLE `sys_config` ( + `config_id` int NOT NULL AUTO_INCREMENT COMMENT '参数主键', + `config_name` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '参数名称', + `config_key` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '参数键名', + `config_value` varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '参数键值', + `config_type` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT 'N' COMMENT '系统内置(Y是 N否)', + `create_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`config_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 106 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '参数配置表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_config +-- ---------------------------- +INSERT INTO `sys_config` VALUES (1, '主框架页-默认皮肤样式名称', 'sys.index.skinName', 'skin-green', 'Y', 'admin', '2023-04-13 20:46:20', 'admin', '2023-04-22 00:45:19', '蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow'); +INSERT INTO `sys_config` VALUES (2, '用户管理-账号初始密码', 'sys.user.initPassword', '123456', 'Y', 'admin', '2023-04-13 20:46:20', '', NULL, '初始化密码 123456'); +INSERT INTO `sys_config` VALUES (3, '主框架页-侧边栏主题', 'sys.index.sideTheme', 'theme-light', 'Y', 'admin', '2023-04-13 20:46:20', 'admin', '2023-04-22 00:45:25', '深色主题theme-dark,浅色主题theme-light'); +INSERT INTO `sys_config` VALUES (4, '账号自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', 'admin', '2023-04-13 20:46:20', '', NULL, '是否开启验证码功能(true开启,false关闭)'); +INSERT INTO `sys_config` VALUES (5, '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'true', 'Y', 'admin', '2023-04-13 20:46:20', 'admin', '2023-04-22 00:41:41', '是否开启注册用户功能(true开启,false关闭)'); +INSERT INTO `sys_config` VALUES (100, '主题颜色', 'sys.index.theme', '#11A983', 'Y', 'admin', '2023-04-22 00:57:18', 'admin', '2023-04-22 00:58:23', NULL); +INSERT INTO `sys_config` VALUES (101, '开启TopNav', 'sys.index.topNav', 'false', 'Y', 'admin', '2023-04-22 00:58:59', '', NULL, NULL); +INSERT INTO `sys_config` VALUES (102, '开启Tags-Views', 'sys.index.tagsView', 'true', 'Y', 'admin', '2023-04-22 00:59:40', '', NULL, NULL); +INSERT INTO `sys_config` VALUES (103, '显示Logo', 'sys.index.sidebarLogo', 'true', 'Y', 'admin', '2023-04-22 01:00:20', '', NULL, NULL); +INSERT INTO `sys_config` VALUES (104, '固定Header', 'sys.index.fixedHeader', 'true', 'Y', 'admin', '2023-04-22 01:00:53', '', NULL, NULL); +INSERT INTO `sys_config` VALUES (105, '动态标题', 'sys.index.dynamicTitle', 'true', 'Y', 'admin', '2023-04-22 01:01:26', 'admin', '2023-04-22 01:01:41', NULL); + +-- ---------------------------- +-- Table structure for sys_deploy_form +-- ---------------------------- +DROP TABLE IF EXISTS `sys_deploy_form`; +CREATE TABLE `sys_deploy_form` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `form_id` bigint NULL DEFAULT NULL COMMENT '表单主键', + `deploy_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '流程实例主键', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 9623 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '流程实例关联表单' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_deploy_form +-- ---------------------------- + +-- ---------------------------- +-- Table structure for sys_dept +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dept`; +CREATE TABLE `sys_dept` ( + `dept_id` bigint NOT NULL AUTO_INCREMENT COMMENT '部门id', + `parent_id` bigint NULL DEFAULT 0 COMMENT '父部门id', + `ancestors` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '祖级列表', + `dept_name` varchar(30) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '部门名称', + `order_num` int NULL DEFAULT 0 COMMENT '显示顺序', + `leader` varchar(20) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '负责人', + `phone` varchar(11) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '联系电话', + `email` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '邮箱', + `status` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '部门状态(0正常 1停用)', + `del_flag` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`dept_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 201 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '部门表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_dept +-- ---------------------------- +INSERT INTO `sys_dept` VALUES (100, 0, '0', '博越科技', 0, '博越', '15888888888', 'boyue@qq.com', '0', '0', 'admin', '2025-05-26 17:20:06', 'admin', '2025-05-29 08:42:28'); +INSERT INTO `sys_dept` VALUES (101, 100, '0,100', '淮安市', 1, '博越', '15888888888', 'boyue@qq.com', '0', '0', 'admin', '2025-05-26 17:20:06', 'admin', '2025-05-29 08:42:59'); +INSERT INTO `sys_dept` VALUES (102, 100, '0,100', '长沙分公司', 2, '若依', '15888888888', 'ry@qq.com', '0', '2', 'admin', '2025-05-26 17:20:06', '', NULL); +INSERT INTO `sys_dept` VALUES (103, 101, '0,100,101', '管理', 2, '管理', '15888888888', 'sfj@qq.com', '0', '0', 'admin', '2025-05-26 17:20:06', 'admin', '2025-05-29 08:44:34'); +INSERT INTO `sys_dept` VALUES (104, 101, '0,100,101', '市场部门', 2, '若依', '15888888888', 'ry@qq.com', '0', '2', 'admin', '2025-05-26 17:20:06', '', NULL); +INSERT INTO `sys_dept` VALUES (105, 101, '0,100,101', '测试部门', 3, '若依', '15888888888', 'ry@qq.com', '0', '2', 'admin', '2025-05-26 17:20:06', '', NULL); +INSERT INTO `sys_dept` VALUES (106, 101, '0,100,101', '财务部门', 4, '若依', '15888888888', 'ry@qq.com', '0', '2', 'admin', '2025-05-26 17:20:06', '', NULL); +INSERT INTO `sys_dept` VALUES (107, 101, '0,100,101', '运维部门', 5, '若依', '15888888888', 'ry@qq.com', '0', '2', 'admin', '2025-05-26 17:20:06', '', NULL); +INSERT INTO `sys_dept` VALUES (108, 102, '0,100,102', '市场部门', 1, '若依', '15888888888', 'ry@qq.com', '0', '2', 'admin', '2025-05-26 17:20:06', '', NULL); +INSERT INTO `sys_dept` VALUES (109, 102, '0,100,102', '财务部门', 2, '若依', '15888888888', 'ry@qq.com', '0', '2', 'admin', '2025-05-26 17:20:06', '', NULL); +INSERT INTO `sys_dept` VALUES (200, 101, '0,100,101', '司法局', 3, NULL, NULL, NULL, '0', '0', 'admin', '2025-05-29 08:44:51', '', NULL); + +-- ---------------------------- +-- Table structure for sys_dict_data +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dict_data`; +CREATE TABLE `sys_dict_data` ( + `dict_code` bigint NOT NULL AUTO_INCREMENT COMMENT '字典编码', + `dict_sort` int NULL DEFAULT 0 COMMENT '字典排序', + `dict_label` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '字典标签', + `dict_value` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '字典键值', + `dict_type` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '字典类型', + `css_class` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '样式属性(其他样式扩展)', + `list_class` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '表格回显样式', + `is_default` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT 'N' COMMENT '是否默认(Y是 N否)', + `status` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1停用)', + `create_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`dict_code`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 160 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '字典数据表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_dict_data +-- ---------------------------- +INSERT INTO `sys_dict_data` VALUES (1, 1, '男', '0', 'sys_user_sex', '', '', 'Y', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '性别男'); +INSERT INTO `sys_dict_data` VALUES (2, 2, '女', '1', 'sys_user_sex', '', '', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '性别女'); +INSERT INTO `sys_dict_data` VALUES (3, 3, '未知', '2', 'sys_user_sex', '', '', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '性别未知'); +INSERT INTO `sys_dict_data` VALUES (4, 1, '显示', '0', 'sys_show_hide', '', 'primary', 'Y', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '显示菜单'); +INSERT INTO `sys_dict_data` VALUES (5, 2, '隐藏', '1', 'sys_show_hide', '', 'danger', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '隐藏菜单'); +INSERT INTO `sys_dict_data` VALUES (6, 1, '正常', '0', 'sys_normal_disable', '', 'primary', 'Y', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '正常状态'); +INSERT INTO `sys_dict_data` VALUES (7, 2, '停用', '1', 'sys_normal_disable', '', 'danger', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '停用状态'); +INSERT INTO `sys_dict_data` VALUES (8, 1, '正常', '0', 'sys_job_status', '', 'primary', 'Y', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '正常状态'); +INSERT INTO `sys_dict_data` VALUES (9, 2, '暂停', '1', 'sys_job_status', '', 'danger', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '停用状态'); +INSERT INTO `sys_dict_data` VALUES (10, 1, '默认', 'DEFAULT', 'sys_job_group', '', '', 'Y', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '默认分组'); +INSERT INTO `sys_dict_data` VALUES (11, 2, '系统', 'SYSTEM', 'sys_job_group', '', '', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '系统分组'); +INSERT INTO `sys_dict_data` VALUES (12, 1, '是', 'Y', 'sys_yes_no', '', 'primary', 'Y', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '系统默认是'); +INSERT INTO `sys_dict_data` VALUES (13, 2, '否', 'N', 'sys_yes_no', '', 'danger', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '系统默认否'); +INSERT INTO `sys_dict_data` VALUES (14, 1, '通知', '1', 'sys_notice_type', '', 'warning', 'Y', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '通知'); +INSERT INTO `sys_dict_data` VALUES (15, 2, '公告', '2', 'sys_notice_type', '', 'success', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '公告'); +INSERT INTO `sys_dict_data` VALUES (16, 1, '正常', '0', 'sys_notice_status', '', 'primary', 'Y', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '正常状态'); +INSERT INTO `sys_dict_data` VALUES (17, 2, '关闭', '1', 'sys_notice_status', '', 'danger', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '关闭状态'); +INSERT INTO `sys_dict_data` VALUES (18, 99, '其他', '0', 'sys_oper_type', '', 'info', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '其他操作'); +INSERT INTO `sys_dict_data` VALUES (19, 1, '新增', '1', 'sys_oper_type', '', 'info', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '新增操作'); +INSERT INTO `sys_dict_data` VALUES (20, 2, '修改', '2', 'sys_oper_type', '', 'info', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '修改操作'); +INSERT INTO `sys_dict_data` VALUES (21, 3, '删除', '3', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '删除操作'); +INSERT INTO `sys_dict_data` VALUES (22, 4, '授权', '4', 'sys_oper_type', '', 'primary', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '授权操作'); +INSERT INTO `sys_dict_data` VALUES (23, 5, '导出', '5', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '导出操作'); +INSERT INTO `sys_dict_data` VALUES (24, 6, '导入', '6', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '导入操作'); +INSERT INTO `sys_dict_data` VALUES (25, 7, '强退', '7', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '强退操作'); +INSERT INTO `sys_dict_data` VALUES (26, 8, '生成代码', '8', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '生成操作'); +INSERT INTO `sys_dict_data` VALUES (27, 9, '清空数据', '9', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '清空操作'); +INSERT INTO `sys_dict_data` VALUES (28, 1, '成功', '0', 'sys_common_status', '', 'primary', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '正常状态'); +INSERT INTO `sys_dict_data` VALUES (29, 2, '失败', '1', 'sys_common_status', '', 'danger', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '停用状态'); +INSERT INTO `sys_dict_data` VALUES (100, 0, 'POST', 'POST', 'online_api_method', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:23:23', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (101, 0, 'GET', 'GET', 'online_api_method', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:23:30', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (102, 0, 'PUT', 'PUT', 'online_api_method', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:23:37', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (103, 0, 'DELETE', 'DELETE', 'online_api_method', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:23:49', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (104, 0, 'select', 'select', 'online_api_tag', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:24:06', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (105, 0, 'update', 'update', 'online_api_tag', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:24:12', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (106, 0, 'insert', 'insert', 'online_api_tag', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:24:18', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (107, 0, 'delete', 'delete', 'online_api_tag', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:24:26', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (108, 0, 'selectList', 'selectList', 'online_api_actuator', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:25:00', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (109, 0, 'insert', 'insert', 'online_api_actuator', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:25:05', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (110, 0, 'selectOne', 'selectOne', 'online_api_actuator', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:25:11', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (111, 0, 'update', 'update', 'online_api_actuator', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:25:16', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (112, 0, 'delete', 'delete', 'online_api_actuator', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:25:21', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (142, 0, '未读', '0', 'message_status', NULL, 'primary', 'N', '0', 'xl', '2024-12-21 15:13:02', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (143, 1, '已读', '1', 'message_status', NULL, 'success', 'N', '0', 'xl', '2024-12-21 15:13:15', 'xl', '2024-12-21 15:13:22', NULL); +INSERT INTO `sys_dict_data` VALUES (144, 0, '平台', '0', 'send_mode', NULL, 'primary', 'N', '0', 'xl', '2024-12-25 09:40:01', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (145, 1, '短信', '1', 'send_mode', NULL, 'success', 'N', '0', 'xl', '2024-12-25 09:40:16', 'xl', '2025-01-01 10:12:07', NULL); +INSERT INTO `sys_dict_data` VALUES (146, 2, '邮件', '2', 'send_mode', NULL, 'warning', 'N', '0', 'xl', '2024-12-25 09:40:28', 'xl', '2025-01-01 10:12:14', NULL); +INSERT INTO `sys_dict_data` VALUES (147, 0, '验证码', '0', 'template_type', NULL, 'primary', 'N', '0', 'xl', '2025-01-03 09:22:52', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (148, 0, '通知', '0', 'message_type', NULL, 'primary', 'N', '0', 'xl', '2025-01-03 15:12:29', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (149, 0, '提示', '1', 'message_type', NULL, 'success', 'N', '0', 'xl', '2025-01-03 15:12:41', 'xl', '2025-01-03 15:12:45', NULL); +INSERT INTO `sys_dict_data` VALUES (150, 1, '推广', '1', 'template_type', NULL, 'success', 'N', '0', 'xl', '2025-01-03 15:13:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (151, 0, '系统指定', 'fixed', 'exp_data_type', NULL, 'default', 'N', '0', 'admin', '2024-03-12 09:04:46', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (152, 0, '动态选择', 'dynamic', 'exp_data_type', NULL, 'default', 'N', '0', 'admin', '2024-03-12 09:05:02', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (153, 0, '任务监听', '1', 'sys_listener_type', NULL, 'default', 'N', '0', 'admin', '2022-12-25 11:47:26', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (154, 2, '执行监听', '2', 'sys_listener_type', NULL, 'default', 'N', '0', 'admin', '2022-12-25 11:47:37', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (155, 0, 'JAVA类', 'classListener', 'sys_listener_value_type', NULL, 'default', 'N', '0', 'admin', '2022-12-25 11:48:55', 'admin', '2024-09-05 21:38:02', NULL); +INSERT INTO `sys_dict_data` VALUES (156, 0, '表达式', 'expressionListener', 'sys_listener_value_type', NULL, 'default', 'N', '0', 'admin', '2022-12-25 11:49:05', 'admin', '2024-09-05 21:38:10', NULL); +INSERT INTO `sys_dict_data` VALUES (157, 0, '代理表达式', 'delegateExpressionListener', 'sys_listener_value_type', NULL, 'default', 'N', '0', 'admin', '2022-12-25 11:49:16', 'admin', '2024-09-05 21:38:16', NULL); +INSERT INTO `sys_dict_data` VALUES (158, 0, '请假', 'leave', 'sys_process_category', NULL, 'default', 'N', '0', 'admin', '2024-03-12 09:08:42', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (159, 0, '报销', 'expense', 'sys_process_category', NULL, 'default', 'N', '0', 'admin', '2024-03-12 09:09:02', '', NULL, NULL); + +-- ---------------------------- +-- Table structure for sys_dict_type +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dict_type`; +CREATE TABLE `sys_dict_type` ( + `dict_id` bigint NOT NULL AUTO_INCREMENT COMMENT '字典主键', + `dict_name` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '字典名称', + `dict_type` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '字典类型', + `status` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1停用)', + `create_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`dict_id`) USING BTREE, + UNIQUE INDEX `dict_type`(`dict_type` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 109 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '字典类型表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_dict_type +-- ---------------------------- +INSERT INTO `sys_dict_type` VALUES (1, '用户性别', 'sys_user_sex', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '用户性别列表'); +INSERT INTO `sys_dict_type` VALUES (2, '菜单状态', 'sys_show_hide', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '菜单状态列表'); +INSERT INTO `sys_dict_type` VALUES (3, '系统开关', 'sys_normal_disable', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '系统开关列表'); +INSERT INTO `sys_dict_type` VALUES (4, '任务状态', 'sys_job_status', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '任务状态列表'); +INSERT INTO `sys_dict_type` VALUES (5, '任务分组', 'sys_job_group', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '任务分组列表'); +INSERT INTO `sys_dict_type` VALUES (6, '系统是否', 'sys_yes_no', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '系统是否列表'); +INSERT INTO `sys_dict_type` VALUES (7, '通知类型', 'sys_notice_type', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '通知类型列表'); +INSERT INTO `sys_dict_type` VALUES (8, '通知状态', 'sys_notice_status', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '通知状态列表'); +INSERT INTO `sys_dict_type` VALUES (9, '操作类型', 'sys_oper_type', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '操作类型列表'); +INSERT INTO `sys_dict_type` VALUES (10, '系统状态', 'sys_common_status', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '登录状态列表'); +INSERT INTO `sys_dict_type` VALUES (100, '请求方式', 'online_api_method', '0', 'admin', '2024-02-21 18:22:03', 'admin', '2024-02-21 18:22:13', NULL); +INSERT INTO `sys_dict_type` VALUES (101, '标签名', 'online_api_tag', '0', 'admin', '2024-02-21 18:22:29', '', NULL, NULL); +INSERT INTO `sys_dict_type` VALUES (102, '响应类型', 'online_api_result', '0', 'admin', '2024-02-21 18:22:46', '', NULL, NULL); +INSERT INTO `sys_dict_type` VALUES (103, '执行器', 'online_api_actuator', '0', 'admin', '2024-02-21 18:23:03', '', NULL, NULL); +INSERT INTO `sys_dict_type` VALUES (104, '表达式类型', 'exp_data_type', '0', 'admin', '2024-03-12 09:03:02', '', NULL, NULL); +INSERT INTO `sys_dict_type` VALUES (105, '监听类型', 'sys_listener_type', '0', 'admin', '2022-12-18 22:03:07', '', NULL, NULL); +INSERT INTO `sys_dict_type` VALUES (106, '监听值类型', 'sys_listener_value_type', '0', 'admin', '2022-12-18 22:03:39', '', NULL, NULL); +INSERT INTO `sys_dict_type` VALUES (107, '监听属性', 'sys_listener_event_type', '0', 'admin', '2022-12-18 22:04:29', '', NULL, NULL); +INSERT INTO `sys_dict_type` VALUES (108, '流程分类', 'sys_process_category', '0', 'admin', '2024-03-12 09:08:18', '', NULL, NULL); + +-- ---------------------------- +-- Table structure for sys_expression +-- ---------------------------- +DROP TABLE IF EXISTS `sys_expression`; +CREATE TABLE `sys_expression` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '表单主键', + `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '表达式名称', + `expression` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '表达式内容', + `data_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '表达式类型', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `create_by` bigint NULL DEFAULT NULL COMMENT '创建人员', + `update_by` bigint NULL DEFAULT NULL COMMENT '更新人员', + `status` tinyint NULL DEFAULT 0 COMMENT '状态', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 69 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '流程表达式' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_expression +-- ---------------------------- + +-- ---------------------------- +-- Table structure for sys_file_info +-- ---------------------------- +DROP TABLE IF EXISTS `sys_file_info`; +CREATE TABLE `sys_file_info` ( + `file_id` bigint NOT NULL AUTO_INCREMENT COMMENT '文件主键', + `file_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '原始文件名', + `file_path` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '统一逻辑路径(/开头)', + `storage_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '存储类型(local/minio/oss)', + `file_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '文件类型/后缀', + `file_size` bigint NULL DEFAULT NULL COMMENT '文件大小(字节)', + `md5` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '文件MD5', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + PRIMARY KEY (`file_id`) USING BTREE, + UNIQUE INDEX `uk_md5`(`md5` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_file_info +-- ---------------------------- + +-- ---------------------------- +-- Table structure for sys_job +-- ---------------------------- +DROP TABLE IF EXISTS `sys_job`; +CREATE TABLE `sys_job` ( + `job_id` bigint NOT NULL AUTO_INCREMENT COMMENT '任务ID', + `job_name` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT '' COMMENT '任务名称', + `job_group` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT 'DEFAULT' COMMENT '任务组名', + `invoke_target` varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '调用目标字符串', + `cron_expression` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT 'cron执行表达式', + `misfire_policy` varchar(20) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '3' COMMENT '计划执行错误策略(1立即执行 2执行一次 3放弃执行)', + `concurrent` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '1' COMMENT '是否并发执行(0允许 1禁止)', + `status` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1暂停)', + `create_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '备注信息', + PRIMARY KEY (`job_id`, `job_name`, `job_group`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 100 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '定时任务调度表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_job +-- ---------------------------- +INSERT INTO `sys_job` VALUES (1, '系统默认(无参)', 'DEFAULT', 'ryTask.ryNoParams', '0/10 * * * * ?', '3', '1', '1', 'admin', '2025-05-26 17:20:07', '', NULL, ''); +INSERT INTO `sys_job` VALUES (2, '系统默认(有参)', 'DEFAULT', 'ryTask.ryParams(\'ry\')', '0/15 * * * * ?', '3', '1', '1', 'admin', '2025-05-26 17:20:07', '', NULL, ''); +INSERT INTO `sys_job` VALUES (3, '系统默认(多参)', 'DEFAULT', 'ryTask.ryMultipleParams(\'ry\', true, 2000L, 316.50D, 100)', '0/20 * * * * ?', '3', '1', '1', 'admin', '2025-05-26 17:20:07', '', NULL, ''); + +-- ---------------------------- +-- Table structure for sys_job_log +-- ---------------------------- +DROP TABLE IF EXISTS `sys_job_log`; +CREATE TABLE `sys_job_log` ( + `job_log_id` bigint NOT NULL AUTO_INCREMENT COMMENT '任务日志ID', + `job_name` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '任务名称', + `job_group` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '任务组名', + `invoke_target` varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '调用目标字符串', + `job_message` varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '日志信息', + `status` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '执行状态(0正常 1失败)', + `exception_info` varchar(2000) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '异常信息', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`job_log_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '定时任务调度日志表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_job_log +-- ---------------------------- + +-- ---------------------------- +-- Table structure for sys_listener +-- ---------------------------- +DROP TABLE IF EXISTS `sys_listener`; +CREATE TABLE `sys_listener` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '表单主键', + `name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '名称', + `type` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '监听类型', + `event_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '事件类型', + `value_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '值类型', + `value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '执行内容', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `create_by` bigint NULL DEFAULT NULL COMMENT '创建人员', + `update_by` bigint NULL DEFAULT NULL COMMENT '更新人员', + `status` tinyint NULL DEFAULT 0 COMMENT '状态', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '流程监听' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_listener +-- ---------------------------- + +-- ---------------------------- +-- Table structure for sys_logininfor +-- ---------------------------- +DROP TABLE IF EXISTS `sys_logininfor`; +CREATE TABLE `sys_logininfor` ( + `info_id` bigint NOT NULL AUTO_INCREMENT COMMENT '访问ID', + `user_name` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '用户账号', + `ipaddr` varchar(128) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '登录IP地址', + `login_location` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '登录地点', + `browser` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '浏览器类型', + `os` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '操作系统', + `status` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '登录状态(0成功 1失败)', + `msg` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '提示消息', + `login_time` datetime NULL DEFAULT NULL COMMENT '访问时间', + PRIMARY KEY (`info_id`) USING BTREE, + INDEX `idx_sys_logininfor_s`(`status` ASC) USING BTREE, + INDEX `idx_sys_logininfor_lt`(`login_time` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 133 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '系统访问记录' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_logininfor +-- ---------------------------- +INSERT INTO `sys_logininfor` VALUES (100, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-26 18:37:16'); +INSERT INTO `sys_logininfor` VALUES (101, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '1', '验证码错误', '2025-05-28 18:36:01'); +INSERT INTO `sys_logininfor` VALUES (102, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-28 18:36:05'); +INSERT INTO `sys_logininfor` VALUES (103, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '退出成功', '2025-05-28 19:33:38'); +INSERT INTO `sys_logininfor` VALUES (104, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-28 19:33:47'); +INSERT INTO `sys_logininfor` VALUES (105, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-28 20:46:43'); +INSERT INTO `sys_logininfor` VALUES (106, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-28 21:18:44'); +INSERT INTO `sys_logininfor` VALUES (107, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-29 06:59:57'); +INSERT INTO `sys_logininfor` VALUES (108, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-29 08:41:00'); +INSERT INTO `sys_logininfor` VALUES (109, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '退出成功', '2025-05-29 10:46:05'); +INSERT INTO `sys_logininfor` VALUES (110, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-29 10:46:17'); +INSERT INTO `sys_logininfor` VALUES (111, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-29 12:33:00'); +INSERT INTO `sys_logininfor` VALUES (112, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '退出成功', '2025-05-29 12:33:12'); +INSERT INTO `sys_logininfor` VALUES (113, 'hasfj', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-29 12:33:26'); +INSERT INTO `sys_logininfor` VALUES (114, 'hasfj', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '退出成功', '2025-05-29 12:33:50'); +INSERT INTO `sys_logininfor` VALUES (115, 'admin', '49.74.40.190', 'XX XX', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-29 13:28:43'); +INSERT INTO `sys_logininfor` VALUES (116, 'admin', '49.74.40.190', 'XX XX', 'Chrome 13', 'Windows 10', '0', '退出成功', '2025-05-29 13:28:52'); +INSERT INTO `sys_logininfor` VALUES (117, 'admin', '49.74.40.190', 'XX XX', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-29 14:55:02'); +INSERT INTO `sys_logininfor` VALUES (118, 'hasfj', '49.74.40.190', 'XX XX', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-29 14:56:29'); +INSERT INTO `sys_logininfor` VALUES (119, 'hasfj', '114.238.34.181', 'XX XX', 'Safari', 'Mac OS X', '0', '登录成功', '2025-05-29 14:57:15'); +INSERT INTO `sys_logininfor` VALUES (120, 'admin', '49.82.111.128', 'XX XX', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-30 13:39:02'); +INSERT INTO `sys_logininfor` VALUES (121, 'admin', '49.82.111.128', 'XX XX', 'Chrome 13', 'Windows 10', '0', '退出成功', '2025-05-30 13:49:58'); +INSERT INTO `sys_logininfor` VALUES (122, 'admin', '49.82.111.128', 'XX XX', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-30 13:50:12'); +INSERT INTO `sys_logininfor` VALUES (123, 'admin', '180.125.41.129', 'XX XX', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-31 08:54:48'); +INSERT INTO `sys_logininfor` VALUES (124, 'admin', '49.87.176.188', 'XX XX', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-31 12:12:39'); +INSERT INTO `sys_logininfor` VALUES (125, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-31 14:44:08'); +INSERT INTO `sys_logininfor` VALUES (126, 'admin', '180.125.41.129', 'XX XX', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-31 15:04:25'); +INSERT INTO `sys_logininfor` VALUES (127, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-31 19:26:24'); +INSERT INTO `sys_logininfor` VALUES (128, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-06-01 10:15:29'); +INSERT INTO `sys_logininfor` VALUES (129, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-06-01 11:03:00'); +INSERT INTO `sys_logininfor` VALUES (130, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-06-02 13:51:42'); +INSERT INTO `sys_logininfor` VALUES (131, 'admin', '218.2.75.254', 'XX XX', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-06-02 19:18:18'); +INSERT INTO `sys_logininfor` VALUES (132, 'admin', '218.2.75.254', 'XX XX', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-06-02 19:33:32'); + +-- ---------------------------- +-- Table structure for sys_menu +-- ---------------------------- +DROP TABLE IF EXISTS `sys_menu`; +CREATE TABLE `sys_menu` ( + `menu_id` bigint NOT NULL AUTO_INCREMENT COMMENT '菜单ID', + `menu_name` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '菜单名称', + `parent_id` bigint NULL DEFAULT 0 COMMENT '父菜单ID', + `order_num` int NULL DEFAULT 0 COMMENT '显示顺序', + `path` varchar(200) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '路由地址', + `component` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '组件路径', + `query` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '路由参数', + `route_name` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '路由名称', + `is_frame` int NULL DEFAULT 1 COMMENT '是否为外链(0是 1否)', + `is_cache` int NULL DEFAULT 0 COMMENT '是否缓存(0缓存 1不缓存)', + `menu_type` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '菜单类型(M目录 C菜单 F按钮)', + `visible` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)', + `status` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '菜单状态(0正常 1停用)', + `perms` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '权限标识', + `icon` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '#' COMMENT '菜单图标', + `create_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '备注', + PRIMARY KEY (`menu_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2105 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '菜单权限表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_menu +-- ---------------------------- +INSERT INTO `sys_menu` VALUES (1, '系统管理', 0, 1, 'system', NULL, '', '', 1, 0, 'M', '0', '0', '', 'system', 'admin', '2025-05-26 17:20:06', '', NULL, '系统管理目录'); +INSERT INTO `sys_menu` VALUES (2, '系统监控', 0, 2, 'monitor', NULL, '', '', 1, 0, 'M', '0', '0', '', 'monitor', 'admin', '2025-05-26 17:20:06', '', NULL, '系统监控目录'); +INSERT INTO `sys_menu` VALUES (3, '系统工具', 0, 3, 'tool', NULL, '', '', 1, 0, 'M', '0', '0', '', 'tool', 'admin', '2025-05-26 17:20:06', '', NULL, '系统工具目录'); +INSERT INTO `sys_menu` VALUES (100, '用户管理', 1, 1, 'user', 'system/user/index', '', '', 1, 0, 'C', '0', '0', 'system:user:list', 'user', 'admin', '2025-05-26 17:20:06', '', NULL, '用户管理菜单'); +INSERT INTO `sys_menu` VALUES (101, '角色管理', 1, 2, 'role', 'system/role/index', '', '', 1, 0, 'C', '0', '0', 'system:role:list', 'peoples', 'admin', '2025-05-26 17:20:06', '', NULL, '角色管理菜单'); +INSERT INTO `sys_menu` VALUES (102, '菜单管理', 1, 3, 'menu', 'system/menu/index', '', '', 1, 0, 'C', '0', '0', 'system:menu:list', 'tree-table', 'admin', '2025-05-26 17:20:06', '', NULL, '菜单管理菜单'); +INSERT INTO `sys_menu` VALUES (103, '部门管理', 1, 4, 'dept', 'system/dept/index', '', '', 1, 0, 'C', '0', '0', 'system:dept:list', 'tree', 'admin', '2025-05-26 17:20:06', '', NULL, '部门管理菜单'); +INSERT INTO `sys_menu` VALUES (104, '岗位管理', 1, 5, 'post', 'system/post/index', '', '', 1, 0, 'C', '0', '0', 'system:post:list', 'post', 'admin', '2025-05-26 17:20:06', '', NULL, '岗位管理菜单'); +INSERT INTO `sys_menu` VALUES (105, '字典管理', 1, 6, 'dict', 'system/dict/index', '', '', 1, 0, 'C', '0', '0', 'system:dict:list', 'dict', 'admin', '2025-05-26 17:20:06', '', NULL, '字典管理菜单'); +INSERT INTO `sys_menu` VALUES (106, '参数设置', 1, 7, 'config', 'system/config/index', '', '', 1, 0, 'C', '0', '0', 'system:config:list', 'edit', 'admin', '2025-05-26 17:20:06', '', NULL, '参数设置菜单'); +INSERT INTO `sys_menu` VALUES (107, '通知公告', 1, 8, 'notice', 'system/notice/index', '', '', 1, 0, 'C', '0', '0', 'system:notice:list', 'message', 'admin', '2025-05-26 17:20:06', '', NULL, '通知公告菜单'); +INSERT INTO `sys_menu` VALUES (108, '日志管理', 1, 9, 'log', '', '', '', 1, 0, 'M', '0', '0', '', 'log', 'admin', '2025-05-26 17:20:06', '', NULL, '日志管理菜单'); +INSERT INTO `sys_menu` VALUES (109, '在线用户', 2, 1, 'online', 'monitor/online/index', '', '', 1, 0, 'C', '0', '0', 'monitor:online:list', 'online', 'admin', '2025-05-26 17:20:06', '', NULL, '在线用户菜单'); +INSERT INTO `sys_menu` VALUES (110, '定时任务', 2, 2, 'job', 'monitor/job/index', '', '', 1, 0, 'C', '0', '0', 'monitor:job:list', 'job', 'admin', '2025-05-26 17:20:06', '', NULL, '定时任务菜单'); +INSERT INTO `sys_menu` VALUES (111, '数据监控', 2, 3, 'druid', 'monitor/druid/index', '', '', 1, 0, 'C', '0', '0', 'monitor:druid:list', 'druid', 'admin', '2025-05-26 17:20:06', '', NULL, '数据监控菜单'); +INSERT INTO `sys_menu` VALUES (112, '服务监控', 2, 4, 'server', 'monitor/server/index', '', '', 1, 0, 'C', '0', '0', 'monitor:server:list', 'server', 'admin', '2025-05-26 17:20:06', '', NULL, '服务监控菜单'); +INSERT INTO `sys_menu` VALUES (113, '缓存监控', 2, 5, 'cache', 'monitor/cache/index', '', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis', 'admin', '2025-05-26 17:20:06', '', NULL, '缓存监控菜单'); +INSERT INTO `sys_menu` VALUES (114, '缓存列表', 2, 6, 'cacheList', 'monitor/cache/list', '', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis-list', 'admin', '2025-05-26 17:20:06', '', NULL, '缓存列表菜单'); +INSERT INTO `sys_menu` VALUES (115, '表单构建', 3, 1, 'build', 'tool/build/index', '', '', 1, 0, 'C', '0', '0', 'tool:build:list', 'build', 'admin', '2025-05-26 17:20:06', '', NULL, '表单构建菜单'); +INSERT INTO `sys_menu` VALUES (116, '代码生成', 3, 2, 'gen', 'tool/gen/index', '', '', 1, 0, 'C', '0', '0', 'tool:gen:list', 'code', 'admin', '2025-05-26 18:42:26', '', NULL, '代码生成菜单'); +INSERT INTO `sys_menu` VALUES (117, '系统接口', 3, 3, 'swagger', 'tool/swagger/index', '', '', 1, 0, 'C', '0', '0', 'tool:swagger:list', 'swagger', 'admin', '2025-05-26 17:20:06', '', NULL, '系统接口菜单'); +INSERT INTO `sys_menu` VALUES (500, '操作日志', 108, 1, 'operlog', 'monitor/operlog/index', '', '', 1, 0, 'C', '0', '0', 'monitor:operlog:list', 'form', 'admin', '2025-05-26 17:20:06', '', NULL, '操作日志菜单'); +INSERT INTO `sys_menu` VALUES (501, '登录日志', 108, 2, 'logininfor', 'monitor/logininfor/index', '', '', 1, 0, 'C', '0', '0', 'monitor:logininfor:list', 'logininfor', 'admin', '2025-05-26 17:20:06', '', NULL, '登录日志菜单'); +INSERT INTO `sys_menu` VALUES (1000, '用户查询', 100, 1, '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:query', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1001, '用户新增', 100, 2, '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:add', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1002, '用户修改', 100, 3, '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:edit', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1003, '用户删除', 100, 4, '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:remove', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1004, '用户导出', 100, 5, '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:export', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1005, '用户导入', 100, 6, '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:import', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1006, '重置密码', 100, 7, '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:resetPwd', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1007, '角色查询', 101, 1, '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:query', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1008, '角色新增', 101, 2, '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:add', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1009, '角色修改', 101, 3, '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:edit', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1010, '角色删除', 101, 4, '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:remove', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1011, '角色导出', 101, 5, '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:export', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1012, '菜单查询', 102, 1, '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:query', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1013, '菜单新增', 102, 2, '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:add', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1014, '菜单修改', 102, 3, '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:edit', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1015, '菜单删除', 102, 4, '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:remove', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1016, '部门查询', 103, 1, '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:query', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1017, '部门新增', 103, 2, '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:add', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1018, '部门修改', 103, 3, '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:edit', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1019, '部门删除', 103, 4, '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:remove', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1020, '岗位查询', 104, 1, '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:query', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1021, '岗位新增', 104, 2, '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:add', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1022, '岗位修改', 104, 3, '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:edit', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1023, '岗位删除', 104, 4, '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:remove', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1024, '岗位导出', 104, 5, '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:export', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1025, '字典查询', 105, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:query', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1026, '字典新增', 105, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:add', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1027, '字典修改', 105, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:edit', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1028, '字典删除', 105, 4, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:remove', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1029, '字典导出', 105, 5, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:export', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1030, '参数查询', 106, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:query', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1031, '参数新增', 106, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:add', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1032, '参数修改', 106, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:edit', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1033, '参数删除', 106, 4, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:remove', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1034, '参数导出', 106, 5, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:export', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1035, '公告查询', 107, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:query', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1036, '公告新增', 107, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:add', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1037, '公告修改', 107, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:edit', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1038, '公告删除', 107, 4, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:remove', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1039, '操作查询', 500, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:query', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1040, '操作删除', 500, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:remove', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1041, '日志导出', 500, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:export', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1042, '登录查询', 501, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:query', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1043, '登录删除', 501, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:remove', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1044, '日志导出', 501, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:export', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1045, '账户解锁', 501, 4, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:unlock', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1046, '在线查询', 109, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:query', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1047, '批量强退', 109, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:batchLogout', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1048, '单条强退', 109, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:forceLogout', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1049, '任务查询', 110, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:query', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1050, '任务新增', 110, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:add', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1051, '任务修改', 110, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:edit', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1052, '任务删除', 110, 4, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:remove', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1053, '状态修改', 110, 5, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:changeStatus', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1054, '任务导出', 110, 6, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:export', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1055, '生成查询', 116, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:query', '#', 'admin', '2025-05-26 18:42:26', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1056, '生成修改', 116, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:edit', '#', 'admin', '2025-05-26 18:42:26', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1057, '生成删除', 116, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:remove', '#', 'admin', '2025-05-26 18:42:26', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1058, '导入代码', 116, 4, '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:import', '#', 'admin', '2025-05-26 18:42:26', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1059, '预览代码', 116, 5, '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:preview', '#', 'admin', '2025-05-26 18:42:26', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1060, '生成代码', 116, 6, '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:code', '#', 'admin', '2025-05-26 18:42:26', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2000, '支付管理', 0, 4, 'pay', NULL, NULL, '', 1, 0, 'M', '1', '1', '', 'money', 'admin', '2024-02-15 22:40:23', 'admin', '2025-05-28 18:37:31', ''); +INSERT INTO `sys_menu` VALUES (2001, '订单', 2000, 1, 'order', 'pay/order/index', NULL, '', 1, 0, 'C', '0', '0', 'pay:order:list', '#', 'admin', '2025-05-26 18:42:52', '', NULL, '订单菜单'); +INSERT INTO `sys_menu` VALUES (2002, '订单查询', 2001, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'pay:order:query', '#', 'admin', '2025-05-26 18:42:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2003, '订单新增', 2001, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'pay:order:add', '#', 'admin', '2025-05-26 18:42:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2004, '订单修改', 2001, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'pay:order:edit', '#', 'admin', '2025-05-26 18:42:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2005, '订单删除', 2001, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'pay:order:remove', '#', 'admin', '2025-05-26 18:42:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2006, '订单导出', 2001, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'pay:order:export', '#', 'admin', '2025-05-26 18:42:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2007, '发票', 2000, 1, 'invoice', 'pay/invoice/index', NULL, '', 1, 0, 'C', '0', '0', 'pay:invoice:list', '#', 'admin', '2025-05-26 18:42:52', '', NULL, '发票菜单'); +INSERT INTO `sys_menu` VALUES (2008, '发票查询', 2007, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'pay:invoice:query', '#', 'admin', '2025-05-26 18:42:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2009, '发票新增', 2007, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'pay:invoice:add', '#', 'admin', '2025-05-26 18:42:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2010, '发票修改', 2007, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'pay:invoice:edit', '#', 'admin', '2025-05-26 18:42:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2011, '发票删除', 2007, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'pay:invoice:remove', '#', 'admin', '2025-05-26 18:42:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2012, '发票导出', 2007, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'pay:invoice:export', '#', 'admin', '2025-05-26 18:42:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2013, 'Online', 0, 5, 'onlinedev', NULL, NULL, '', 1, 0, 'M', '1', '1', '', 'international', 'admin', '2024-03-07 19:38:34', 'admin', '2025-05-28 18:37:18', ''); +INSERT INTO `sys_menu` VALUES (2014, 'mybatis在线接口', 2013, 1, 'mb', 'online/mb/index', NULL, '', 1, 0, 'C', '0', '0', 'online:mb:list', 'code', 'admin', '2025-05-26 18:42:57', '', NULL, 'mybatis在线接口菜单'); +INSERT INTO `sys_menu` VALUES (2015, '数据库', 2013, 1, 'db', 'online/db/index', NULL, '', 1, 0, 'C', '0', '0', 'admin', 'table', 'admin', '2024-03-07 19:48:24', 'admin', '2024-03-07 19:54:46', ''); +INSERT INTO `sys_menu` VALUES (2016, 'mybatis在线接口查询', 2015, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'online:mb:query', '#', 'admin', '2025-05-26 18:42:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2017, 'mybatis在线接口新增', 2015, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'online:mb:add', '#', 'admin', '2025-05-26 18:42:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2018, 'mybatis在线接口修改', 2015, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'online:mb:edit', '#', 'admin', '2025-05-26 18:42:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2019, 'mybatis在线接口删除', 2015, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'online:mb:remove', '#', 'admin', '2025-05-26 18:42:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2020, 'mybatis在线接口导出', 2015, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'online:mb:export', '#', 'admin', '2025-05-26 18:42:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2021, '消息系统', 0, 6, 'modelMessage', NULL, NULL, '', 1, 0, 'M', '1', '1', '', 'message', 'admin', '2024-12-31 11:57:29', 'admin', '2025-05-28 18:37:14', ''); +INSERT INTO `sys_menu` VALUES (2022, '消息管理', 2021, 0, 'messageSystem', 'modelMessage/messageSystem/index', NULL, '', 1, 0, 'C', '0', '0', 'modelMessage:messageSystem:list', '#', 'admin', '2024-12-21 15:00:31', 'admin', '2024-12-31 15:04:49', '消息管理菜单'); +INSERT INTO `sys_menu` VALUES (2023, '消息管理查询', 2022, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:messageSystem:query', '#', 'admin', '2024-12-21 15:00:31', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2024, '消息管理新增', 2022, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:messageSystem:add', '#', 'admin', '2024-12-21 15:00:31', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2025, '消息管理修改', 2022, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:messageSystem:edit', '#', 'admin', '2024-12-21 15:00:31', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2026, '消息管理删除', 2022, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:messageSystem:remove', '#', 'admin', '2024-12-21 15:00:31', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2027, '消息管理导出', 2022, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:messageSystem:export', '#', 'admin', '2024-12-21 15:00:31', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2028, '模版管理', 2021, 1, 'template', 'modelMessage/template/index', NULL, '', 1, 0, 'C', '0', '0', 'modelMessage:template:list', '#', 'admin', '2024-12-31 14:59:52', '', NULL, '模版管理菜单'); +INSERT INTO `sys_menu` VALUES (2029, '模版管理查询', 2028, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:template:query', '#', 'admin', '2024-12-31 14:59:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2030, '模版管理新增', 2028, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:template:add', '#', 'admin', '2024-12-31 14:59:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2031, '模版管理修改', 2028, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:template:edit', '#', 'admin', '2024-12-31 14:59:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2032, '模版管理删除', 2028, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:template:remove', '#', 'admin', '2024-12-31 14:59:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2033, '模版管理导出', 2028, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:template:export', '#', 'admin', '2024-12-31 14:59:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2034, '变量管理', 2021, 2, 'variable', 'modelMessage/variable/index', NULL, '', 1, 0, 'C', '0', '0', 'modelMessage:variable:list', '#', 'admin', '2024-12-31 15:01:50', 'admin', '2024-12-31 15:04:56', '变量管理菜单'); +INSERT INTO `sys_menu` VALUES (2035, '变量管理查询', 2034, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:variable:query', '#', 'admin', '2024-12-31 15:01:50', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2036, '变量管理新增', 2034, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:variable:add', '#', 'admin', '2024-12-31 15:01:50', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2037, '变量管理修改', 2034, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:variable:edit', '#', 'admin', '2024-12-31 15:01:50', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2038, '变量管理删除', 2034, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:variable:remove', '#', 'admin', '2024-12-31 15:01:50', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2039, '变量管理导出', 2034, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:variable:export', '#', 'admin', '2024-12-31 15:01:50', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2040, '表单管理', 0, 4, 'formManagement', NULL, NULL, '', 1, 0, 'M', '1', '1', '', 'form', 'admin', '2024-02-15 22:40:23', 'admin', '2025-05-28 18:37:27', ''); +INSERT INTO `sys_menu` VALUES (2041, '表单模板', 2040, 1, 'formtemplate', 'form/template/index', NULL, '', 1, 0, 'C', '0', '0', 'form:template:list', '#', 'admin', '2025-05-26 18:43:09', '', NULL, '表单模板菜单'); +INSERT INTO `sys_menu` VALUES (2042, '表单模板查询', 2041, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'form:template:query', '#', 'admin', '2025-05-26 18:43:09', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2043, '表单模板新增', 2041, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'form:template:add', '#', 'admin', '2025-05-26 18:43:09', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2044, '表单模板修改', 2041, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'form:template:edit', '#', 'admin', '2025-05-26 18:43:09', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2045, '表单模板删除', 2041, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'form:template:remove', '#', 'admin', '2025-05-26 18:43:09', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2046, '表单模板导出', 2041, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'form:template:export', '#', 'admin', '2025-05-26 18:43:09', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2047, '表单数据', 2040, 1, 'formdata', 'form/data/index', NULL, '', 1, 0, 'C', '0', '0', 'form:data:list', '#', 'admin', '2025-05-26 18:43:09', '', NULL, '表单数据菜单'); +INSERT INTO `sys_menu` VALUES (2048, '表单数据查询', 2047, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'form:data:query', '#', 'admin', '2025-05-26 18:43:09', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2049, '表单数据新增', 2047, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'form:data:add', '#', 'admin', '2025-05-26 18:43:09', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2050, '表单数据修改', 2047, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'form:data:edit', '#', 'admin', '2025-05-26 18:43:09', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2051, '表单数据删除', 2047, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'form:data:remove', '#', 'admin', '2025-05-26 18:43:09', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2052, '表单数据导出', 2047, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'form:data:export', '#', 'admin', '2025-05-26 18:43:09', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2053, '流程管理', 0, 6, 'flowable', NULL, NULL, NULL, 1, 0, 'M', '1', '1', '', 'cascader', 'tony', '2021-03-25 11:35:09', 'admin', '2025-05-28 18:37:09', ''); +INSERT INTO `sys_menu` VALUES (2054, '流程定义', 2053, 2, 'definition', 'flowable/definition/index', NULL, NULL, 1, 0, 'C', '0', '0', '', 'job', 'tony', '2021-03-25 13:53:55', 'admin', '2022-12-29 17:40:39', ''); +INSERT INTO `sys_menu` VALUES (2055, '任务管理', 0, 7, 'task', NULL, NULL, NULL, 1, 0, 'M', '1', '1', '', 'dict', 'tony', '2021-03-26 10:53:10', 'admin', '2025-05-28 18:37:05', ''); +INSERT INTO `sys_menu` VALUES (2056, '待办任务', 2055, 2, 'todo', 'flowable/task/todo/index', NULL, NULL, 1, 1, 'C', '0', '0', '', 'cascader', 'admin', '2021-03-26 10:55:52', 'admin', '2021-03-30 09:26:36', ''); +INSERT INTO `sys_menu` VALUES (2057, '已办任务', 2055, 3, 'finished', 'flowable/task/finished/index', NULL, NULL, 1, 1, 'C', '0', '0', '', 'time-range', 'admin', '2021-03-26 10:57:54', 'admin', '2021-03-30 09:26:50', ''); +INSERT INTO `sys_menu` VALUES (2058, '已发任务', 2055, 1, 'process', 'flowable/task/myProcess/index', NULL, NULL, 1, 1, 'C', '0', '0', '', 'guide', 'admin', '2021-03-30 09:26:23', 'admin', '2022-12-12 09:58:07', ''); +INSERT INTO `sys_menu` VALUES (2059, '新增', 2058, 1, '', NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'system:deployment:add', '#', 'admin', '2021-07-07 14:25:22', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2060, '编辑', 2058, 2, '', NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'system:deployment:edit', '#', 'admin', '2021-07-07 14:25:47', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2061, '删除', 2058, 3, '', NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'system:deployment:remove', '#', 'admin', '2021-07-07 14:26:02', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2062, '流程表达式', 2053, 3, 'expression', 'flowable/expression/index', NULL, NULL, 1, 1, 'C', '0', '0', 'system:expression:list', 'list', 'admin', '2022-12-12 17:12:19', 'admin', '2022-12-12 17:13:44', '流程达式菜单'); +INSERT INTO `sys_menu` VALUES (2063, '流程达式查询', 2062, 1, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:expression:query', '#', 'admin', '2022-12-12 17:12:19', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2064, '流程达式新增', 2062, 2, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:expression:add', '#', 'admin', '2022-12-12 17:12:19', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2065, '流程达式修改', 2062, 3, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:expression:edit', '#', 'admin', '2022-12-12 17:12:19', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2066, '流程达式删除', 2062, 4, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:expression:remove', '#', 'admin', '2022-12-12 17:12:19', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2067, '流程达式导出', 2062, 5, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:expression:export', '#', 'admin', '2022-12-12 17:12:19', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2068, '流程监听', 2053, 4, 'listener', 'flowable/listener/index', NULL, NULL, 1, 0, 'C', '0', '0', 'system:listener:list', 'monitor', 'admin', '2022-12-25 11:44:16', 'admin', '2022-12-29 08:59:21', '流程监听菜单'); +INSERT INTO `sys_menu` VALUES (2069, '流程监听查询', 2068, 1, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:listener:query', '#', 'admin', '2022-12-25 11:44:16', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2070, '流程监听新增', 2068, 2, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:listener:add', '#', 'admin', '2022-12-25 11:44:16', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2071, '流程监听修改', 2068, 3, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:listener:edit', '#', 'admin', '2022-12-25 11:44:16', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2072, '流程监听删除', 2068, 4, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:listener:remove', '#', 'admin', '2022-12-25 11:44:16', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2073, '流程监听导出', 2068, 5, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:listener:export', '#', 'admin', '2022-12-25 11:44:16', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2074, '文件管理', 0, 4, 'file', NULL, NULL, '', 1, 0, 'M', '1', '1', '', 'excel', 'admin', '2024-02-15 22:40:23', 'admin', '2025-05-28 18:37:23', ''); +INSERT INTO `sys_menu` VALUES (2075, '文件信息', 2074, 1, 'info', 'file/info/index', NULL, '', 1, 0, 'C', '0', '0', 'file:info:list', 'excel', 'admin', '2025-05-26 18:43:20', '', NULL, '文件信息菜单'); +INSERT INTO `sys_menu` VALUES (2076, '文件信息查询', 2075, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'file:info:query', '#', 'admin', '2025-05-26 18:43:20', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2077, '文件信息新增', 2075, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'file:info:add', '#', 'admin', '2025-05-26 18:43:20', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2078, '文件信息修改', 2075, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'file:info:edit', '#', 'admin', '2025-05-26 18:43:20', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2079, '文件信息删除', 2075, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'file:info:remove', '#', 'admin', '2025-05-26 18:43:20', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2080, '文件信息导出', 2075, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'file:info:export', '#', 'admin', '2025-05-26 18:43:20', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2081, '第三方认证', 1, 1, 'oauth', 'system/oauth/index', NULL, '', 1, 0, 'C', '0', '0', 'system:oauth:list', 'checkbox', 'admin', '2025-05-26 18:43:24', '', NULL, '第三方认证菜单'); +INSERT INTO `sys_menu` VALUES (2082, '第三方认证查询', 2081, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'system:oauth:query', '#', 'admin', '2025-05-26 18:43:24', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2083, '第三方认证新增', 2081, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'system:oauth:add', '#', 'admin', '2025-05-26 18:43:24', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2084, '第三方认证修改', 2081, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'system:oauth:edit', '#', 'admin', '2025-05-26 18:43:24', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2085, '第三方认证删除', 2081, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'system:oauth:remove', '#', 'admin', '2025-05-26 18:43:24', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2086, '第三方认证导出', 2081, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'system:oauth:export', '#', 'admin', '2025-05-26 18:43:24', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2097, '淮安市司法局网站', 0, 40, 'hasfj', NULL, NULL, '', 1, 0, 'M', '0', '0', '', 'peoples', 'admin', '2025-05-28 21:46:52', '', NULL, '淮安市司法局网站管理'); +INSERT INTO `sys_menu` VALUES (2099, '页面内容管理', 2097, 1, 'hasfjpages', 'hasfj/hasfjpages/index', NULL, '', 1, 0, 'C', '0', '0', 'hasfj:hasfjpages:list', 'documentation', 'admin', '2025-05-28 21:48:22', '', NULL, '司法局法律规定、典型案例、单下载菜单'); +INSERT INTO `sys_menu` VALUES (2100, '页面内容查询', 2099, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'hasfj:hasfjpages:query', '#', 'admin', '2025-05-28 21:48:22', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2101, '页面内容新增', 2099, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'hasfj:hasfjpages:add', '#', 'admin', '2025-05-28 21:48:22', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2102, '页面内容修改', 2099, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'hasfj:hasfjpages:edit', '#', 'admin', '2025-05-28 21:48:22', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2103, '页面内容删除', 2099, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'hasfj:hasfjpages:remove', '#', 'admin', '2025-05-28 21:48:22', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2104, '页面内容导出', 2099, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'hasfj:hasfjpages:export', '#', 'admin', '2025-05-28 21:48:22', '', NULL, ''); + +-- ---------------------------- +-- Table structure for sys_notice +-- ---------------------------- +DROP TABLE IF EXISTS `sys_notice`; +CREATE TABLE `sys_notice` ( + `notice_id` int NOT NULL AUTO_INCREMENT COMMENT '公告ID', + `notice_title` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '公告标题', + `notice_type` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '公告类型(1通知 2公告)', + `notice_content` longblob NULL COMMENT '公告内容', + `status` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '公告状态(0正常 1关闭)', + `create_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`notice_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '通知公告表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_notice +-- ---------------------------- +INSERT INTO `sys_notice` VALUES (1, '温馨提醒:2018-07-01 若依新版本发布啦', '2', 0xE696B0E78988E69CACE58685E5AEB9, '0', 'admin', '2025-05-26 17:20:07', '', NULL, '管理员'); +INSERT INTO `sys_notice` VALUES (2, '维护通知:2018-07-01 若依系统凌晨维护', '1', 0xE7BBB4E68AA4E58685E5AEB9, '0', 'admin', '2025-05-26 17:20:07', '', NULL, '管理员'); + +-- ---------------------------- +-- Table structure for sys_oper_log +-- ---------------------------- +DROP TABLE IF EXISTS `sys_oper_log`; +CREATE TABLE `sys_oper_log` ( + `oper_id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志主键', + `title` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '模块标题', + `business_type` int NULL DEFAULT 0 COMMENT '业务类型(0其它 1新增 2修改 3删除)', + `method` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '方法名称', + `request_method` varchar(10) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '请求方式', + `operator_type` int NULL DEFAULT 0 COMMENT '操作类别(0其它 1后台用户 2手机端用户)', + `oper_name` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '操作人员', + `dept_name` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '部门名称', + `oper_url` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '请求URL', + `oper_ip` varchar(128) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '主机地址', + `oper_location` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '操作地点', + `oper_param` varchar(2000) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '请求参数', + `json_result` varchar(2000) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '返回参数', + `status` int NULL DEFAULT 0 COMMENT '操作状态(0正常 1异常)', + `error_msg` varchar(2000) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '错误消息', + `oper_time` datetime NULL DEFAULT NULL COMMENT '操作时间', + `cost_time` bigint NULL DEFAULT 0 COMMENT '消耗时间', + PRIMARY KEY (`oper_id`) USING BTREE, + INDEX `idx_sys_oper_log_bt`(`business_type` ASC) USING BTREE, + INDEX `idx_sys_oper_log_s`(`status` ASC) USING BTREE, + INDEX `idx_sys_oper_log_ot`(`oper_time` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 231 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '操作日志记录' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_oper_log +-- ---------------------------- +INSERT INTO `sys_oper_log` VALUES (100, '用户头像', 2, 'com.boyue.web.controller.system.SysProfileController.avatar()', 'POST', 1, 'admin', '研发部门', '/system/user/profile/avatar', '127.0.0.1', '内网IP', '', '{\"msg\":\"操作成功\",\"imgUrl\":\"http://localhost:8080/profile/files/master/avatar/admin/1/20250526183841-avatar.png\",\"code\":200}', 0, NULL, '2025-05-26 18:38:41', 103); +INSERT INTO `sys_oper_log` VALUES (101, '菜单管理', 3, 'com.boyue.web.controller.system.SysMenuController.remove()', 'DELETE', 1, 'admin', '研发部门', '/system/menu/4', '127.0.0.1', '内网IP', '4', '{\"msg\":\"菜单已分配,不允许删除\",\"code\":601}', 0, NULL, '2025-05-28 18:36:42', 16); +INSERT INTO `sys_oper_log` VALUES (102, '菜单管理', 2, 'com.boyue.web.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/menu', '127.0.0.1', '内网IP', '{\"children\":[],\"createTime\":\"2025-05-26 17:20:06\",\"icon\":\"guide\",\"isCache\":\"0\",\"isFrame\":\"0\",\"menuId\":4,\"menuName\":\"若依官网\",\"menuType\":\"M\",\"orderNum\":4,\"params\":{},\"parentId\":0,\"path\":\"http://boyue.vip\",\"perms\":\"\",\"query\":\"\",\"routeName\":\"\",\"status\":\"1\",\"updateBy\":\"admin\",\"visible\":\"1\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 18:36:51', 31); +INSERT INTO `sys_oper_log` VALUES (103, '菜单管理', 3, 'com.boyue.web.controller.system.SysMenuController.remove()', 'DELETE', 1, 'admin', '研发部门', '/system/menu/4', '127.0.0.1', '内网IP', '4', '{\"msg\":\"菜单已分配,不允许删除\",\"code\":601}', 0, NULL, '2025-05-28 18:36:54', 6); +INSERT INTO `sys_oper_log` VALUES (104, '菜单管理', 2, 'com.boyue.web.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/menu', '127.0.0.1', '内网IP', '{\"children\":[],\"createTime\":\"2021-03-26 10:53:10\",\"icon\":\"dict\",\"isCache\":\"0\",\"isFrame\":\"1\",\"menuId\":2055,\"menuName\":\"任务管理\",\"menuType\":\"M\",\"orderNum\":7,\"params\":{},\"parentId\":0,\"path\":\"task\",\"perms\":\"\",\"status\":\"1\",\"updateBy\":\"admin\",\"visible\":\"1\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 18:37:05', 62); +INSERT INTO `sys_oper_log` VALUES (105, '菜单管理', 2, 'com.boyue.web.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/menu', '127.0.0.1', '内网IP', '{\"children\":[],\"createTime\":\"2021-03-25 11:35:09\",\"icon\":\"cascader\",\"isCache\":\"0\",\"isFrame\":\"1\",\"menuId\":2053,\"menuName\":\"流程管理\",\"menuType\":\"M\",\"orderNum\":6,\"params\":{},\"parentId\":0,\"path\":\"flowable\",\"perms\":\"\",\"status\":\"1\",\"updateBy\":\"admin\",\"visible\":\"1\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 18:37:09', 27); +INSERT INTO `sys_oper_log` VALUES (106, '菜单管理', 2, 'com.boyue.web.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/menu', '127.0.0.1', '内网IP', '{\"children\":[],\"createTime\":\"2024-12-31 11:57:29\",\"icon\":\"message\",\"isCache\":\"0\",\"isFrame\":\"1\",\"menuId\":2021,\"menuName\":\"消息系统\",\"menuType\":\"M\",\"orderNum\":6,\"params\":{},\"parentId\":0,\"path\":\"modelMessage\",\"perms\":\"\",\"routeName\":\"\",\"status\":\"1\",\"updateBy\":\"admin\",\"visible\":\"1\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 18:37:14', 17); +INSERT INTO `sys_oper_log` VALUES (107, '菜单管理', 2, 'com.boyue.web.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/menu', '127.0.0.1', '内网IP', '{\"children\":[],\"createTime\":\"2024-03-07 19:38:34\",\"icon\":\"international\",\"isCache\":\"0\",\"isFrame\":\"1\",\"menuId\":2013,\"menuName\":\"Online\",\"menuType\":\"M\",\"orderNum\":5,\"params\":{},\"parentId\":0,\"path\":\"onlinedev\",\"perms\":\"\",\"routeName\":\"\",\"status\":\"1\",\"updateBy\":\"admin\",\"visible\":\"1\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 18:37:18', 23); +INSERT INTO `sys_oper_log` VALUES (108, '菜单管理', 2, 'com.boyue.web.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/menu', '127.0.0.1', '内网IP', '{\"children\":[],\"createTime\":\"2024-02-15 22:40:23\",\"icon\":\"excel\",\"isCache\":\"0\",\"isFrame\":\"1\",\"menuId\":2074,\"menuName\":\"文件管理\",\"menuType\":\"M\",\"orderNum\":4,\"params\":{},\"parentId\":0,\"path\":\"file\",\"perms\":\"\",\"routeName\":\"\",\"status\":\"1\",\"updateBy\":\"admin\",\"visible\":\"1\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 18:37:23', 29); +INSERT INTO `sys_oper_log` VALUES (109, '菜单管理', 2, 'com.boyue.web.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/menu', '127.0.0.1', '内网IP', '{\"children\":[],\"createTime\":\"2024-02-15 22:40:23\",\"icon\":\"form\",\"isCache\":\"0\",\"isFrame\":\"1\",\"menuId\":2040,\"menuName\":\"表单管理\",\"menuType\":\"M\",\"orderNum\":4,\"params\":{},\"parentId\":0,\"path\":\"formManagement\",\"perms\":\"\",\"routeName\":\"\",\"status\":\"1\",\"updateBy\":\"admin\",\"visible\":\"1\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 18:37:27', 26); +INSERT INTO `sys_oper_log` VALUES (110, '菜单管理', 2, 'com.boyue.web.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/menu', '127.0.0.1', '内网IP', '{\"children\":[],\"createTime\":\"2024-02-15 22:40:23\",\"icon\":\"money\",\"isCache\":\"0\",\"isFrame\":\"1\",\"menuId\":2000,\"menuName\":\"支付管理\",\"menuType\":\"M\",\"orderNum\":4,\"params\":{},\"parentId\":0,\"path\":\"pay\",\"perms\":\"\",\"routeName\":\"\",\"status\":\"1\",\"updateBy\":\"admin\",\"visible\":\"1\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 18:37:31', 20); +INSERT INTO `sys_oper_log` VALUES (111, '代码生成', 6, 'com.boyue.generator.controller.GenController.importTableSave()', 'POST', 1, 'admin', '研发部门', '/tool/gen/importTable', '127.0.0.1', '内网IP', '{\"tables\":\"html_pages\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 18:57:49', 51); +INSERT INTO `sys_oper_log` VALUES (112, '代码生成', 2, 'com.boyue.generator.controller.GenController.synchDb()', 'GET', 1, 'admin', '研发部门', '/tool/gen/synchDb/html_pages', '127.0.0.1', '内网IP', '{}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 19:04:38', 48); +INSERT INTO `sys_oper_log` VALUES (113, '代码生成', 2, 'com.boyue.generator.controller.GenController.editSave()', 'PUT', 1, 'admin', '研发部门', '/tool/gen', '127.0.0.1', '内网IP', '{\"allGenTableColumns\":[{\"capJavaField\":\"Id\",\"columnComment\":\"序号\",\"columnId\":1,\"columnName\":\"id\",\"columnType\":\"int(11)\",\"dictType\":\"\",\"edit\":false,\"htmlType\":\"input\",\"increment\":false,\"insert\":false,\"isIncrement\":\"0\",\"isInsert\":\"0\",\"isList\":\"1\",\"isPk\":\"1\",\"isRequired\":\"0\",\"javaField\":\"id\",\"javaType\":\"Long\",\"list\":true,\"params\":{},\"pk\":true,\"query\":false,\"queryType\":\"EQ\",\"required\":false,\"sort\":1,\"superColumn\":false,\"tableId\":1,\"usableColumn\":false},{\"capJavaField\":\"FormatId\",\"columnComment\":\"访问id\",\"columnId\":2,\"columnName\":\"format_id\",\"columnType\":\"varchar(6)\",\"dictType\":\"\",\"edit\":true,\"htmlType\":\"input\",\"increment\":false,\"insert\":true,\"isEdit\":\"1\",\"isIncrement\":\"0\",\"isInsert\":\"1\",\"isList\":\"1\",\"isPk\":\"0\",\"isQuery\":\"1\",\"isRequired\":\"1\",\"javaField\":\"formatId\",\"javaType\":\"String\",\"list\":true,\"params\":{},\"pk\":false,\"query\":true,\"queryType\":\"EQ\",\"required\":true,\"sort\":2,\"superColumn\":false,\"tableId\":1,\"usableColumn\":false},{\"capJavaField\":\"Title\",\"columnComment\":\"页面标题\",\"columnId\":3,\"columnName\":\"title\",\"columnType\":\"varchar(200)\",\"dictType\":\"\",\"edit\":true,\"htmlType\":\"input\",\"increment\":false,\"insert\":true,\"isEdit\":\"1\",\"isIncrement\":\"0\",\"isInsert\":\"1\",\"isList\":\"1\",\"isPk\":\"0\",\"isQuery\":\"1\",\"isRequired\":\"1\",\"javaField\":\"title\",\"javaType\":\"String\",\"list\":true,\"params\":{},\"pk\":false,\"query\":true,\"queryType\":\"EQ\",\"required\":true,\"sort\":3,\"superColumn\":false,\"tableId\":1,\"usableColumn\":false},{\"capJavaField\":\"Content\",\"columnComment\":\"HTML内容\",\"columnId\":4,\"columnName\":\"content\",\"columnType\":\"text\",\"dictType\":\"\",\"edit\":true,\"htmlType\":\"editor\",\"increment\":false,\"insert\":true,\"isEdit\":\"1\",\"isIncrement\":\"0\",\"isInsert\":\"1\",\"isList\":\"1\",\"isPk\":\"0\",\"isQuery\":\"1\",\"isRequired\":\"1\",\"javaField\":\"content\",\"javaType\":\"String\",\"list\":true,\"params\":{},\"pk\":false,\"query\":true,\"queryType\":\"EQ\",\"required\":true,\"sort\":4,\"superColumn\":false,\"tableId\":1,\"usableColumn\":false},{\"capJavaField\":\"PageType\",\"columnComment\":\"页面类型:法律规定 law、典型案例 case、表单下载from\",\"columnId\":5,\"columnName\":\"page_type\",\"columnTyp', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 19:06:45', 105); +INSERT INTO `sys_oper_log` VALUES (114, '代码生成', 8, 'com.boyue.generator.controller.GenController.batchGenCode()', 'GET', 1, 'admin', '研发部门', '/tool/gen/batchGenCode', '127.0.0.1', '内网IP', '{\"tables\":\"html_pages\"}', NULL, 0, NULL, '2025-05-28 19:06:51', 29); +INSERT INTO `sys_oper_log` VALUES (115, '菜单管理', 1, 'com.boyue.web.controller.system.SysMenuController.add()', 'POST', 1, 'admin', '研发部门', '/system/menu', '127.0.0.1', '内网IP', '{\"children\":[],\"createBy\":\"admin\",\"isCache\":\"0\",\"isFrame\":\"1\",\"menuName\":\"司法局\",\"menuType\":\"M\",\"orderNum\":4,\"params\":{},\"parentId\":0,\"path\":\"hasfj\",\"status\":\"0\",\"visible\":\"0\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 19:08:03', 28); +INSERT INTO `sys_oper_log` VALUES (116, '代码生成', 2, 'com.boyue.generator.controller.GenController.editSave()', 'PUT', 1, 'admin', '研发部门', '/tool/gen', '127.0.0.1', '内网IP', '{\"allGenTableColumns\":[{\"capJavaField\":\"Id\",\"columnComment\":\"序号\",\"columnId\":1,\"columnName\":\"id\",\"columnType\":\"int(11)\",\"dictType\":\"\",\"edit\":false,\"htmlType\":\"input\",\"increment\":false,\"insert\":false,\"isIncrement\":\"0\",\"isInsert\":\"0\",\"isList\":\"1\",\"isPk\":\"1\",\"isRequired\":\"0\",\"javaField\":\"id\",\"javaType\":\"Long\",\"list\":true,\"params\":{},\"pk\":true,\"query\":false,\"queryType\":\"EQ\",\"required\":false,\"sort\":1,\"superColumn\":false,\"tableId\":1,\"usableColumn\":false},{\"capJavaField\":\"FormatId\",\"columnComment\":\"访问id\",\"columnId\":2,\"columnName\":\"format_id\",\"columnType\":\"varchar(6)\",\"dictType\":\"\",\"edit\":true,\"htmlType\":\"input\",\"increment\":false,\"insert\":true,\"isEdit\":\"1\",\"isIncrement\":\"0\",\"isInsert\":\"1\",\"isList\":\"1\",\"isPk\":\"0\",\"isQuery\":\"1\",\"isRequired\":\"1\",\"javaField\":\"formatId\",\"javaType\":\"String\",\"list\":true,\"params\":{},\"pk\":false,\"query\":true,\"queryType\":\"EQ\",\"required\":true,\"sort\":2,\"superColumn\":false,\"tableId\":1,\"usableColumn\":false},{\"capJavaField\":\"Title\",\"columnComment\":\"页面标题\",\"columnId\":3,\"columnName\":\"title\",\"columnType\":\"varchar(200)\",\"dictType\":\"\",\"edit\":true,\"htmlType\":\"input\",\"increment\":false,\"insert\":true,\"isEdit\":\"1\",\"isIncrement\":\"0\",\"isInsert\":\"1\",\"isList\":\"1\",\"isPk\":\"0\",\"isQuery\":\"1\",\"isRequired\":\"1\",\"javaField\":\"title\",\"javaType\":\"String\",\"list\":true,\"params\":{},\"pk\":false,\"query\":true,\"queryType\":\"EQ\",\"required\":true,\"sort\":3,\"superColumn\":false,\"tableId\":1,\"usableColumn\":false},{\"capJavaField\":\"Content\",\"columnComment\":\"HTML内容\",\"columnId\":4,\"columnName\":\"content\",\"columnType\":\"text\",\"dictType\":\"\",\"edit\":true,\"htmlType\":\"editor\",\"increment\":false,\"insert\":true,\"isEdit\":\"1\",\"isIncrement\":\"0\",\"isInsert\":\"1\",\"isList\":\"1\",\"isPk\":\"0\",\"isQuery\":\"1\",\"isRequired\":\"1\",\"javaField\":\"content\",\"javaType\":\"String\",\"list\":true,\"params\":{},\"pk\":false,\"query\":true,\"queryType\":\"EQ\",\"required\":true,\"sort\":4,\"superColumn\":false,\"tableId\":1,\"usableColumn\":false},{\"capJavaField\":\"PageType\",\"columnComment\":\"页面类型:法律规定 law、典型案例 case、表单下载from\",\"columnId\":5,\"columnName\":\"page_type\",\"columnTyp', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 19:09:02', 55); +INSERT INTO `sys_oper_log` VALUES (117, '代码生成', 8, 'com.boyue.generator.controller.GenController.batchGenCode()', 'GET', 1, 'admin', '研发部门', '/tool/gen/batchGenCode', '127.0.0.1', '内网IP', '{\"tables\":\"html_pages\"}', NULL, 0, NULL, '2025-05-28 19:09:04', 24); +INSERT INTO `sys_oper_log` VALUES (118, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"content\":\"
      表单下载内容...
      \",\"createTime\":\"2025-05-28 18:55:57\",\"formatId\":\"000001\",\"id\":7,\"pageType\":\"from\",\"pageUrl\":\"http://localhost/table.html?Id=000001\",\"params\":{},\"sortOrder\":30,\"status\":1,\"title\":\"劳动仲裁申请表\",\"updateTime\":\"2025-05-28 20:23:58\",\"viewCount\":0}', NULL, 1, '\r\n### Error updating database. Cause: java.sql.SQLException: Data truncated for column \'page_type\' at row 1\r\n### The error may exist in file [E:\\WORK\\boyue-kfcode\\boyue-java\\boyue-models\\boyue-hasfj\\target\\classes\\mapper\\hasfj\\HtmlPagesMapper.xml]\r\n### The error may involve com.boyue.hasfj.mapper.HtmlPagesMapper.updateHtmlPages-Inline\r\n### The error occurred while setting parameters\r\n### SQL: update html_pages SET format_id = ?, title = ?, content = ?, page_type = ?, page_url = ?, create_time = ?, update_time = ?, status = ?, sort_order = ?, view_count = ? where id = ?\r\n### Cause: java.sql.SQLException: Data truncated for column \'page_type\' at row 1\n; Data truncated for column \'page_type\' at row 1', '2025-05-28 20:23:58', 144); +INSERT INTO `sys_oper_log` VALUES (119, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"content\":\"
      劳动法第四十条内容及解读...
      \",\"createTime\":\"2025-05-28 18:55:57\",\"formatId\":\"000001\",\"id\":1,\"pageType\":\"law\",\"pageUrl\":\"http://localhost/show.html?Id=000001\",\"params\":{},\"sortOrder\":10,\"status\":1,\"title\":\"劳动法第四十条解读\",\"updateTime\":\"2025-05-28 20:53:47\",\"viewCount\":26}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 20:53:47', 12); +INSERT INTO `sys_oper_log` VALUES (120, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"content\":\"
      表单下载内容...
      \",\"createTime\":\"2025-05-28 18:55:57\",\"formatId\":\"000002\",\"id\":8,\"pageType\":\"from\",\"pageUrl\":\"http://localhost/table.html?Id=000002\",\"params\":{},\"sortOrder\":31,\"status\":1,\"title\":\"劳动合同模板\",\"updateTime\":\"2025-05-28 20:54:06\",\"viewCount\":0}', NULL, 1, '\r\n### Error updating database. Cause: java.sql.SQLException: Data truncated for column \'page_type\' at row 1\r\n### The error may exist in file [E:\\WORK\\boyue-kfcode\\boyue-java\\boyue-models\\boyue-hasfj\\target\\classes\\mapper\\hasfj\\HtmlPagesMapper.xml]\r\n### The error may involve com.boyue.hasfj.mapper.HtmlPagesMapper.updateHtmlPages-Inline\r\n### The error occurred while setting parameters\r\n### SQL: update html_pages SET format_id = ?, title = ?, content = ?, page_type = ?, page_url = ?, create_time = ?, update_time = ?, status = ?, sort_order = ?, view_count = ? where id = ?\r\n### Cause: java.sql.SQLException: Data truncated for column \'page_type\' at row 1\n; Data truncated for column \'page_type\' at row 1', '2025-05-28 20:54:06', 5); +INSERT INTO `sys_oper_log` VALUES (121, '菜单管理', 2, 'com.boyue.web.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/menu', '127.0.0.1', '内网IP', '{\"children\":[],\"createTime\":\"2025-05-28 19:08:03\",\"icon\":\"#\",\"isCache\":\"0\",\"isFrame\":\"1\",\"menuId\":2087,\"menuName\":\"司法局百问百答\",\"menuType\":\"M\",\"orderNum\":4,\"params\":{},\"parentId\":0,\"path\":\"hasfj\",\"perms\":\"\",\"routeName\":\"\",\"status\":\"0\",\"updateBy\":\"admin\",\"visible\":\"0\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 21:19:06', 26); +INSERT INTO `sys_oper_log` VALUES (122, '菜单管理', 2, 'com.boyue.web.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/menu', '127.0.0.1', '内网IP', '{\"children\":[],\"createTime\":\"2025-05-28 19:08:03\",\"icon\":\"example\",\"isCache\":\"0\",\"isFrame\":\"1\",\"menuId\":2087,\"menuName\":\"司法局百问百答\",\"menuType\":\"M\",\"orderNum\":4,\"params\":{},\"parentId\":0,\"path\":\"hasfj\",\"perms\":\"\",\"routeName\":\"\",\"status\":\"0\",\"updateBy\":\"admin\",\"visible\":\"0\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 21:19:27', 13); +INSERT INTO `sys_oper_log` VALUES (123, '菜单管理', 2, 'com.boyue.web.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/menu', '127.0.0.1', '内网IP', '{\"children\":[],\"component\":\"hasfj/hasfjpages/index\",\"createTime\":\"2025-05-28 19:09:34\",\"icon\":\"#\",\"isCache\":\"0\",\"isFrame\":\"1\",\"menuId\":2088,\"menuName\":\"司法局百问百答内容配置\",\"menuType\":\"C\",\"orderNum\":1,\"params\":{},\"parentId\":2087,\"path\":\"hasfjpages\",\"perms\":\"hasfj:hasfjpages:list\",\"routeName\":\"\",\"status\":\"0\",\"updateBy\":\"admin\",\"visible\":\"0\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 21:21:31', 100); +INSERT INTO `sys_oper_log` VALUES (124, '菜单管理', 2, 'com.boyue.web.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/menu', '127.0.0.1', '内网IP', '{\"children\":[],\"component\":\"hasfj/hasfjpages/index\",\"createTime\":\"2025-05-28 19:09:34\",\"icon\":\"#\",\"isCache\":\"0\",\"isFrame\":\"1\",\"menuId\":2088,\"menuName\":\"内容配置\",\"menuType\":\"C\",\"orderNum\":1,\"params\":{},\"parentId\":2087,\"path\":\"hasfjpages\",\"perms\":\"hasfj:hasfjpages:list\",\"routeName\":\"\",\"status\":\"0\",\"updateBy\":\"admin\",\"visible\":\"0\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 21:22:07', 26); +INSERT INTO `sys_oper_log` VALUES (125, '菜单管理', 2, 'com.boyue.web.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/menu', '127.0.0.1', '内网IP', '{\"children\":[],\"component\":\"hasfj/hasfjpages/index\",\"createTime\":\"2025-05-28 19:09:34\",\"icon\":\"edit\",\"isCache\":\"0\",\"isFrame\":\"1\",\"menuId\":2088,\"menuName\":\"内容配置\",\"menuType\":\"C\",\"orderNum\":1,\"params\":{},\"parentId\":2087,\"path\":\"hasfjpages\",\"perms\":\"hasfj:hasfjpages:list\",\"routeName\":\"\",\"status\":\"0\",\"updateBy\":\"admin\",\"visible\":\"0\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 21:22:15', 10); +INSERT INTO `sys_oper_log` VALUES (126, '司法局法律规定、典型案例、单下载', 1, 'com.boyue.hasfj.controller.HtmlPagesController.add()', 'POST', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"content\":\"

      000004

      \",\"createTime\":\"2025-05-28 21:31:01\",\"formatId\":\"000004\",\"pageType\":\"law\",\"pageUrl\":\"000004\",\"params\":{},\"sortOrder\":10,\"status\":1,\"title\":\"000004\",\"viewCount\":0}', NULL, 1, '\r\n### Error updating database. Cause: java.sql.SQLException: Field \'update_time\' doesn\'t have a default value\r\n### The error may exist in file [E:\\WORK\\boyue-kfcode\\boyue-java\\boyue-models\\boyue-hasfj\\target\\classes\\mapper\\hasfj\\HtmlPagesMapper.xml]\r\n### The error may involve com.boyue.hasfj.mapper.HtmlPagesMapper.insertHtmlPages-Inline\r\n### The error occurred while setting parameters\r\n### SQL: insert into html_pages ( format_id, title, content, page_type, page_url, create_time, status, sort_order, view_count ) values ( ?, ?, ?, ?, ?, ?, ?, ?, ? )\r\n### Cause: java.sql.SQLException: Field \'update_time\' doesn\'t have a default value\n; Field \'update_time\' doesn\'t have a default value', '2025-05-28 21:31:01', 4); +INSERT INTO `sys_oper_log` VALUES (127, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"1\",\"content\":\"
      劳动法第四十条内容及解读...
      \",\"createTime\":\"2025-05-28 18:55:57\",\"formatId\":\"000001\",\"id\":1,\"pageType\":\"law\",\"pageUrl\":\"http://localhost/show.html?Id=000001\",\"params\":{},\"sortOrder\":10,\"status\":1,\"title\":\"劳动法第四十条解读\",\"updateTime\":\"2025-05-28 21:32:08\",\"viewCount\":35}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 21:32:08', 7); +INSERT INTO `sys_oper_log` VALUES (128, '菜单管理', 3, 'com.boyue.web.controller.system.SysMenuController.remove()', 'DELETE', 1, 'admin', '研发部门', '/system/menu/2098', '127.0.0.1', '内网IP', '2098', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 21:48:55', 128); +INSERT INTO `sys_oper_log` VALUES (129, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 21:43:55\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-28 21:49:48\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 21:49:48', 122); +INSERT INTO `sys_oper_log` VALUES (130, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"案例分析组\",\"content\":\"

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      \",\"createTime\":\"2025-05-28 21:43:55\",\"description\":\"典型劳资纠纷案例分析及处理结果\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳资纠纷,加班费,劳动仲裁\",\"pageType\":\"case\",\"pageUrl\":\"/showcase.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"某公司劳资纠纷典型案例\",\"updateTime\":\"2025-05-28 21:50:00\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 21:50:00', 52); +INSERT INTO `sys_oper_log` VALUES (131, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 21:43:55\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":4,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动仲裁申请表\",\"updateTime\":\"2025-05-28 21:50:07\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 21:50:07', 18); +INSERT INTO `sys_oper_log` VALUES (132, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"法律法规编辑部\",\"content\":\"

      合同法关于订立合同的规定

      合同是平等主体的自然人、法人、其他组织之间设立、变更、终止民事权利义务关系的协议。婚姻、收养、监护等有关身份关系的协议,适用其他法律的规定。

      \",\"createTime\":\"2025-05-28 21:43:55\",\"description\":\"合同法对于订立合同的基本规定和解释\",\"formatId\":\"000002\",\"id\":2,\"keywords\":\"合同法,合同订立,民事权利\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000002\",\"params\":{},\"sortOrder\":2,\"status\":1,\"title\":\"合同法关于订立合同的规定\",\"updateTime\":\"2025-05-28 21:50:19\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 21:50:19', 84); +INSERT INTO `sys_oper_log` VALUES (133, '司法局法律规定、典型案例、单下载', 5, 'com.boyue.hasfj.controller.HtmlPagesController.export()', 'POST', 1, 'admin', '研发部门', '/hasfj/hasfjpages/export', '127.0.0.1', '内网IP', '{\"pageSize\":\"10\",\"pageNum\":\"1\"}', NULL, 0, NULL, '2025-05-28 22:14:32', 617); +INSERT INTO `sys_oper_log` VALUES (134, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-28 22:19:39\",\"viewCount\":3}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 22:19:39', 82); +INSERT INTO `sys_oper_log` VALUES (135, '角色管理', 1, 'com.boyue.web.controller.system.SysRoleController.add()', 'POST', 1, 'admin', '研发部门', '/system/role', '127.0.0.1', '内网IP', '{\"admin\":false,\"createBy\":\"admin\",\"deptCheckStrictly\":true,\"deptIds\":[],\"flag\":false,\"menuCheckStrictly\":true,\"menuIds\":[1,100,1000,1001,1002,1003,1004,1005,1006,2097,2099,2100,2101,2102,2103,2104],\"params\":{},\"roleId\":100,\"roleKey\":\"sfj\",\"roleName\":\"司法局\",\"roleSort\":3,\"status\":\"0\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:41:41', 74); +INSERT INTO `sys_oper_log` VALUES (136, '用户管理', 3, 'com.boyue.web.controller.system.SysUserController.remove()', 'DELETE', 1, 'admin', '研发部门', '/system/user/2', '127.0.0.1', '内网IP', '[2]', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:41:50', 49); +INSERT INTO `sys_oper_log` VALUES (137, '部门管理', 2, 'com.boyue.web.controller.system.SysDeptController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/dept', '127.0.0.1', '内网IP', '{\"ancestors\":\"0\",\"children\":[],\"deptId\":100,\"deptName\":\"博越科技\",\"email\":\"boyue@qq.com\",\"leader\":\"博越\",\"orderNum\":0,\"params\":{},\"parentId\":0,\"phone\":\"15888888888\",\"status\":\"0\",\"updateBy\":\"admin\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:42:28', 120); +INSERT INTO `sys_oper_log` VALUES (138, '部门管理', 3, 'com.boyue.web.controller.system.SysDeptController.remove()', 'DELETE', 1, 'admin', '研发部门', '/system/dept/102', '127.0.0.1', '内网IP', '102', '{\"msg\":\"存在下级部门,不允许删除\",\"code\":601}', 0, NULL, '2025-05-29 08:42:32', 4); +INSERT INTO `sys_oper_log` VALUES (139, '部门管理', 3, 'com.boyue.web.controller.system.SysDeptController.remove()', 'DELETE', 1, 'admin', '研发部门', '/system/dept/109', '127.0.0.1', '内网IP', '109', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:42:34', 25); +INSERT INTO `sys_oper_log` VALUES (140, '部门管理', 3, 'com.boyue.web.controller.system.SysDeptController.remove()', 'DELETE', 1, 'admin', '研发部门', '/system/dept/108', '127.0.0.1', '内网IP', '108', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:42:36', 91); +INSERT INTO `sys_oper_log` VALUES (141, '部门管理', 3, 'com.boyue.web.controller.system.SysDeptController.remove()', 'DELETE', 1, 'admin', '研发部门', '/system/dept/102', '127.0.0.1', '内网IP', '102', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:42:38', 22); +INSERT INTO `sys_oper_log` VALUES (142, '部门管理', 3, 'com.boyue.web.controller.system.SysDeptController.remove()', 'DELETE', 1, 'admin', '研发部门', '/system/dept/107', '127.0.0.1', '内网IP', '107', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:42:43', 29); +INSERT INTO `sys_oper_log` VALUES (143, '部门管理', 3, 'com.boyue.web.controller.system.SysDeptController.remove()', 'DELETE', 1, 'admin', '研发部门', '/system/dept/106', '127.0.0.1', '内网IP', '106', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:42:44', 30); +INSERT INTO `sys_oper_log` VALUES (144, '部门管理', 2, 'com.boyue.web.controller.system.SysDeptController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/dept', '127.0.0.1', '内网IP', '{\"ancestors\":\"0,100\",\"children\":[],\"deptId\":101,\"deptName\":\"淮安市\",\"email\":\"boyue@qq.com\",\"leader\":\"博越\",\"orderNum\":1,\"params\":{},\"parentId\":100,\"parentName\":\"博越科技\",\"phone\":\"15888888888\",\"status\":\"0\",\"updateBy\":\"admin\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:42:59', 76); +INSERT INTO `sys_oper_log` VALUES (145, '部门管理', 3, 'com.boyue.web.controller.system.SysDeptController.remove()', 'DELETE', 1, 'admin', '研发部门', '/system/dept/105', '127.0.0.1', '内网IP', '105', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:43:02', 16); +INSERT INTO `sys_oper_log` VALUES (146, '部门管理', 3, 'com.boyue.web.controller.system.SysDeptController.remove()', 'DELETE', 1, 'admin', '研发部门', '/system/dept/104', '127.0.0.1', '内网IP', '104', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:43:04', 20); +INSERT INTO `sys_oper_log` VALUES (147, '部门管理', 2, 'com.boyue.web.controller.system.SysDeptController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/dept', '127.0.0.1', '内网IP', '{\"ancestors\":\"0,100,101\",\"children\":[],\"deptId\":103,\"deptName\":\"司法局部门\",\"email\":\"sfj@qq.com\",\"leader\":\"司法局\",\"orderNum\":1,\"params\":{},\"parentId\":101,\"parentName\":\"淮安市\",\"phone\":\"15888888888\",\"status\":\"0\",\"updateBy\":\"admin\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:43:41', 130); +INSERT INTO `sys_oper_log` VALUES (148, '部门管理', 2, 'com.boyue.web.controller.system.SysDeptController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/dept', '127.0.0.1', '内网IP', '{\"ancestors\":\"0,100,101\",\"children\":[],\"deptId\":103,\"deptName\":\"司法局部门\",\"email\":\"sfj@qq.com\",\"leader\":\"司法局\",\"orderNum\":2,\"params\":{},\"parentId\":101,\"parentName\":\"淮安市\",\"phone\":\"15888888888\",\"status\":\"0\",\"updateBy\":\"admin\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:43:46', 29); +INSERT INTO `sys_oper_log` VALUES (149, '部门管理', 2, 'com.boyue.web.controller.system.SysDeptController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/dept', '127.0.0.1', '内网IP', '{\"ancestors\":\"0,100,101\",\"children\":[],\"deptId\":103,\"deptName\":\"管理\",\"email\":\"sfj@qq.com\",\"leader\":\"管理\",\"orderNum\":2,\"params\":{},\"parentId\":101,\"parentName\":\"淮安市\",\"phone\":\"15888888888\",\"status\":\"0\",\"updateBy\":\"admin\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:44:34', 66); +INSERT INTO `sys_oper_log` VALUES (150, '部门管理', 1, 'com.boyue.web.controller.system.SysDeptController.add()', 'POST', 1, 'admin', '研发部门', '/system/dept', '127.0.0.1', '内网IP', '{\"ancestors\":\"0,100,101\",\"children\":[],\"createBy\":\"admin\",\"deptName\":\"司法局\",\"orderNum\":3,\"params\":{},\"parentId\":101,\"status\":\"0\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:44:51', 109); +INSERT INTO `sys_oper_log` VALUES (151, '用户管理', 1, 'com.boyue.web.controller.system.SysUserController.add()', 'POST', 1, 'admin', '研发部门', '/system/user', '127.0.0.1', '内网IP', '{\"admin\":false,\"createBy\":\"admin\",\"deptId\":103,\"nickName\":\"淮安市司法局\",\"params\":{},\"postIds\":[],\"roleIds\":[100],\"status\":\"0\",\"userId\":100,\"userName\":\"hasfj\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:45:27', 97); +INSERT INTO `sys_oper_log` VALUES (152, '用户管理', 2, 'com.boyue.web.controller.system.SysUserController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/user', '127.0.0.1', '内网IP', '{\"admin\":false,\"avatar\":\"\",\"createBy\":\"admin\",\"createTime\":\"2025-05-29 08:45:27\",\"delFlag\":\"0\",\"dept\":{\"ancestors\":\"0,100,101\",\"children\":[],\"deptId\":103,\"deptName\":\"管理\",\"leader\":\"管理\",\"orderNum\":2,\"params\":{},\"parentId\":101,\"status\":\"0\"},\"deptId\":200,\"email\":\"\",\"loginIp\":\"\",\"nickName\":\"淮安市司法局\",\"params\":{},\"phonenumber\":\"\",\"postIds\":[],\"roleIds\":[100],\"roles\":[{\"admin\":false,\"dataScope\":\"1\",\"deptCheckStrictly\":false,\"flag\":false,\"menuCheckStrictly\":false,\"params\":{},\"roleId\":100,\"roleKey\":\"sfj\",\"roleName\":\"司法局\",\"roleSort\":3,\"status\":\"0\"}],\"sex\":\"0\",\"status\":\"0\",\"updateBy\":\"admin\",\"userId\":100,\"userName\":\"hasfj\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:45:53', 101); +INSERT INTO `sys_oper_log` VALUES (153, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"attachmentUrl\":\"http://localhost:9799/profile/files/master/2025/05/29/20250529090531_b976.png\",\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-29 09:05:34\",\"viewCount\":6}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:05:34', 98); +INSERT INTO `sys_oper_log` VALUES (154, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"attachmentUrl\":\"http://localhost:9799/profile/files/master/2025/05/29/20250529090601_37f6.xls\",\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"劳动仲裁申请表\",\"updateTime\":\"2025-05-29 09:06:03\",\"viewCount\":2}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:06:03', 50); +INSERT INTO `sys_oper_log` VALUES (155, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"attachmentUrl\":\"http://localhost:9799/profile/files/master/2025/05/29/20250529090531_b976.png\",\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-29 09:06:36\",\"viewCount\":7}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:06:36', 33); +INSERT INTO `sys_oper_log` VALUES (156, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"attachmentUrl\":\"http://localhost:9799/profile/files/master/2025/05/29/20250529090531_b976.png\",\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-29 09:06:55\",\"viewCount\":7}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:06:56', 114); +INSERT INTO `sys_oper_log` VALUES (157, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"attachmentUrl\":\"http://localhost:9799/profile/files/master/2025/05/29/20250529090531_b976.png\",\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-29 09:09:59\",\"viewCount\":7}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:09:59', 118); +INSERT INTO `sys_oper_log` VALUES (158, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"attachmentUrl\":\"http://localhost:9799/profile/files/master/2025/05/29/20250529090531_b976.png\",\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-29 09:10:41\",\"viewCount\":7}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:10:41', 130); +INSERT INTO `sys_oper_log` VALUES (159, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-29 09:17:00\",\"viewCount\":7}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:17:00', 144); +INSERT INTO `sys_oper_log` VALUES (160, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[{\\\"name\\\":\\\"hasfjpages_1748441672046.xlsx\\\",\\\"url\\\":\\\"http://localhost:9799/profile/files/master/2025/05/29/20250529091727_6301.xlsx\\\"},{\\\"name\\\":\\\"boyue.zip\\\",\\\"url\\\":\\\"http://localhost:9799/profile/files/master/2025/05/29/20250529092056_cc9b.zip\\\"}]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-29 09:20:57\",\"viewCount\":7}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:20:58', 187); +INSERT INTO `sys_oper_log` VALUES (161, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"attachmentUrl\":\"http://localhost:9799/profile/files/master/2025/05/29/20250529090531_b976.png\",\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[{\\\"name\\\":\\\"hasfjpages_1748441672046.xlsx\\\",\\\"url\\\":\\\"http://localhost:9799/profile/files/master/2025/05/29/20250529091727_6301.xlsx\\\"}]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-29 09:21:13\",\"viewCount\":7}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:21:13', 51); +INSERT INTO `sys_oper_log` VALUES (162, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[{\\\"name\\\":\\\"hasfjpages_1748441672046.xlsx\\\",\\\"url\\\":\\\"http://localhost:9799/profile/files/master/2025/05/29/20250529091727_6301.xlsx\\\"}]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-29 09:21:22\",\"viewCount\":7}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:21:22', 44); +INSERT INTO `sys_oper_log` VALUES (163, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"attachmentUrl\":\"http://localhost:9799/profile/files/master/2025/05/29/20250529090531_b976.png\",\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[{\\\"name\\\":\\\"hasfjpages_1748441672046.xlsx\\\",\\\"url\\\":\\\"http://localhost:9799/profile/files/master/2025/05/29/20250529091727_6301.xlsx\\\"},{\\\"name\\\":\\\"boyue.zip\\\",\\\"url\\\":\\\"http://localhost:9799/profile/files/master/2025/05/29/20250529092056_cc9b.zip\\\"}]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-29 09:23:39\",\"viewCount\":7}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:23:39', 122); +INSERT INTO `sys_oper_log` VALUES (164, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"attachmentUrl\":\"\",\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-29 09:24:17\",\"viewCount\":7}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:24:17', 91); +INSERT INTO `sys_oper_log` VALUES (165, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"hasfjpages_1748441672046.xlsx\\\",\\\"url\\\":\\\"http://localhost:9799/profile/files/master/2025/05/29/20250529091727_6301.xlsx\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"劳动仲裁申请表\",\"updateTime\":\"2025-05-29 09:28:48\",\"viewCount\":4}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:28:48', 31); +INSERT INTO `sys_oper_log` VALUES (166, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"hasfjpages_1748441672046.xlsx\\\",\\\"url\\\":\\\"http://localhost:9799/profile/files/master/2025/05/29/20250529091727_6301.xlsx\\\"},{\\\"name\\\":\\\"boyue.zip\\\",\\\"url\\\":\\\"http://localhost:9799/profile/files/master/2025/05/29/20250529092056_cc9b.zip\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"劳动仲裁申请表\",\"updateTime\":\"2025-05-29 09:29:02\",\"viewCount\":5}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:29:02', 95); +INSERT INTO `sys_oper_log` VALUES (167, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"劳动仲裁申请表\",\"updateTime\":\"2025-05-29 09:29:19\",\"viewCount\":6}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:29:19', 23); +INSERT INTO `sys_oper_log` VALUES (168, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"法律法规编辑部\",\"content\":\"

      测试

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-29 09:38:39\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:38:39', 64); +INSERT INTO `sys_oper_log` VALUES (169, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"boyue.zip\\\",\\\"url\\\":\\\"http://localhost:9799/profile/files/master/2025/05/29/20250529092056_cc9b.zip\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"劳动仲裁申请表\",\"updateTime\":\"2025-05-29 11:30:45\",\"viewCount\":9}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 11:30:45', 23); +INSERT INTO `sys_oper_log` VALUES (170, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"法律法规编辑部\",\"content\":\"

      《公司法》《个人独资企业法》《合伙企业法》

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-29 07:02:45\",\"viewCount\":215}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:02:45', 104); +INSERT INTO `sys_oper_log` VALUES (171, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"案例分析组\",\"content\":\"

      李某与王某成立“普通合伙企业”经营餐饮,后因亏损负债100万元,法院判令李某、王某承担无限连带责任。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"典型劳资纠纷案例分析及处理结果\",\"formatId\":\"000001\",\"id\":2,\"keywords\":\"劳资纠纷,加班费,劳动仲裁\",\"multiAttachments\":\"[]\",\"pageType\":\"case\",\"pageUrl\":\"/showcase.html?Id=000001\",\"params\":{},\"sortOrder\":2,\"status\":1,\"title\":\"某公司劳资纠纷典型案例\",\"updateTime\":\"2025-05-29 07:03:38\",\"viewCount\":4}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:03:38', 115); +INSERT INTO `sys_oper_log` VALUES (172, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"boyue.zip\\\",\\\"url\\\":\\\"http://localhost:9799/profile/files/master/2025/05/29/20250529092056_cc9b.zip\\\"},{\\\"name\\\":\\\"淮安市“七五”普法调查问卷.doc\\\",\\\"url\\\":\\\"http://hj.haxy.com.cn/profile/files/master/2025/05/29/20250529070442_e2a0.doc\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"劳动仲裁申请表\",\"updateTime\":\"2025-05-29 07:04:51\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:04:51', 60); +INSERT INTO `sys_oper_log` VALUES (173, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"法律法规编辑部\",\"content\":\"

      《公司法》《个人独资企业法》《合伙企业法》

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-29 07:07:55\",\"viewCount\":215}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:07:55', 82); +INSERT INTO `sys_oper_log` VALUES (174, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"案例分析组\",\"content\":\"

      李某与王某成立“普通合伙企业”经营餐饮,后因亏损负债100万元,法院判令李某、王某承担无限连带责任。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"典型劳资纠纷案例分析及处理结果\",\"formatId\":\"000001\",\"id\":2,\"keywords\":\"劳资纠纷,加班费,劳动仲裁\",\"multiAttachments\":\"[]\",\"pageType\":\"case\",\"pageUrl\":\"/showcase.html?Id=000001\",\"params\":{},\"sortOrder\":2,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-29 07:08:06\",\"viewCount\":4}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:08:06', 46); +INSERT INTO `sys_oper_log` VALUES (175, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"boyue.zip\\\",\\\"url\\\":\\\"http://localhost:9799/profile/files/master/2025/05/29/20250529092056_cc9b.zip\\\"},{\\\"name\\\":\\\"淮安市“七五”普法调查问卷.doc\\\",\\\"url\\\":\\\"http://hj.haxy.com.cn/profile/files/master/2025/05/29/20250529070442_e2a0.doc\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-29 07:08:16\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:08:16', 38); +INSERT INTO `sys_oper_log` VALUES (176, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000002\",\"id\":4,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000002\",\"params\":{},\"sortOrder\":4,\"status\":1,\"title\":\"有限责任公司和股份有限公司的区别是什么?\",\"updateTime\":\"2025-05-29 07:08:49\",\"viewCount\":17}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:08:49', 56); +INSERT INTO `sys_oper_log` VALUES (177, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"案例分析组\",\"content\":\"

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"典型劳资纠纷案例分析及处理结果\",\"formatId\":\"000002\",\"id\":5,\"keywords\":\"劳资纠纷,加班费,劳动仲裁\",\"multiAttachments\":\"[]\",\"pageType\":\"case\",\"pageUrl\":\"/showcase.html?Id=000002\",\"params\":{},\"sortOrder\":5,\"status\":1,\"title\":\"有限责任公司和股份有限公司的区别是什么?\",\"updateTime\":\"2025-05-29 07:08:59\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:08:59', 52); +INSERT INTO `sys_oper_log` VALUES (178, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000002\",\"id\":6,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000002\",\"params\":{},\"sortOrder\":6,\"status\":1,\"title\":\"有限责任公司和股份有限公司的区别是什么?\",\"updateTime\":\"2025-05-29 07:09:10\",\"viewCount\":1}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:09:11', 41); +INSERT INTO `sys_oper_log` VALUES (179, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000003\",\"id\":7,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000003\",\"params\":{},\"sortOrder\":7,\"status\":1,\"title\":\"如何确定企业名称?\",\"updateTime\":\"2025-05-29 07:09:30\",\"viewCount\":15}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:09:30', 36); +INSERT INTO `sys_oper_log` VALUES (180, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"案例分析组\",\"content\":\"

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"典型劳资纠纷案例分析及处理结果\",\"formatId\":\"000003\",\"id\":8,\"keywords\":\"劳资纠纷,加班费,劳动仲裁\",\"multiAttachments\":\"[]\",\"pageType\":\"case\",\"pageUrl\":\"/showcase.html?Id=000003\",\"params\":{},\"sortOrder\":8,\"status\":1,\"title\":\"如何确定企业名称?\",\"updateTime\":\"2025-05-29 07:09:40\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:09:40', 27); +INSERT INTO `sys_oper_log` VALUES (181, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000003\",\"id\":9,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000003\",\"params\":{},\"sortOrder\":9,\"status\":1,\"title\":\"如何确定企业名称?\",\"updateTime\":\"2025-05-29 07:09:50\",\"viewCount\":2}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:09:50', 17); +INSERT INTO `sys_oper_log` VALUES (182, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"法律法规编辑部\",\"content\":\"

      《证券法》第七十八条,发行人及法律、行政法规和国务院证券监督管理机构规定的其他信息披露义务人,应当及时依法履行信息披露义务。信息披露义务人披露的信息,应当真实、准确、完整,简明清晰,通俗易懂,不得有虚假记载、误导性陈述或者重大遗漏。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000002\",\"id\":4,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000002\",\"params\":{},\"sortOrder\":4,\"status\":1,\"title\":\"有限责任公司和股份有限公司的区别是什么?\",\"updateTime\":\"2025-05-29 07:11:12\",\"viewCount\":17}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:11:12', 92); +INSERT INTO `sys_oper_log` VALUES (183, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"案例分析组\",\"content\":\"

      某科技创业团队初期仅3名股东,误以为股份公司“更规范易融资”,直接注册为股份公司。因股份公司需设股东大会、董事会、监事会,团队人数不足,决策效率低下;同时需定期披露年报、重大事项,合规成本高。天使投资机构因其结构复杂、股权转让受限(需股东大会批准)放弃投资,最终公司因资金链断裂解散清算。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"典型劳资纠纷案例分析及处理结果\",\"formatId\":\"000002\",\"id\":5,\"keywords\":\"劳资纠纷,加班费,劳动仲裁\",\"multiAttachments\":\"[]\",\"pageType\":\"case\",\"pageUrl\":\"/showcase.html?Id=000002\",\"params\":{},\"sortOrder\":5,\"status\":1,\"title\":\"有限责任公司和股份有限公司的区别是什么?\",\"updateTime\":\"2025-05-29 07:11:33\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:11:33', 65); +INSERT INTO `sys_oper_log` VALUES (184, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"法律法规编辑部\",\"content\":\"

      《企业名称登记管理规定》《企业名称登记管理规定实施办法》。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000003\",\"id\":7,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000003\",\"params\":{},\"sortOrder\":7,\"status\":1,\"title\":\"如何确定企业名称?\",\"updateTime\":\"2025-05-29 07:11:54\",\"viewCount\":15}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:11:54', 41); +INSERT INTO `sys_oper_log` VALUES (185, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"案例分析组\",\"content\":\"

      甲公司注册名称为“上海特斯拉新能源科技有限公司”,未经特斯拉公司授权。法院认定其名称攀附知名品牌商誉,构成不正当竞争,判令更名并赔偿20万元。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"典型劳资纠纷案例分析及处理结果\",\"formatId\":\"000003\",\"id\":8,\"keywords\":\"劳资纠纷,加班费,劳动仲裁\",\"multiAttachments\":\"[]\",\"pageType\":\"case\",\"pageUrl\":\"/showcase.html?Id=000003\",\"params\":{},\"sortOrder\":8,\"status\":1,\"title\":\"如何确定企业名称?\",\"updateTime\":\"2025-05-29 07:12:11\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:12:11', 44); +INSERT INTO `sys_oper_log` VALUES (186, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"法律法规编辑部\",\"content\":\"

      《公司法》第十条,公司的法定代表人按照公司章程的规定,由代表公司执行公司事务的董事或者经理担任。担任法定代表人的董事或者经理辞任的,视为同时辞去法定代表人。法定代表人辞任的,公司应当在法定代表人辞任之日起三十日内确定新的法定代表人。第十一条:法定代表人以公司名义从事的民事活动,其法律后果由公司承受。公司章程或者股东会对法定代表人职权的限制,不得对抗善意相对人。法定代表人因执行职务造成他人损害的,由公司承担民事责任。公司承担民事责任后,依照法律或者公司章程的规定,可以向有过错的法定代表人追偿。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000004\",\"id\":10,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000004\",\"params\":{},\"sortOrder\":10,\"status\":1,\"title\":\"法定代表人的要求有哪些?\",\"updateTime\":\"2025-05-29 07:12:43\",\"viewCount\":7}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:12:43', 51); +INSERT INTO `sys_oper_log` VALUES (187, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"案例分析组\",\"content\":\"

      某公司法定代表人李某因个人债务被列为失信被执行人,法院限制其高消费。公司向银行申请贷款时,因李某信用瑕疵遭拒,资金链断裂导致停产。市场监管部门责令更换法定代表人,但因股东争议拖延,最终公司破产清算。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"典型劳资纠纷案例分析及处理结果\",\"formatId\":\"000004\",\"id\":11,\"keywords\":\"劳资纠纷,加班费,劳动仲裁\",\"multiAttachments\":\"[]\",\"pageType\":\"case\",\"pageUrl\":\"/showcase.html?Id=000004\",\"params\":{},\"sortOrder\":11,\"status\":1,\"title\":\"法定代表人的要求有哪些?\",\"updateTime\":\"2025-05-29 07:13:16\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:13:16', 75); +INSERT INTO `sys_oper_log` VALUES (188, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000004\",\"id\":12,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000004\",\"params\":{},\"sortOrder\":12,\"status\":1,\"title\":\"法定代表人的要求有哪些?\",\"updateTime\":\"2025-05-29 07:13:34\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:13:35', 75); +INSERT INTO `sys_oper_log` VALUES (189, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"法律法规编辑部\",\"content\":\"

      《公司法》第一百七十八条,有下列情形之一的,不得担任公司的董事、监事、高级管理人员:(一)无民事行为能力或者限制民事行为能力;(二)因贪污、贿赂、侵占财产、挪用财产或者破坏社会主义市场经济秩序,被判处刑罚,或者因犯罪被剥夺政治权利,执行期满未逾五年,被宣告缓刑的,自缓刑考验期满之日起未逾二年;(三)担任破产清算的公司、企业的董事或者厂长、经理,对该公司、企业的破产负有个人责任的,自该公司、企业破产清算完结之日起未逾三年;(四)担任因违法被吊销营业执照、责令关闭的公司、企业的法定代表人,并负有个人责任的,自该公司、企业被吊销营业执照、责令关闭之日起未逾三年;(五)个人因所负数额较大债务到期未清偿被人民法院列为失信被执行人。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000005\",\"id\":13,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000005\",\"params\":{},\"sortOrder\":13,\"status\":1,\"title\":\"董事、监事、高级管理人员的资格限制有哪些?\",\"updateTime\":\"2025-05-29 07:14:05\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:14:05', 56); +INSERT INTO `sys_oper_log` VALUES (190, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"案例分析组\",\"content\":\"

      某公司股东李某(公务员)通过亲属代持股权,后因公司债务纠纷被债权人揭发。法院认定代持协议无效,李某需退还全部股权收益,公司被处罚款10万元。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"典型劳资纠纷案例分析及处理结果\",\"formatId\":\"000005\",\"id\":14,\"keywords\":\"劳资纠纷,加班费,劳动仲裁\",\"multiAttachments\":\"[]\",\"pageType\":\"case\",\"pageUrl\":\"/showcase.html?Id=000005\",\"params\":{},\"sortOrder\":14,\"status\":1,\"title\":\"董事、监事、高级管理人员的资格限制有哪些?\",\"updateTime\":\"2025-05-29 07:14:42\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:14:42', 51); +INSERT INTO `sys_oper_log` VALUES (191, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000005\",\"id\":15,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000005\",\"params\":{},\"sortOrder\":15,\"status\":1,\"title\":\"董事、监事、高级管理人员的资格限制有哪些?\",\"updateTime\":\"2025-05-29 07:14:52\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:14:52', 39); +INSERT INTO `sys_oper_log` VALUES (192, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"法律法规编辑部\",\"content\":\"

      最高人民法院关于适用《中华人民共和国公司法》若干问题的规定(三)第二十四条,有限责任公司的实际出资人与名义出资人订立合同,约定由实际出资人出资并享有投资权益,以名义出资人为名义股东,实际出资人与名义股东对该合同效力发生争议的,如无法律规定的无效情形,人民法院应当认定该合同有效。前款规定的实际出资人与名义股东因投资权益的归属发生争议,实际出资人以其实际履行了出资义务为由向名义股东主张权利的,人民法院应予支持。名义股东以公司股东名册记载、公司登记机关登记为由否认实际出资人权利的,人民法院不予支持。实际出资人未经公司其他股东半数以上同意,请求公司变更股东、签发出资证明书、记载于股东名册、记载于公司章程并办理公司登记机关登记的,人民法院不予支持。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000006\",\"id\":16,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000006\",\"params\":{},\"sortOrder\":16,\"status\":1,\"title\":\"隐名股东有哪些常见法律风险?\",\"updateTime\":\"2025-05-29 07:15:42\",\"viewCount\":1}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:15:42', 48); +INSERT INTO `sys_oper_log` VALUES (193, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"案例分析组\",\"content\":\"

      隐名股东李某委托张某代持某公司10%股权,张某未经同意将股权转让给王某,王某不知情且支付合理价款。王某善意取得股权,李某仅能要求张某赔偿损失。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"典型劳资纠纷案例分析及处理结果\",\"formatId\":\"000006\",\"id\":17,\"keywords\":\"劳资纠纷,加班费,劳动仲裁\",\"multiAttachments\":\"[]\",\"pageType\":\"case\",\"pageUrl\":\"/showcase.html?Id=000006\",\"params\":{},\"sortOrder\":17,\"status\":1,\"title\":\"隐名股东有哪些常见法律风险?\",\"updateTime\":\"2025-05-29 07:16:04\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:16:04', 57); +INSERT INTO `sys_oper_log` VALUES (194, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000006\",\"id\":18,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000006\",\"params\":{},\"sortOrder\":18,\"status\":1,\"title\":\"隐名股东有哪些常见法律风险?\",\"updateTime\":\"2025-05-29 07:16:18\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:16:18', 72); +INSERT INTO `sys_oper_log` VALUES (195, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"法律法规编辑部\",\"content\":\"

      《公司法》第六十五条,股东会会议由股东按照出资比例行使表决权;但是,公司章程另有规定的除外。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000007\",\"id\":19,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000007\",\"params\":{},\"sortOrder\":19,\"status\":1,\"title\":\"如何保持创始人在公司的控制权?\",\"updateTime\":\"2025-05-29 07:16:51\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:16:51', 74); +INSERT INTO `sys_oper_log` VALUES (196, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"案例分析组\",\"content\":\"

      某公司创始人通过“AB股”架构(1股10票投票权),即使持股比例仅15%,仍掌控董事会80%席位。公司上市后,其投票权超80%,确保战略决策权。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"典型劳资纠纷案例分析及处理结果\",\"formatId\":\"000007\",\"id\":20,\"keywords\":\"劳资纠纷,加班费,劳动仲裁\",\"multiAttachments\":\"[]\",\"pageType\":\"case\",\"pageUrl\":\"/showcase.html?Id=000007\",\"params\":{},\"sortOrder\":20,\"status\":1,\"title\":\"如何保持创始人在公司的控制权?\",\"updateTime\":\"2025-05-29 07:17:15\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:17:15', 54); +INSERT INTO `sys_oper_log` VALUES (197, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000007\",\"id\":21,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000007\",\"params\":{},\"sortOrder\":21,\"status\":1,\"title\":\"如何保持创始人在公司的控制权?\",\"updateTime\":\"2025-05-29 07:17:28\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:17:28', 49); +INSERT INTO `sys_oper_log` VALUES (198, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"法律法规编辑部\",\"content\":\"

      \\t《公司法》第二百三十一条,公司经营管理发生严重困难,继续存续会使股东利益受到重大损失,通过其他途径不能解决的,持有公司百分之十以上表决权的股东,可以请求人民法院解散公司。

      \\t最高人民法院关于适用《中华人民共和国公司法》若干问题的规定(二)第一条,单独或者合计持有公司全部股东表决权百分之十以上的股东,以下列事由之一提起解散公司诉讼,并符合公司法第一百八十二条规定的,人民法院应予受理:(一)公司持续两年以上无法召开股东会或者股东大会,公司经营管理发生严重困难的;(二)股东表决时无法达到法定或者公司章程规定的比例,持续两年以上不能做出有效的股东会或者股东大会决议,公司经营管理发生严重困难的;(三)公司董事长期冲突,且无法通过股东会或者股东大会解决,公司经营管理发生严重困难的;


      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000008\",\"id\":22,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000008\",\"params\":{},\"sortOrder\":22,\"status\":1,\"title\":\"如何防止出现公司僵局?\",\"updateTime\":\"2025-05-29 07:17:59\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:17:59', 68); +INSERT INTO `sys_oper_log` VALUES (199, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"案例分析组\",\"content\":\"

      某零售企业两位股东各持50%股权,因经营理念严重冲突,股东会无法通过重大决策(如拓展新店、引入投资),公司现金流枯竭,员工离职,被法院裁定解散。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"典型劳资纠纷案例分析及处理结果\",\"formatId\":\"000008\",\"id\":23,\"keywords\":\"劳资纠纷,加班费,劳动仲裁\",\"multiAttachments\":\"[]\",\"pageType\":\"case\",\"pageUrl\":\"/showcase.html?Id=000008\",\"params\":{},\"sortOrder\":23,\"status\":1,\"title\":\"如何防止出现公司僵局?\",\"updateTime\":\"2025-05-29 07:18:27\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:18:27', 61); +INSERT INTO `sys_oper_log` VALUES (200, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000008\",\"id\":24,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000008\",\"params\":{},\"sortOrder\":24,\"status\":1,\"title\":\"如何防止出现公司僵局?\",\"updateTime\":\"2025-05-29 07:18:40\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:18:40', 48); +INSERT INTO `sys_oper_log` VALUES (201, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"法律法规编辑部\",\"content\":\"

      《公司法》第四条,有限责任公司的股东以其认缴的出资额为限对公司承担责任;股份有限公司的股东以其认购的股份为限对公司承担责任。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000009\",\"id\":25,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000009\",\"params\":{},\"sortOrder\":25,\"status\":1,\"title\":\"如何合理确定注册资本?\",\"updateTime\":\"2025-05-29 07:19:12\",\"viewCount\":1}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:19:12', 51); +INSERT INTO `sys_oper_log` VALUES (202, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"案例分析组\",\"content\":\"

      \\t某公司注册资本8000万元(认缴期30年),实际运营资金仅200万元。后因合同纠纷负债500万元,法院认定股东恶意设置超长认缴期,判令股东立即补缴。


      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"典型劳资纠纷案例分析及处理结果\",\"formatId\":\"000009\",\"id\":26,\"keywords\":\"劳资纠纷,加班费,劳动仲裁\",\"multiAttachments\":\"[]\",\"pageType\":\"case\",\"pageUrl\":\"/showcase.html?Id=000009\",\"params\":{},\"sortOrder\":26,\"status\":1,\"title\":\"如何合理确定注册资本?\",\"updateTime\":\"2025-05-29 07:19:32\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:19:32', 69); +INSERT INTO `sys_oper_log` VALUES (203, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000009\",\"id\":27,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000009\",\"params\":{},\"sortOrder\":27,\"status\":1,\"title\":\"如何合理确定注册资本?\",\"updateTime\":\"2025-05-29 07:19:44\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:19:44', 117); +INSERT INTO `sys_oper_log` VALUES (204, '司法局法律规定、典型案例、单下载-删除附件', 3, 'com.boyue.hasfj.controller.HtmlPagesController.deleteAttachment()', 'POST', 1, 'admin', '管理', '/hasfj/hasfjpages/deleteAttachment', '49.87.176.188', 'XX XX', '{\"attachmentUrl\":\"http://localhost:9799/profile/files/master/2025/05/29/20250529092056_cc9b.zip\"}', '{\"msg\":\"删除附件失败\",\"code\":500}', 0, NULL, '2025-05-31 12:12:52', 47); +INSERT INTO `sys_oper_log` VALUES (205, '司法局法律规定、典型案例、单下载-删除附件', 3, 'com.boyue.hasfj.controller.HtmlPagesController.deleteAttachment()', 'POST', 1, 'admin', '管理', '/hasfj/hasfjpages/deleteAttachment', '49.87.176.188', 'XX XX', '{\"attachmentUrl\":\"http://hj.haxy.com.cn/profile/files/master/2025/05/29/20250529070442_e2a0.doc\"}', '{\"msg\":\"删除附件失败\",\"code\":500}', 0, NULL, '2025-05-31 12:12:56', 0); +INSERT INTO `sys_oper_log` VALUES (206, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '49.87.176.188', 'XX XX', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"boyue.zip\\\",\\\"url\\\":\\\"http://localhost:9799/profile/files/master/2025/05/29/20250529092056_cc9b.zip\\\"},{\\\"name\\\":\\\"淮安市“七五”普法调查问卷.doc\\\",\\\"url\\\":\\\"http://hj.haxy.com.cn/profile/files/master/2025/05/29/20250529070442_e2a0.doc\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 04:12:58\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 12:12:58', 41); +INSERT INTO `sys_oper_log` VALUES (207, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '49.87.176.188', 'XX XX', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"01应某某不服市城管局行政处罚行政复议决定书.docx\\\",\\\"url\\\":\\\"http://hj.haxy.com.cn/profile/files/master/2025/05/31/20250531041536_0786.docx\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 04:15:40\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 12:15:40', 74); +INSERT INTO `sys_oper_log` VALUES (208, '司法局法律规定、典型案例、单下载-删除附件', 3, 'com.boyue.hasfj.controller.HtmlPagesController.deleteAttachment()', 'POST', 1, 'admin', '管理', '/hasfj/hasfjpages/deleteAttachment', '127.0.0.1', '内网IP', '{\"attachmentUrl\":\"http://hj.haxy.com.cn/profile/files/master/2025/05/31/20250531041536_0786.docx\"}', '{\"msg\":\"删除附件失败\",\"code\":500}', 0, NULL, '2025-05-31 14:50:57', 10); +INSERT INTO `sys_oper_log` VALUES (209, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"01应某某不服市城管局行政处罚行政复议决定书.docx\\\",\\\"url\\\":\\\"http://hj.haxy.com.cn/profile/files/master/2025/05/31/20250531041536_0786.docx\\\"},{\\\"name\\\":\\\"2025-05-28会议室预约记录 (2).xls\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531145236_2193.xls\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 14:52:38\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 14:52:39', 69); +INSERT INTO `sys_oper_log` VALUES (210, '司法局法律规定、典型案例、单下载-删除附件', 3, 'com.boyue.hasfj.controller.HtmlPagesController.deleteAttachment()', 'POST', 1, 'admin', '管理', '/hasfj/hasfjpages/deleteAttachment', '180.125.41.129', 'XX XX', '{\"attachmentUrl\":\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531145236_2193.xls\"}', '{\"msg\":\"删除附件失败\",\"code\":500}', 0, NULL, '2025-05-31 15:04:35', 19); +INSERT INTO `sys_oper_log` VALUES (211, '司法局法律规定、典型案例、单下载-删除附件', 3, 'com.boyue.hasfj.controller.HtmlPagesController.deleteAttachment()', 'POST', 1, 'admin', '管理', '/hasfj/hasfjpages/deleteAttachment', '180.125.41.129', 'XX XX', '{\"attachmentUrl\":\"http://hj.haxy.com.cn/profile/files/master/2025/05/31/20250531041536_0786.docx\"}', '{\"msg\":\"删除附件失败\",\"code\":500}', 0, NULL, '2025-05-31 15:04:37', 0); +INSERT INTO `sys_oper_log` VALUES (212, '司法局法律规定、典型案例、单下载-删除附件', 3, 'com.boyue.hasfj.controller.HtmlPagesController.deleteAttachment()', 'POST', 1, 'admin', '管理', '/hasfj/hasfjpages/deleteAttachment', '218.2.75.254', 'XX XX', '{\"attachmentUrl\":\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531145236_2193.xls\"}', '{\"msg\":\"删除附件失败\",\"code\":500}', 0, NULL, '2025-05-31 15:05:18', 0); +INSERT INTO `sys_oper_log` VALUES (213, '司法局法律规定、典型案例、单下载-删除附件', 3, 'com.boyue.hasfj.controller.HtmlPagesController.deleteAttachment()', 'POST', 1, 'admin', '管理', '/hasfj/hasfjpages/deleteAttachment', '218.2.75.254', 'XX XX', '{\"attachmentUrl\":\"http://hj.haxy.com.cn/profile/files/master/2025/05/31/20250531041536_0786.docx\"}', '{\"msg\":\"删除附件失败\",\"code\":500}', 0, NULL, '2025-05-31 15:05:20', 1); +INSERT INTO `sys_oper_log` VALUES (214, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 15:08:12\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 15:08:14', 569); +INSERT INTO `sys_oper_log` VALUES (215, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"2025创新大赛提交.zip\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531150820_1da0.zip\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 15:08:22\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 15:08:22', 38); +INSERT INTO `sys_oper_log` VALUES (216, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"boyue.zip\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531151028_e27c.zip\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 15:10:30\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 15:10:31', 73); +INSERT INTO `sys_oper_log` VALUES (217, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"boyue.zip\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531151028_e27c.zip\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 15:13:20\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 15:13:21', 301); +INSERT INTO `sys_oper_log` VALUES (218, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"2025-05-28至2025-05-28会议室预约记录 (1).xls\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531151741_e50b.xls\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 15:17:43\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 15:17:43', 109); +INSERT INTO `sys_oper_log` VALUES (219, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"boyue.zip\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531151028_e27c.zip\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 15:18:59\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 15:19:00', 71); +INSERT INTO `sys_oper_log` VALUES (220, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"2025-05-28至2025-05-28会议室预约记录.xls\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531152447_76e9.xls\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 15:24:49\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 15:24:49', 104); +INSERT INTO `sys_oper_log` VALUES (221, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"2025-05-28至2025-05-28会议室预约记录.xls\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531152447_76e9.xls\\\"},{\\\"name\\\":\\\"2025创新大赛提交.zip\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531150820_1da0.zip\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 15:25:50\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 15:25:51', 81); +INSERT INTO `sys_oper_log` VALUES (222, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 15:32:04\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 15:32:05', 81); +INSERT INTO `sys_oper_log` VALUES (223, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"2025创新大赛提交.zip\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531150820_1da0.zip\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 15:32:16\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 15:32:18', 641); +INSERT INTO `sys_oper_log` VALUES (224, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 19:33:31\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 19:33:32', 151); +INSERT INTO `sys_oper_log` VALUES (225, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"boyue.zip\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531151028_e27c.zip\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 19:33:52\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 19:33:53', 104); +INSERT INTO `sys_oper_log` VALUES (226, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"2025创新大赛提交.zip\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531150820_1da0.zip\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 19:46:19\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 19:46:20', 112); +INSERT INTO `sys_oper_log` VALUES (227, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"2025创新大赛提交.zip\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531150820_1da0.zip\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 20:04:39\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 20:04:40', 83); +INSERT INTO `sys_oper_log` VALUES (228, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"2025创新大赛提交.zip\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531150820_1da0.zip\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 20:14:42\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 20:14:43', 84); +INSERT INTO `sys_oper_log` VALUES (229, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"2025-05-23至2025-05-23会议室预约记录.xls\\\",\\\"url\\\":\\\"/profile/2025/05/31\\\\\\\\20250531203546_d135.xls\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 20:35:48\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 20:35:49', 100); +INSERT INTO `sys_oper_log` VALUES (230, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"boyue.zip\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531151028_e27c.zip\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-06-01 11:03:48\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-06-01 11:03:49', 346); + +-- ---------------------------- +-- Table structure for sys_post +-- ---------------------------- +DROP TABLE IF EXISTS `sys_post`; +CREATE TABLE `sys_post` ( + `post_id` bigint NOT NULL AUTO_INCREMENT COMMENT '岗位ID', + `post_code` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '岗位编码', + `post_name` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '岗位名称', + `post_sort` int NOT NULL COMMENT '显示顺序', + `status` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '状态(0正常 1停用)', + `create_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`post_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '岗位信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_post +-- ---------------------------- +INSERT INTO `sys_post` VALUES (1, 'ceo', '董事长', 1, '0', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_post` VALUES (2, 'se', '项目经理', 2, '0', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_post` VALUES (3, 'hr', '人力资源', 3, '0', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_post` VALUES (4, 'user', '普通员工', 4, '0', 'admin', '2025-05-26 17:20:06', '', NULL, ''); + +-- ---------------------------- +-- Table structure for sys_role +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role`; +CREATE TABLE `sys_role` ( + `role_id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID', + `role_name` varchar(30) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '角色名称', + `role_key` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '角色权限字符串', + `role_sort` int NOT NULL COMMENT '显示顺序', + `data_scope` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '1' COMMENT '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)', + `menu_check_strictly` tinyint(1) NULL DEFAULT 1 COMMENT '菜单树选择项是否关联显示', + `dept_check_strictly` tinyint(1) NULL DEFAULT 1 COMMENT '部门树选择项是否关联显示', + `status` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '角色状态(0正常 1停用)', + `del_flag` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`role_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 101 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '角色信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_role +-- ---------------------------- +INSERT INTO `sys_role` VALUES (1, '超级管理员', 'admin', 1, '1', 1, 1, '0', '0', 'admin', '2025-05-26 17:20:06', '', NULL, '超级管理员'); +INSERT INTO `sys_role` VALUES (2, '普通角色', 'common', 2, '2', 1, 1, '0', '0', 'admin', '2025-05-26 17:20:06', '', NULL, '普通角色'); +INSERT INTO `sys_role` VALUES (100, '司法局', 'sfj', 3, '1', 1, 1, '0', '0', 'admin', '2025-05-29 08:41:41', '', NULL, NULL); + +-- ---------------------------- +-- Table structure for sys_role_dept +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role_dept`; +CREATE TABLE `sys_role_dept` ( + `role_id` bigint NOT NULL COMMENT '角色ID', + `dept_id` bigint NOT NULL COMMENT '部门ID', + PRIMARY KEY (`role_id`, `dept_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '角色和部门关联表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_role_dept +-- ---------------------------- +INSERT INTO `sys_role_dept` VALUES (2, 100); +INSERT INTO `sys_role_dept` VALUES (2, 101); +INSERT INTO `sys_role_dept` VALUES (2, 105); + +-- ---------------------------- +-- Table structure for sys_role_menu +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role_menu`; +CREATE TABLE `sys_role_menu` ( + `role_id` bigint NOT NULL COMMENT '角色ID', + `menu_id` bigint NOT NULL COMMENT '菜单ID', + PRIMARY KEY (`role_id`, `menu_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '角色和菜单关联表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_role_menu +-- ---------------------------- +INSERT INTO `sys_role_menu` VALUES (2, 1); +INSERT INTO `sys_role_menu` VALUES (2, 2); +INSERT INTO `sys_role_menu` VALUES (2, 3); +INSERT INTO `sys_role_menu` VALUES (2, 4); +INSERT INTO `sys_role_menu` VALUES (2, 100); +INSERT INTO `sys_role_menu` VALUES (2, 101); +INSERT INTO `sys_role_menu` VALUES (2, 102); +INSERT INTO `sys_role_menu` VALUES (2, 103); +INSERT INTO `sys_role_menu` VALUES (2, 104); +INSERT INTO `sys_role_menu` VALUES (2, 105); +INSERT INTO `sys_role_menu` VALUES (2, 106); +INSERT INTO `sys_role_menu` VALUES (2, 107); +INSERT INTO `sys_role_menu` VALUES (2, 108); +INSERT INTO `sys_role_menu` VALUES (2, 109); +INSERT INTO `sys_role_menu` VALUES (2, 110); +INSERT INTO `sys_role_menu` VALUES (2, 111); +INSERT INTO `sys_role_menu` VALUES (2, 112); +INSERT INTO `sys_role_menu` VALUES (2, 113); +INSERT INTO `sys_role_menu` VALUES (2, 114); +INSERT INTO `sys_role_menu` VALUES (2, 115); +INSERT INTO `sys_role_menu` VALUES (2, 116); +INSERT INTO `sys_role_menu` VALUES (2, 117); +INSERT INTO `sys_role_menu` VALUES (2, 500); +INSERT INTO `sys_role_menu` VALUES (2, 501); +INSERT INTO `sys_role_menu` VALUES (2, 1000); +INSERT INTO `sys_role_menu` VALUES (2, 1001); +INSERT INTO `sys_role_menu` VALUES (2, 1002); +INSERT INTO `sys_role_menu` VALUES (2, 1003); +INSERT INTO `sys_role_menu` VALUES (2, 1004); +INSERT INTO `sys_role_menu` VALUES (2, 1005); +INSERT INTO `sys_role_menu` VALUES (2, 1006); +INSERT INTO `sys_role_menu` VALUES (2, 1007); +INSERT INTO `sys_role_menu` VALUES (2, 1008); +INSERT INTO `sys_role_menu` VALUES (2, 1009); +INSERT INTO `sys_role_menu` VALUES (2, 1010); +INSERT INTO `sys_role_menu` VALUES (2, 1011); +INSERT INTO `sys_role_menu` VALUES (2, 1012); +INSERT INTO `sys_role_menu` VALUES (2, 1013); +INSERT INTO `sys_role_menu` VALUES (2, 1014); +INSERT INTO `sys_role_menu` VALUES (2, 1015); +INSERT INTO `sys_role_menu` VALUES (2, 1016); +INSERT INTO `sys_role_menu` VALUES (2, 1017); +INSERT INTO `sys_role_menu` VALUES (2, 1018); +INSERT INTO `sys_role_menu` VALUES (2, 1019); +INSERT INTO `sys_role_menu` VALUES (2, 1020); +INSERT INTO `sys_role_menu` VALUES (2, 1021); +INSERT INTO `sys_role_menu` VALUES (2, 1022); +INSERT INTO `sys_role_menu` VALUES (2, 1023); +INSERT INTO `sys_role_menu` VALUES (2, 1024); +INSERT INTO `sys_role_menu` VALUES (2, 1025); +INSERT INTO `sys_role_menu` VALUES (2, 1026); +INSERT INTO `sys_role_menu` VALUES (2, 1027); +INSERT INTO `sys_role_menu` VALUES (2, 1028); +INSERT INTO `sys_role_menu` VALUES (2, 1029); +INSERT INTO `sys_role_menu` VALUES (2, 1030); +INSERT INTO `sys_role_menu` VALUES (2, 1031); +INSERT INTO `sys_role_menu` VALUES (2, 1032); +INSERT INTO `sys_role_menu` VALUES (2, 1033); +INSERT INTO `sys_role_menu` VALUES (2, 1034); +INSERT INTO `sys_role_menu` VALUES (2, 1035); +INSERT INTO `sys_role_menu` VALUES (2, 1036); +INSERT INTO `sys_role_menu` VALUES (2, 1037); +INSERT INTO `sys_role_menu` VALUES (2, 1038); +INSERT INTO `sys_role_menu` VALUES (2, 1039); +INSERT INTO `sys_role_menu` VALUES (2, 1040); +INSERT INTO `sys_role_menu` VALUES (2, 1041); +INSERT INTO `sys_role_menu` VALUES (2, 1042); +INSERT INTO `sys_role_menu` VALUES (2, 1043); +INSERT INTO `sys_role_menu` VALUES (2, 1044); +INSERT INTO `sys_role_menu` VALUES (2, 1045); +INSERT INTO `sys_role_menu` VALUES (2, 1046); +INSERT INTO `sys_role_menu` VALUES (2, 1047); +INSERT INTO `sys_role_menu` VALUES (2, 1048); +INSERT INTO `sys_role_menu` VALUES (2, 1049); +INSERT INTO `sys_role_menu` VALUES (2, 1050); +INSERT INTO `sys_role_menu` VALUES (2, 1051); +INSERT INTO `sys_role_menu` VALUES (2, 1052); +INSERT INTO `sys_role_menu` VALUES (2, 1053); +INSERT INTO `sys_role_menu` VALUES (2, 1054); +INSERT INTO `sys_role_menu` VALUES (2, 1055); +INSERT INTO `sys_role_menu` VALUES (2, 1056); +INSERT INTO `sys_role_menu` VALUES (2, 1057); +INSERT INTO `sys_role_menu` VALUES (2, 1058); +INSERT INTO `sys_role_menu` VALUES (2, 1059); +INSERT INTO `sys_role_menu` VALUES (2, 1060); +INSERT INTO `sys_role_menu` VALUES (100, 1); +INSERT INTO `sys_role_menu` VALUES (100, 100); +INSERT INTO `sys_role_menu` VALUES (100, 1000); +INSERT INTO `sys_role_menu` VALUES (100, 1001); +INSERT INTO `sys_role_menu` VALUES (100, 1002); +INSERT INTO `sys_role_menu` VALUES (100, 1003); +INSERT INTO `sys_role_menu` VALUES (100, 1004); +INSERT INTO `sys_role_menu` VALUES (100, 1005); +INSERT INTO `sys_role_menu` VALUES (100, 1006); +INSERT INTO `sys_role_menu` VALUES (100, 2097); +INSERT INTO `sys_role_menu` VALUES (100, 2099); +INSERT INTO `sys_role_menu` VALUES (100, 2100); +INSERT INTO `sys_role_menu` VALUES (100, 2101); +INSERT INTO `sys_role_menu` VALUES (100, 2102); +INSERT INTO `sys_role_menu` VALUES (100, 2103); +INSERT INTO `sys_role_menu` VALUES (100, 2104); + +-- ---------------------------- +-- Table structure for sys_user +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user`; +CREATE TABLE `sys_user` ( + `user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID', + `dept_id` bigint NULL DEFAULT NULL COMMENT '部门ID', + `user_name` varchar(30) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '用户账号', + `nick_name` varchar(30) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '用户昵称', + `user_type` varchar(2) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '00' COMMENT '用户类型(00系统用户)', + `email` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '用户邮箱', + `phonenumber` varchar(11) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '手机号码', + `sex` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)', + `avatar` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '头像地址', + `password` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '密码', + `status` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '帐号状态(0正常 1停用)', + `del_flag` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `login_ip` varchar(128) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '最后登录IP', + `login_date` datetime NULL DEFAULT NULL COMMENT '最后登录时间', + `create_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`user_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 101 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '用户信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_user +-- ---------------------------- +INSERT INTO `sys_user` VALUES (1, 103, 'admin', '若依', '00', 'ry@163.com', '15888888888', '1', 'avatar/admin/1/20250526183841-avatar.png', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '218.2.75.254', '2025-06-02 19:33:32', 'admin', '2025-05-26 17:20:06', '', '2025-06-02 19:33:32', '管理员'); +INSERT INTO `sys_user` VALUES (2, 105, 'ry', '若依', '00', 'ry@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '2', '127.0.0.1', '2025-05-26 17:20:06', 'admin', '2025-05-26 17:20:06', '', NULL, '测试员'); +INSERT INTO `sys_user` VALUES (100, 200, 'hasfj', '淮安市司法局', '00', '', '', '0', '', '$2a$10$.ZiW8u/mWmHPannD4ewWOenMlUR/BD6lYUrVJopOWfB69..5BJEx2', '0', '0', '114.238.34.181', '2025-05-29 14:57:16', 'admin', '2025-05-29 08:45:27', 'admin', '2025-05-29 14:57:15', NULL); + +-- ---------------------------- +-- Table structure for sys_user_post +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user_post`; +CREATE TABLE `sys_user_post` ( + `user_id` bigint NOT NULL COMMENT '用户ID', + `post_id` bigint NOT NULL COMMENT '岗位ID', + PRIMARY KEY (`user_id`, `post_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '用户与岗位关联表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_user_post +-- ---------------------------- +INSERT INTO `sys_user_post` VALUES (1, 1); + +-- ---------------------------- +-- Table structure for sys_user_role +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user_role`; +CREATE TABLE `sys_user_role` ( + `user_id` bigint NOT NULL COMMENT '用户ID', + `role_id` bigint NOT NULL COMMENT '角色ID', + PRIMARY KEY (`user_id`, `role_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '用户和角色关联表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_user_role +-- ---------------------------- +INSERT INTO `sys_user_role` VALUES (1, 1); +INSERT INTO `sys_user_role` VALUES (100, 100); + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/sql/hasfj/boyuehasfj.sql b/sql/hasfj/boyuehasfj.sql new file mode 100644 index 0000000..b86db7b --- /dev/null +++ b/sql/hasfj/boyuehasfj.sql @@ -0,0 +1,3879 @@ +/* + Navicat Premium Dump SQL + + Source Server : 222.184.49.22测试 + Source Server Type : MySQL + Source Server Version : 80405 (8.4.5) + Source Host : 222.184.49.22:3307 + Source Schema : boyuehasfj + + Target Server Type : MySQL + Target Server Version : 80405 (8.4.5) + File Encoding : 65001 + + Date: 02/06/2025 20:14:56 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for act_app_appdef +-- ---------------------------- +DROP TABLE IF EXISTS `act_app_appdef`; +CREATE TABLE `act_app_appdef` ( + `ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `REV_` int NOT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `KEY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `VERSION_` int NOT NULL, + `CATEGORY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `DEPLOYMENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `RESOURCE_NAME_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `DESCRIPTION_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '', + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_APP_DEF_DPLY`(`DEPLOYMENT_ID_`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 1 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_app_appdef +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_app_deployment +-- ---------------------------- +DROP TABLE IF EXISTS `act_app_deployment`; +CREATE TABLE `act_app_deployment` ( + `ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `CATEGORY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `KEY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `DEPLOY_TIME_` datetime(3) NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '', + PRIMARY KEY (`ID_`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 1 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_app_deployment +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_app_deployment_resource +-- ---------------------------- +DROP TABLE IF EXISTS `act_app_deployment_resource`; +CREATE TABLE `act_app_deployment_resource` ( + `ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `DEPLOYMENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `RESOURCE_BYTES_` longblob NULL, + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_APP_RSRC_DPL`(`DEPLOYMENT_ID_`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 1 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_app_deployment_resource +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_cmmn_casedef +-- ---------------------------- +DROP TABLE IF EXISTS `act_cmmn_casedef`; +CREATE TABLE `act_cmmn_casedef` ( + `ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `REV_` int NOT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `KEY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `VERSION_` int NOT NULL, + `CATEGORY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `DEPLOYMENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `RESOURCE_NAME_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `DESCRIPTION_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `HAS_GRAPHICAL_NOTATION_` tinyint NULL DEFAULT NULL, + `DGRM_RESOURCE_NAME_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `HAS_START_FORM_KEY_` tinyint NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '', + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_CASE_DEF_DPLY`(`DEPLOYMENT_ID_`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 1 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_cmmn_casedef +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_cmmn_deployment +-- ---------------------------- +DROP TABLE IF EXISTS `act_cmmn_deployment`; +CREATE TABLE `act_cmmn_deployment` ( + `ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `CATEGORY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `KEY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `DEPLOY_TIME_` datetime(3) NULL DEFAULT NULL, + `PARENT_DEPLOYMENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '', + PRIMARY KEY (`ID_`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 1 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_cmmn_deployment +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_cmmn_deployment_resource +-- ---------------------------- +DROP TABLE IF EXISTS `act_cmmn_deployment_resource`; +CREATE TABLE `act_cmmn_deployment_resource` ( + `ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `DEPLOYMENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `RESOURCE_BYTES_` longblob NULL, + `GENERATED_` tinyint NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_CMMN_RSRC_DPL`(`DEPLOYMENT_ID_`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 1 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_cmmn_deployment_resource +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_cmmn_hi_case_inst +-- ---------------------------- +DROP TABLE IF EXISTS `act_cmmn_hi_case_inst`; +CREATE TABLE `act_cmmn_hi_case_inst` ( + `ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `REV_` int NOT NULL, + `BUSINESS_KEY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `PARENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `CASE_DEF_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `STATE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `START_TIME_` datetime(3) NULL DEFAULT NULL, + `END_TIME_` datetime(3) NULL DEFAULT NULL, + `START_USER_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `CALLBACK_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `CALLBACK_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `REFERENCE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `REFERENCE_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `LAST_REACTIVATION_TIME_` datetime(3) NULL DEFAULT NULL, + `LAST_REACTIVATION_USER_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `BUSINESS_STATUS_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '', + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_HI_CASE_INST_END`(`END_TIME_`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 1 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_cmmn_hi_case_inst +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_cmmn_hi_mil_inst +-- ---------------------------- +DROP TABLE IF EXISTS `act_cmmn_hi_mil_inst`; +CREATE TABLE `act_cmmn_hi_mil_inst` ( + `ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `REV_` int NOT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `TIME_STAMP_` datetime(3) NOT NULL, + `CASE_INST_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `CASE_DEF_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `ELEMENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '', + PRIMARY KEY (`ID_`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 1 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_cmmn_hi_mil_inst +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_cmmn_hi_plan_item_inst +-- ---------------------------- +DROP TABLE IF EXISTS `act_cmmn_hi_plan_item_inst`; +CREATE TABLE `act_cmmn_hi_plan_item_inst` ( + `ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `REV_` int NOT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `STATE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `CASE_DEF_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `CASE_INST_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `STAGE_INST_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `IS_STAGE_` tinyint NULL DEFAULT NULL, + `ELEMENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `ITEM_DEFINITION_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `ITEM_DEFINITION_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `CREATE_TIME_` datetime(3) NULL DEFAULT NULL, + `LAST_AVAILABLE_TIME_` datetime(3) NULL DEFAULT NULL, + `LAST_ENABLED_TIME_` datetime(3) NULL DEFAULT NULL, + `LAST_DISABLED_TIME_` datetime(3) NULL DEFAULT NULL, + `LAST_STARTED_TIME_` datetime(3) NULL DEFAULT NULL, + `LAST_SUSPENDED_TIME_` datetime(3) NULL DEFAULT NULL, + `COMPLETED_TIME_` datetime(3) NULL DEFAULT NULL, + `OCCURRED_TIME_` datetime(3) NULL DEFAULT NULL, + `TERMINATED_TIME_` datetime(3) NULL DEFAULT NULL, + `EXIT_TIME_` datetime(3) NULL DEFAULT NULL, + `ENDED_TIME_` datetime(3) NULL DEFAULT NULL, + `LAST_UPDATED_TIME_` datetime(3) NULL DEFAULT NULL, + `START_USER_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `REFERENCE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `REFERENCE_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `ENTRY_CRITERION_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `EXIT_CRITERION_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `SHOW_IN_OVERVIEW_` tinyint NULL DEFAULT NULL, + `EXTRA_VALUE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `DERIVED_CASE_DEF_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `LAST_UNAVAILABLE_TIME_` datetime(3) NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '', + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_HI_PLAN_ITEM_INST_CASE`(`CASE_INST_ID_`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 1 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_cmmn_hi_plan_item_inst +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_cmmn_ru_case_inst +-- ---------------------------- +DROP TABLE IF EXISTS `act_cmmn_ru_case_inst`; +CREATE TABLE `act_cmmn_ru_case_inst` ( + `ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `REV_` int NOT NULL, + `BUSINESS_KEY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `PARENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `CASE_DEF_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `STATE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `START_TIME_` datetime(3) NULL DEFAULT NULL, + `START_USER_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `CALLBACK_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `CALLBACK_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `LOCK_TIME_` datetime(3) NULL DEFAULT NULL, + `LOCK_OWNER_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `IS_COMPLETEABLE_` tinyint NULL DEFAULT NULL, + `REFERENCE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `REFERENCE_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `LAST_REACTIVATION_TIME_` datetime(3) NULL DEFAULT NULL, + `LAST_REACTIVATION_USER_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `BUSINESS_STATUS_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '', + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_CASE_INST_CASE_DEF`(`CASE_DEF_ID_`) USING BTREE, + INDEX `ACT_IDX_CASE_INST_PARENT`(`PARENT_ID_`) USING BTREE, + INDEX `ACT_IDX_CASE_INST_REF_ID_`(`REFERENCE_ID_`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 1 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_cmmn_ru_case_inst +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_cmmn_ru_mil_inst +-- ---------------------------- +DROP TABLE IF EXISTS `act_cmmn_ru_mil_inst`; +CREATE TABLE `act_cmmn_ru_mil_inst` ( + `ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `TIME_STAMP_` datetime(3) NOT NULL, + `CASE_INST_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `CASE_DEF_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `ELEMENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '', + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_MIL_CASE_DEF`(`CASE_DEF_ID_`) USING BTREE, + INDEX `ACT_IDX_MIL_CASE_INST`(`CASE_INST_ID_`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 1 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_cmmn_ru_mil_inst +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_cmmn_ru_plan_item_inst +-- ---------------------------- +DROP TABLE IF EXISTS `act_cmmn_ru_plan_item_inst`; +CREATE TABLE `act_cmmn_ru_plan_item_inst` ( + `ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `REV_` int NOT NULL, + `CASE_DEF_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `CASE_INST_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `STAGE_INST_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `IS_STAGE_` tinyint NULL DEFAULT NULL, + `ELEMENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `STATE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `CREATE_TIME_` datetime(3) NULL DEFAULT NULL, + `START_USER_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `REFERENCE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `REFERENCE_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `ITEM_DEFINITION_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `ITEM_DEFINITION_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `IS_COMPLETEABLE_` tinyint NULL DEFAULT NULL, + `IS_COUNT_ENABLED_` tinyint NULL DEFAULT NULL, + `VAR_COUNT_` int NULL DEFAULT NULL, + `SENTRY_PART_INST_COUNT_` int NULL DEFAULT NULL, + `LAST_AVAILABLE_TIME_` datetime(3) NULL DEFAULT NULL, + `LAST_ENABLED_TIME_` datetime(3) NULL DEFAULT NULL, + `LAST_DISABLED_TIME_` datetime(3) NULL DEFAULT NULL, + `LAST_STARTED_TIME_` datetime(3) NULL DEFAULT NULL, + `LAST_SUSPENDED_TIME_` datetime(3) NULL DEFAULT NULL, + `COMPLETED_TIME_` datetime(3) NULL DEFAULT NULL, + `OCCURRED_TIME_` datetime(3) NULL DEFAULT NULL, + `TERMINATED_TIME_` datetime(3) NULL DEFAULT NULL, + `EXIT_TIME_` datetime(3) NULL DEFAULT NULL, + `ENDED_TIME_` datetime(3) NULL DEFAULT NULL, + `ENTRY_CRITERION_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `EXIT_CRITERION_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `EXTRA_VALUE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `DERIVED_CASE_DEF_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `LAST_UNAVAILABLE_TIME_` datetime(3) NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '', + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_PLAN_ITEM_CASE_DEF`(`CASE_DEF_ID_`) USING BTREE, + INDEX `ACT_IDX_PLAN_ITEM_CASE_INST`(`CASE_INST_ID_`) USING BTREE, + INDEX `ACT_IDX_PLAN_ITEM_STAGE_INST`(`STAGE_INST_ID_`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 1 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_cmmn_ru_plan_item_inst +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_cmmn_ru_sentry_part_inst +-- ---------------------------- +DROP TABLE IF EXISTS `act_cmmn_ru_sentry_part_inst`; +CREATE TABLE `act_cmmn_ru_sentry_part_inst` ( + `ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `REV_` int NOT NULL, + `CASE_DEF_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `CASE_INST_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `PLAN_ITEM_INST_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `ON_PART_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `IF_PART_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `TIME_STAMP_` datetime(3) NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_SENTRY_CASE_DEF`(`CASE_DEF_ID_`) USING BTREE, + INDEX `ACT_IDX_SENTRY_CASE_INST`(`CASE_INST_ID_`) USING BTREE, + INDEX `ACT_IDX_SENTRY_PLAN_ITEM`(`PLAN_ITEM_INST_ID_`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 1 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_cmmn_ru_sentry_part_inst +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_dmn_decision +-- ---------------------------- +DROP TABLE IF EXISTS `act_dmn_decision`; +CREATE TABLE `act_dmn_decision` ( + `ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `VERSION_` int NULL DEFAULT NULL, + `KEY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `CATEGORY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `DEPLOYMENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `RESOURCE_NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `DESCRIPTION_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `DECISION_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 1 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_dmn_decision +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_dmn_deployment +-- ---------------------------- +DROP TABLE IF EXISTS `act_dmn_deployment`; +CREATE TABLE `act_dmn_deployment` ( + `ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `CATEGORY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `DEPLOY_TIME_` datetime(3) NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `PARENT_DEPLOYMENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 1 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_dmn_deployment +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_dmn_deployment_resource +-- ---------------------------- +DROP TABLE IF EXISTS `act_dmn_deployment_resource`; +CREATE TABLE `act_dmn_deployment_resource` ( + `ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `DEPLOYMENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `RESOURCE_BYTES_` longblob NULL, + PRIMARY KEY (`ID_`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 1 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_dmn_deployment_resource +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_dmn_hi_decision_execution +-- ---------------------------- +DROP TABLE IF EXISTS `act_dmn_hi_decision_execution`; +CREATE TABLE `act_dmn_hi_decision_execution` ( + `ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `DECISION_DEFINITION_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `DEPLOYMENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `START_TIME_` datetime(3) NULL DEFAULT NULL, + `END_TIME_` datetime(3) NULL DEFAULT NULL, + `INSTANCE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `EXECUTION_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `ACTIVITY_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `FAILED_` tinyint NULL DEFAULT 0, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `EXECUTION_JSON_` longtext CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL, + `SCOPE_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_DMN_INSTANCE_ID`(`INSTANCE_ID_`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 1 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_dmn_hi_decision_execution +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_evt_log +-- ---------------------------- +DROP TABLE IF EXISTS `act_evt_log`; +CREATE TABLE `act_evt_log` ( + `LOG_NR_` bigint NOT NULL AUTO_INCREMENT, + `TYPE_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROC_DEF_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROC_INST_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `EXECUTION_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TASK_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TIME_STAMP_` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `USER_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DATA_` longblob NULL, + `LOCK_OWNER_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `LOCK_TIME_` timestamp(3) NULL DEFAULT NULL, + `IS_PROCESSED_` tinyint NULL DEFAULT 0, + PRIMARY KEY (`LOG_NR_`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_evt_log +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_ge_bytearray +-- ---------------------------- +DROP TABLE IF EXISTS `act_ge_bytearray`; +CREATE TABLE `act_ge_bytearray` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DEPLOYMENT_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `BYTES_` longblob NULL, + `GENERATED_` tinyint NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_FK_BYTEARR_DEPL`(`DEPLOYMENT_ID_` ASC) USING BTREE, + CONSTRAINT `ACT_FK_BYTEARR_DEPL` FOREIGN KEY (`DEPLOYMENT_ID_`) REFERENCES `act_re_deployment` (`id_`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_ge_bytearray +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_ge_property +-- ---------------------------- +DROP TABLE IF EXISTS `act_ge_property`; +CREATE TABLE `act_ge_property` ( + `NAME_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `VALUE_` varchar(300) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `REV_` int NULL DEFAULT NULL, + PRIMARY KEY (`NAME_`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_ge_property +-- ---------------------------- +INSERT INTO `act_ge_property` VALUES ('app.schema.version', '7.1.0.2', 1); +INSERT INTO `act_ge_property` VALUES ('cfg.execution-related-entities-count', 'true', 1); +INSERT INTO `act_ge_property` VALUES ('cfg.task-related-entities-count', 'true', 1); +INSERT INTO `act_ge_property` VALUES ('cmmn.schema.version', '7.1.0.2', 1); +INSERT INTO `act_ge_property` VALUES ('common.schema.version', '7.1.0.2', 1); +INSERT INTO `act_ge_property` VALUES ('dmn.schema.version', '7.1.0.2', 1); +INSERT INTO `act_ge_property` VALUES ('eventregistry.schema.version', '7.1.0.2', 1); +INSERT INTO `act_ge_property` VALUES ('next.dbid', '1', 1); +INSERT INTO `act_ge_property` VALUES ('schema.history', 'create(7.1.0.2)', 1); +INSERT INTO `act_ge_property` VALUES ('schema.version', '7.1.0.2', 1); + +-- ---------------------------- +-- Table structure for act_hi_actinst +-- ---------------------------- +DROP TABLE IF EXISTS `act_hi_actinst`; +CREATE TABLE `act_hi_actinst` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT 1, + `PROC_DEF_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `PROC_INST_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `EXECUTION_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `ACT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `TASK_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CALL_PROC_INST_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ACT_NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ACT_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `ASSIGNEE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `START_TIME_` datetime(3) NOT NULL, + `END_TIME_` datetime(3) NULL DEFAULT NULL, + `TRANSACTION_ORDER_` int NULL DEFAULT NULL, + `DURATION_` bigint NULL DEFAULT NULL, + `DELETE_REASON_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT '', + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_HI_ACT_INST_START`(`START_TIME_` ASC) USING BTREE, + INDEX `ACT_IDX_HI_ACT_INST_END`(`END_TIME_` ASC) USING BTREE, + INDEX `ACT_IDX_HI_ACT_INST_PROCINST`(`PROC_INST_ID_` ASC, `ACT_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_HI_ACT_INST_EXEC`(`EXECUTION_ID_` ASC, `ACT_ID_` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_hi_actinst +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_hi_attachment +-- ---------------------------- +DROP TABLE IF EXISTS `act_hi_attachment`; +CREATE TABLE `act_hi_attachment` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT NULL, + `USER_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DESCRIPTION_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TASK_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROC_INST_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `URL_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CONTENT_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TIME_` datetime(3) NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_hi_attachment +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_hi_comment +-- ---------------------------- +DROP TABLE IF EXISTS `act_hi_comment`; +CREATE TABLE `act_hi_comment` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TIME_` datetime(3) NOT NULL, + `USER_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TASK_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROC_INST_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ACTION_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `MESSAGE_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `FULL_MSG_` longblob NULL, + PRIMARY KEY (`ID_`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_hi_comment +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_hi_detail +-- ---------------------------- +DROP TABLE IF EXISTS `act_hi_detail`; +CREATE TABLE `act_hi_detail` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `PROC_INST_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `EXECUTION_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TASK_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ACT_INST_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `VAR_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `REV_` int NULL DEFAULT NULL, + `TIME_` datetime(3) NOT NULL, + `BYTEARRAY_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DOUBLE_` double NULL DEFAULT NULL, + `LONG_` bigint NULL DEFAULT NULL, + `TEXT_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TEXT2_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_HI_DETAIL_PROC_INST`(`PROC_INST_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_HI_DETAIL_ACT_INST`(`ACT_INST_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_HI_DETAIL_TIME`(`TIME_` ASC) USING BTREE, + INDEX `ACT_IDX_HI_DETAIL_NAME`(`NAME_` ASC) USING BTREE, + INDEX `ACT_IDX_HI_DETAIL_TASK_ID`(`TASK_ID_` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_hi_detail +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_hi_entitylink +-- ---------------------------- +DROP TABLE IF EXISTS `act_hi_entitylink`; +CREATE TABLE `act_hi_entitylink` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `LINK_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CREATE_TIME_` datetime(3) NULL DEFAULT NULL, + `SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SUB_SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_DEFINITION_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PARENT_ELEMENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `REF_SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `REF_SCOPE_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `REF_SCOPE_DEFINITION_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ROOT_SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ROOT_SCOPE_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `HIERARCHY_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_HI_ENT_LNK_SCOPE`(`SCOPE_ID_` ASC, `SCOPE_TYPE_` ASC, `LINK_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_HI_ENT_LNK_REF_SCOPE`(`REF_SCOPE_ID_` ASC, `REF_SCOPE_TYPE_` ASC, `LINK_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_HI_ENT_LNK_ROOT_SCOPE`(`ROOT_SCOPE_ID_` ASC, `ROOT_SCOPE_TYPE_` ASC, `LINK_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_HI_ENT_LNK_SCOPE_DEF`(`SCOPE_DEFINITION_ID_` ASC, `SCOPE_TYPE_` ASC, `LINK_TYPE_` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_hi_entitylink +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_hi_identitylink +-- ---------------------------- +DROP TABLE IF EXISTS `act_hi_identitylink`; +CREATE TABLE `act_hi_identitylink` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `GROUP_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `USER_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TASK_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CREATE_TIME_` datetime(3) NULL DEFAULT NULL, + `PROC_INST_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SUB_SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_DEFINITION_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_HI_IDENT_LNK_USER`(`USER_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_HI_IDENT_LNK_SCOPE`(`SCOPE_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_HI_IDENT_LNK_SUB_SCOPE`(`SUB_SCOPE_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_HI_IDENT_LNK_SCOPE_DEF`(`SCOPE_DEFINITION_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_HI_IDENT_LNK_TASK`(`TASK_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_HI_IDENT_LNK_PROCINST`(`PROC_INST_ID_` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_hi_identitylink +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_hi_procinst +-- ---------------------------- +DROP TABLE IF EXISTS `act_hi_procinst`; +CREATE TABLE `act_hi_procinst` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT 1, + `PROC_INST_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `BUSINESS_KEY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROC_DEF_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `START_TIME_` datetime(3) NOT NULL, + `END_TIME_` datetime(3) NULL DEFAULT NULL, + `DURATION_` bigint NULL DEFAULT NULL, + `START_USER_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `START_ACT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `END_ACT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SUPER_PROCESS_INSTANCE_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DELETE_REASON_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT '', + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CALLBACK_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CALLBACK_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `REFERENCE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `REFERENCE_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROPAGATED_STAGE_INST_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `BUSINESS_STATUS_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE, + UNIQUE INDEX `PROC_INST_ID_`(`PROC_INST_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_HI_PRO_INST_END`(`END_TIME_` ASC) USING BTREE, + INDEX `ACT_IDX_HI_PRO_I_BUSKEY`(`BUSINESS_KEY_` ASC) USING BTREE, + INDEX `ACT_IDX_HI_PRO_SUPER_PROCINST`(`SUPER_PROCESS_INSTANCE_ID_` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_hi_procinst +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_hi_taskinst +-- ---------------------------- +DROP TABLE IF EXISTS `act_hi_taskinst`; +CREATE TABLE `act_hi_taskinst` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT 1, + `PROC_DEF_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TASK_DEF_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TASK_DEF_KEY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROC_INST_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `EXECUTION_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SUB_SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_DEFINITION_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROPAGATED_STAGE_INST_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `STATE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PARENT_TASK_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DESCRIPTION_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `OWNER_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ASSIGNEE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `START_TIME_` datetime(3) NOT NULL, + `IN_PROGRESS_TIME_` datetime(3) NULL DEFAULT NULL, + `IN_PROGRESS_STARTED_BY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CLAIM_TIME_` datetime(3) NULL DEFAULT NULL, + `CLAIMED_BY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SUSPENDED_TIME_` datetime(3) NULL DEFAULT NULL, + `SUSPENDED_BY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `END_TIME_` datetime(3) NULL DEFAULT NULL, + `COMPLETED_BY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DURATION_` bigint NULL DEFAULT NULL, + `DELETE_REASON_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PRIORITY_` int NULL DEFAULT NULL, + `IN_PROGRESS_DUE_DATE_` datetime(3) NULL DEFAULT NULL, + `DUE_DATE_` datetime(3) NULL DEFAULT NULL, + `FORM_KEY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CATEGORY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT '', + `LAST_UPDATED_TIME_` datetime(3) NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_HI_TASK_SCOPE`(`SCOPE_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_HI_TASK_SUB_SCOPE`(`SUB_SCOPE_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_HI_TASK_SCOPE_DEF`(`SCOPE_DEFINITION_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_HI_TASK_INST_PROCINST`(`PROC_INST_ID_` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_hi_taskinst +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_hi_tsk_log +-- ---------------------------- +DROP TABLE IF EXISTS `act_hi_tsk_log`; +CREATE TABLE `act_hi_tsk_log` ( + `ID_` bigint NOT NULL AUTO_INCREMENT, + `TYPE_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TASK_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `TIME_STAMP_` timestamp(3) NOT NULL, + `USER_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DATA_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `EXECUTION_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROC_INST_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROC_DEF_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_DEFINITION_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SUB_SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT '', + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_ACT_HI_TSK_LOG_TASK`(`TASK_ID_` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_hi_tsk_log +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_hi_varinst +-- ---------------------------- +DROP TABLE IF EXISTS `act_hi_varinst`; +CREATE TABLE `act_hi_varinst` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT 1, + `PROC_INST_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `EXECUTION_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TASK_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `VAR_TYPE_` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SUB_SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `BYTEARRAY_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DOUBLE_` double NULL DEFAULT NULL, + `LONG_` bigint NULL DEFAULT NULL, + `TEXT_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TEXT2_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `META_INFO_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CREATE_TIME_` datetime(3) NULL DEFAULT NULL, + `LAST_UPDATED_TIME_` datetime(3) NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_HI_PROCVAR_NAME_TYPE`(`NAME_` ASC, `VAR_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_HI_VAR_SCOPE_ID_TYPE`(`SCOPE_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_HI_VAR_SUB_ID_TYPE`(`SUB_SCOPE_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_HI_PROCVAR_PROC_INST`(`PROC_INST_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_HI_PROCVAR_TASK_ID`(`TASK_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_HI_PROCVAR_EXE`(`EXECUTION_ID_` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_hi_varinst +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_id_bytearray +-- ---------------------------- +DROP TABLE IF EXISTS `act_id_bytearray`; +CREATE TABLE `act_id_bytearray` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `BYTES_` longblob NULL, + PRIMARY KEY (`ID_`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_id_bytearray +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_id_group +-- ---------------------------- +DROP TABLE IF EXISTS `act_id_group`; +CREATE TABLE `act_id_group` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_id_group +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_id_info +-- ---------------------------- +DROP TABLE IF EXISTS `act_id_info`; +CREATE TABLE `act_id_info` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT NULL, + `USER_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TYPE_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `KEY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `VALUE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PASSWORD_` longblob NULL, + `PARENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_id_info +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_id_membership +-- ---------------------------- +DROP TABLE IF EXISTS `act_id_membership`; +CREATE TABLE `act_id_membership` ( + `USER_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `GROUP_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + PRIMARY KEY (`USER_ID_`, `GROUP_ID_`) USING BTREE, + INDEX `ACT_FK_MEMB_GROUP`(`GROUP_ID_` ASC) USING BTREE, + CONSTRAINT `ACT_FK_MEMB_GROUP` FOREIGN KEY (`GROUP_ID_`) REFERENCES `act_id_group` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_MEMB_USER` FOREIGN KEY (`USER_ID_`) REFERENCES `act_id_user` (`id_`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_id_membership +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_id_priv +-- ---------------------------- +DROP TABLE IF EXISTS `act_id_priv`; +CREATE TABLE `act_id_priv` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + PRIMARY KEY (`ID_`) USING BTREE, + UNIQUE INDEX `ACT_UNIQ_PRIV_NAME`(`NAME_` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_id_priv +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_id_priv_mapping +-- ---------------------------- +DROP TABLE IF EXISTS `act_id_priv_mapping`; +CREATE TABLE `act_id_priv_mapping` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `PRIV_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `USER_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `GROUP_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_FK_PRIV_MAPPING`(`PRIV_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_PRIV_USER`(`USER_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_PRIV_GROUP`(`GROUP_ID_` ASC) USING BTREE, + CONSTRAINT `ACT_FK_PRIV_MAPPING` FOREIGN KEY (`PRIV_ID_`) REFERENCES `act_id_priv` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_id_priv_mapping +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_id_property +-- ---------------------------- +DROP TABLE IF EXISTS `act_id_property`; +CREATE TABLE `act_id_property` ( + `NAME_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `VALUE_` varchar(300) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `REV_` int NULL DEFAULT NULL, + PRIMARY KEY (`NAME_`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_id_property +-- ---------------------------- +INSERT INTO `act_id_property` VALUES ('schema.version', '7.1.0.2', 1); + +-- ---------------------------- +-- Table structure for act_id_token +-- ---------------------------- +DROP TABLE IF EXISTS `act_id_token`; +CREATE TABLE `act_id_token` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT NULL, + `TOKEN_VALUE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TOKEN_DATE_` timestamp(3) NULL DEFAULT NULL, + `IP_ADDRESS_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `USER_AGENT_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `USER_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TOKEN_DATA_` varchar(2000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_id_token +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_id_user +-- ---------------------------- +DROP TABLE IF EXISTS `act_id_user`; +CREATE TABLE `act_id_user` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT NULL, + `FIRST_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `LAST_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DISPLAY_NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `EMAIL_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PWD_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PICTURE_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT '', + PRIMARY KEY (`ID_`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_id_user +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_procdef_info +-- ---------------------------- +DROP TABLE IF EXISTS `act_procdef_info`; +CREATE TABLE `act_procdef_info` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `PROC_DEF_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT NULL, + `INFO_JSON_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE, + UNIQUE INDEX `ACT_UNIQ_INFO_PROCDEF`(`PROC_DEF_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_INFO_PROCDEF`(`PROC_DEF_ID_` ASC) USING BTREE, + INDEX `ACT_FK_INFO_JSON_BA`(`INFO_JSON_ID_` ASC) USING BTREE, + CONSTRAINT `ACT_FK_INFO_JSON_BA` FOREIGN KEY (`INFO_JSON_ID_`) REFERENCES `act_ge_bytearray` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_INFO_PROCDEF` FOREIGN KEY (`PROC_DEF_ID_`) REFERENCES `act_re_procdef` (`id_`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_procdef_info +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_re_deployment +-- ---------------------------- +DROP TABLE IF EXISTS `act_re_deployment`; +CREATE TABLE `act_re_deployment` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CATEGORY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `KEY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT '', + `DEPLOY_TIME_` timestamp(3) NULL DEFAULT NULL, + `DERIVED_FROM_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DERIVED_FROM_ROOT_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PARENT_DEPLOYMENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ENGINE_VERSION_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_re_deployment +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_re_model +-- ---------------------------- +DROP TABLE IF EXISTS `act_re_model`; +CREATE TABLE `act_re_model` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `KEY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CATEGORY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CREATE_TIME_` timestamp(3) NULL DEFAULT NULL, + `LAST_UPDATE_TIME_` timestamp(3) NULL DEFAULT NULL, + `VERSION_` int NULL DEFAULT NULL, + `META_INFO_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DEPLOYMENT_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `EDITOR_SOURCE_VALUE_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `EDITOR_SOURCE_EXTRA_VALUE_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT '', + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_FK_MODEL_SOURCE`(`EDITOR_SOURCE_VALUE_ID_` ASC) USING BTREE, + INDEX `ACT_FK_MODEL_SOURCE_EXTRA`(`EDITOR_SOURCE_EXTRA_VALUE_ID_` ASC) USING BTREE, + INDEX `ACT_FK_MODEL_DEPLOYMENT`(`DEPLOYMENT_ID_` ASC) USING BTREE, + CONSTRAINT `ACT_FK_MODEL_DEPLOYMENT` FOREIGN KEY (`DEPLOYMENT_ID_`) REFERENCES `act_re_deployment` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_MODEL_SOURCE` FOREIGN KEY (`EDITOR_SOURCE_VALUE_ID_`) REFERENCES `act_ge_bytearray` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_MODEL_SOURCE_EXTRA` FOREIGN KEY (`EDITOR_SOURCE_EXTRA_VALUE_ID_`) REFERENCES `act_ge_bytearray` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_re_model +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_re_procdef +-- ---------------------------- +DROP TABLE IF EXISTS `act_re_procdef`; +CREATE TABLE `act_re_procdef` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT NULL, + `CATEGORY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `KEY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `VERSION_` int NOT NULL, + `DEPLOYMENT_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `RESOURCE_NAME_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DGRM_RESOURCE_NAME_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DESCRIPTION_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `HAS_START_FORM_KEY_` tinyint NULL DEFAULT NULL, + `HAS_GRAPHICAL_NOTATION_` tinyint NULL DEFAULT NULL, + `SUSPENSION_STATE_` int NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT '', + `ENGINE_VERSION_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DERIVED_FROM_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DERIVED_FROM_ROOT_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DERIVED_VERSION_` int NOT NULL DEFAULT 0, + PRIMARY KEY (`ID_`) USING BTREE, + UNIQUE INDEX `ACT_UNIQ_PROCDEF`(`KEY_` ASC, `VERSION_` ASC, `DERIVED_VERSION_` ASC, `TENANT_ID_` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_re_procdef +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_ru_actinst +-- ---------------------------- +DROP TABLE IF EXISTS `act_ru_actinst`; +CREATE TABLE `act_ru_actinst` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT 1, + `PROC_DEF_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `PROC_INST_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `EXECUTION_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `ACT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `TASK_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CALL_PROC_INST_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ACT_NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ACT_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `ASSIGNEE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `START_TIME_` datetime(3) NOT NULL, + `END_TIME_` datetime(3) NULL DEFAULT NULL, + `DURATION_` bigint NULL DEFAULT NULL, + `TRANSACTION_ORDER_` int NULL DEFAULT NULL, + `DELETE_REASON_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT '', + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_RU_ACTI_START`(`START_TIME_` ASC) USING BTREE, + INDEX `ACT_IDX_RU_ACTI_END`(`END_TIME_` ASC) USING BTREE, + INDEX `ACT_IDX_RU_ACTI_PROC`(`PROC_INST_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_RU_ACTI_PROC_ACT`(`PROC_INST_ID_` ASC, `ACT_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_RU_ACTI_EXEC`(`EXECUTION_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_RU_ACTI_EXEC_ACT`(`EXECUTION_ID_` ASC, `ACT_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_RU_ACTI_TASK`(`TASK_ID_` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_ru_actinst +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_ru_deadletter_job +-- ---------------------------- +DROP TABLE IF EXISTS `act_ru_deadletter_job`; +CREATE TABLE `act_ru_deadletter_job` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT NULL, + `CATEGORY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `EXCLUSIVE_` tinyint(1) NULL DEFAULT NULL, + `EXECUTION_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROCESS_INSTANCE_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROC_DEF_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ELEMENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ELEMENT_NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SUB_SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_DEFINITION_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CORRELATION_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `EXCEPTION_STACK_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `EXCEPTION_MSG_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DUEDATE_` timestamp(3) NULL DEFAULT NULL, + `REPEAT_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `HANDLER_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `HANDLER_CFG_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CUSTOM_VALUES_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CREATE_TIME_` timestamp(3) NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT '', + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_DEADLETTER_JOB_EXCEPTION_STACK_ID`(`EXCEPTION_STACK_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_DEADLETTER_JOB_CUSTOM_VALUES_ID`(`CUSTOM_VALUES_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_DEADLETTER_JOB_CORRELATION_ID`(`CORRELATION_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_DJOB_SCOPE`(`SCOPE_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_DJOB_SUB_SCOPE`(`SUB_SCOPE_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_DJOB_SCOPE_DEF`(`SCOPE_DEFINITION_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_FK_DEADLETTER_JOB_EXECUTION`(`EXECUTION_ID_` ASC) USING BTREE, + INDEX `ACT_FK_DEADLETTER_JOB_PROCESS_INSTANCE`(`PROCESS_INSTANCE_ID_` ASC) USING BTREE, + INDEX `ACT_FK_DEADLETTER_JOB_PROC_DEF`(`PROC_DEF_ID_` ASC) USING BTREE, + CONSTRAINT `ACT_FK_DEADLETTER_JOB_CUSTOM_VALUES` FOREIGN KEY (`CUSTOM_VALUES_ID_`) REFERENCES `act_ge_bytearray` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_DEADLETTER_JOB_EXCEPTION` FOREIGN KEY (`EXCEPTION_STACK_ID_`) REFERENCES `act_ge_bytearray` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_DEADLETTER_JOB_EXECUTION` FOREIGN KEY (`EXECUTION_ID_`) REFERENCES `act_ru_execution` (`id_`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_DEADLETTER_JOB_PROC_DEF` FOREIGN KEY (`PROC_DEF_ID_`) REFERENCES `act_re_procdef` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_DEADLETTER_JOB_PROCESS_INSTANCE` FOREIGN KEY (`PROCESS_INSTANCE_ID_`) REFERENCES `act_ru_execution` (`id_`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_ru_deadletter_job +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_ru_entitylink +-- ---------------------------- +DROP TABLE IF EXISTS `act_ru_entitylink`; +CREATE TABLE `act_ru_entitylink` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT NULL, + `CREATE_TIME_` datetime(3) NULL DEFAULT NULL, + `LINK_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SUB_SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_DEFINITION_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PARENT_ELEMENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `REF_SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `REF_SCOPE_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `REF_SCOPE_DEFINITION_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ROOT_SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ROOT_SCOPE_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `HIERARCHY_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_ENT_LNK_SCOPE`(`SCOPE_ID_` ASC, `SCOPE_TYPE_` ASC, `LINK_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_ENT_LNK_REF_SCOPE`(`REF_SCOPE_ID_` ASC, `REF_SCOPE_TYPE_` ASC, `LINK_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_ENT_LNK_ROOT_SCOPE`(`ROOT_SCOPE_ID_` ASC, `ROOT_SCOPE_TYPE_` ASC, `LINK_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_ENT_LNK_SCOPE_DEF`(`SCOPE_DEFINITION_ID_` ASC, `SCOPE_TYPE_` ASC, `LINK_TYPE_` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_ru_entitylink +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_ru_event_subscr +-- ---------------------------- +DROP TABLE IF EXISTS `act_ru_event_subscr`; +CREATE TABLE `act_ru_event_subscr` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT NULL, + `EVENT_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `EVENT_NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `EXECUTION_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROC_INST_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ACTIVITY_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CONFIGURATION_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CREATED_` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `PROC_DEF_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SUB_SCOPE_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_DEFINITION_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_DEFINITION_KEY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_TYPE_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `LOCK_TIME_` timestamp(3) NULL DEFAULT NULL, + `LOCK_OWNER_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT '', + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_EVENT_SUBSCR_CONFIG_`(`CONFIGURATION_` ASC) USING BTREE, + INDEX `ACT_IDX_EVENT_SUBSCR_EXEC_ID`(`EXECUTION_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_EVENT_SUBSCR_PROC_ID`(`PROC_INST_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_EVENT_SUBSCR_SCOPEREF_`(`SCOPE_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + CONSTRAINT `ACT_FK_EVENT_EXEC` FOREIGN KEY (`EXECUTION_ID_`) REFERENCES `act_ru_execution` (`id_`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_ru_event_subscr +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_ru_execution +-- ---------------------------- +DROP TABLE IF EXISTS `act_ru_execution`; +CREATE TABLE `act_ru_execution` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT NULL, + `PROC_INST_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `BUSINESS_KEY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PARENT_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROC_DEF_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SUPER_EXEC_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ROOT_PROC_INST_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ACT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `IS_ACTIVE_` tinyint NULL DEFAULT NULL, + `IS_CONCURRENT_` tinyint NULL DEFAULT NULL, + `IS_SCOPE_` tinyint NULL DEFAULT NULL, + `IS_EVENT_SCOPE_` tinyint NULL DEFAULT NULL, + `IS_MI_ROOT_` tinyint NULL DEFAULT NULL, + `SUSPENSION_STATE_` int NULL DEFAULT NULL, + `CACHED_ENT_STATE_` int NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT '', + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `START_ACT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `START_TIME_` datetime(3) NULL DEFAULT NULL, + `START_USER_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `LOCK_TIME_` timestamp(3) NULL DEFAULT NULL, + `LOCK_OWNER_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `IS_COUNT_ENABLED_` tinyint NULL DEFAULT NULL, + `EVT_SUBSCR_COUNT_` int NULL DEFAULT NULL, + `TASK_COUNT_` int NULL DEFAULT NULL, + `JOB_COUNT_` int NULL DEFAULT NULL, + `TIMER_JOB_COUNT_` int NULL DEFAULT NULL, + `SUSP_JOB_COUNT_` int NULL DEFAULT NULL, + `DEADLETTER_JOB_COUNT_` int NULL DEFAULT NULL, + `EXTERNAL_WORKER_JOB_COUNT_` int NULL DEFAULT NULL, + `VAR_COUNT_` int NULL DEFAULT NULL, + `ID_LINK_COUNT_` int NULL DEFAULT NULL, + `CALLBACK_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CALLBACK_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `REFERENCE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `REFERENCE_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROPAGATED_STAGE_INST_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `BUSINESS_STATUS_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_EXEC_BUSKEY`(`BUSINESS_KEY_` ASC) USING BTREE, + INDEX `ACT_IDC_EXEC_ROOT`(`ROOT_PROC_INST_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_EXEC_REF_ID_`(`REFERENCE_ID_` ASC) USING BTREE, + INDEX `ACT_FK_EXE_PROCINST`(`PROC_INST_ID_` ASC) USING BTREE, + INDEX `ACT_FK_EXE_PARENT`(`PARENT_ID_` ASC) USING BTREE, + INDEX `ACT_FK_EXE_SUPER`(`SUPER_EXEC_` ASC) USING BTREE, + INDEX `ACT_FK_EXE_PROCDEF`(`PROC_DEF_ID_` ASC) USING BTREE, + CONSTRAINT `ACT_FK_EXE_PARENT` FOREIGN KEY (`PARENT_ID_`) REFERENCES `act_ru_execution` (`ID_`) ON DELETE CASCADE ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_EXE_PROCDEF` FOREIGN KEY (`PROC_DEF_ID_`) REFERENCES `act_re_procdef` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_EXE_PROCINST` FOREIGN KEY (`PROC_INST_ID_`) REFERENCES `act_ru_execution` (`ID_`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `ACT_FK_EXE_SUPER` FOREIGN KEY (`SUPER_EXEC_`) REFERENCES `act_ru_execution` (`ID_`) ON DELETE CASCADE ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_ru_execution +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_ru_external_job +-- ---------------------------- +DROP TABLE IF EXISTS `act_ru_external_job`; +CREATE TABLE `act_ru_external_job` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT NULL, + `CATEGORY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `LOCK_EXP_TIME_` timestamp(3) NULL DEFAULT NULL, + `LOCK_OWNER_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `EXCLUSIVE_` tinyint(1) NULL DEFAULT NULL, + `EXECUTION_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROCESS_INSTANCE_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROC_DEF_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ELEMENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ELEMENT_NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SUB_SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_DEFINITION_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CORRELATION_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `RETRIES_` int NULL DEFAULT NULL, + `EXCEPTION_STACK_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `EXCEPTION_MSG_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DUEDATE_` timestamp(3) NULL DEFAULT NULL, + `REPEAT_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `HANDLER_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `HANDLER_CFG_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CUSTOM_VALUES_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CREATE_TIME_` timestamp(3) NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT '', + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_EXTERNAL_JOB_EXCEPTION_STACK_ID`(`EXCEPTION_STACK_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_EXTERNAL_JOB_CUSTOM_VALUES_ID`(`CUSTOM_VALUES_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_EXTERNAL_JOB_CORRELATION_ID`(`CORRELATION_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_EJOB_SCOPE`(`SCOPE_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_EJOB_SUB_SCOPE`(`SUB_SCOPE_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_EJOB_SCOPE_DEF`(`SCOPE_DEFINITION_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + CONSTRAINT `ACT_FK_EXTERNAL_JOB_CUSTOM_VALUES` FOREIGN KEY (`CUSTOM_VALUES_ID_`) REFERENCES `act_ge_bytearray` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_EXTERNAL_JOB_EXCEPTION` FOREIGN KEY (`EXCEPTION_STACK_ID_`) REFERENCES `act_ge_bytearray` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_ru_external_job +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_ru_history_job +-- ---------------------------- +DROP TABLE IF EXISTS `act_ru_history_job`; +CREATE TABLE `act_ru_history_job` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT NULL, + `LOCK_EXP_TIME_` timestamp(3) NULL DEFAULT NULL, + `LOCK_OWNER_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `RETRIES_` int NULL DEFAULT NULL, + `EXCEPTION_STACK_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `EXCEPTION_MSG_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `HANDLER_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `HANDLER_CFG_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CUSTOM_VALUES_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ADV_HANDLER_CFG_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CREATE_TIME_` timestamp(3) NULL DEFAULT NULL, + `SCOPE_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT '', + PRIMARY KEY (`ID_`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_ru_history_job +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_ru_identitylink +-- ---------------------------- +DROP TABLE IF EXISTS `act_ru_identitylink`; +CREATE TABLE `act_ru_identitylink` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT NULL, + `GROUP_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `USER_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TASK_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROC_INST_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROC_DEF_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SUB_SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_DEFINITION_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_IDENT_LNK_USER`(`USER_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_IDENT_LNK_GROUP`(`GROUP_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_IDENT_LNK_SCOPE`(`SCOPE_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_IDENT_LNK_SUB_SCOPE`(`SUB_SCOPE_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_IDENT_LNK_SCOPE_DEF`(`SCOPE_DEFINITION_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_ATHRZ_PROCEDEF`(`PROC_DEF_ID_` ASC) USING BTREE, + INDEX `ACT_FK_TSKASS_TASK`(`TASK_ID_` ASC) USING BTREE, + INDEX `ACT_FK_IDL_PROCINST`(`PROC_INST_ID_` ASC) USING BTREE, + CONSTRAINT `ACT_FK_ATHRZ_PROCEDEF` FOREIGN KEY (`PROC_DEF_ID_`) REFERENCES `act_re_procdef` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_IDL_PROCINST` FOREIGN KEY (`PROC_INST_ID_`) REFERENCES `act_ru_execution` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_TSKASS_TASK` FOREIGN KEY (`TASK_ID_`) REFERENCES `act_ru_task` (`id_`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_ru_identitylink +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_ru_job +-- ---------------------------- +DROP TABLE IF EXISTS `act_ru_job`; +CREATE TABLE `act_ru_job` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT NULL, + `CATEGORY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `LOCK_EXP_TIME_` timestamp(3) NULL DEFAULT NULL, + `LOCK_OWNER_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `EXCLUSIVE_` tinyint(1) NULL DEFAULT NULL, + `EXECUTION_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROCESS_INSTANCE_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROC_DEF_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ELEMENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ELEMENT_NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SUB_SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_DEFINITION_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CORRELATION_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `RETRIES_` int NULL DEFAULT NULL, + `EXCEPTION_STACK_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `EXCEPTION_MSG_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DUEDATE_` timestamp(3) NULL DEFAULT NULL, + `REPEAT_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `HANDLER_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `HANDLER_CFG_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CUSTOM_VALUES_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CREATE_TIME_` timestamp(3) NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT '', + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_JOB_EXCEPTION_STACK_ID`(`EXCEPTION_STACK_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_JOB_CUSTOM_VALUES_ID`(`CUSTOM_VALUES_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_JOB_CORRELATION_ID`(`CORRELATION_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_JOB_SCOPE`(`SCOPE_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_JOB_SUB_SCOPE`(`SUB_SCOPE_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_JOB_SCOPE_DEF`(`SCOPE_DEFINITION_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_FK_JOB_EXECUTION`(`EXECUTION_ID_` ASC) USING BTREE, + INDEX `ACT_FK_JOB_PROCESS_INSTANCE`(`PROCESS_INSTANCE_ID_` ASC) USING BTREE, + INDEX `ACT_FK_JOB_PROC_DEF`(`PROC_DEF_ID_` ASC) USING BTREE, + CONSTRAINT `ACT_FK_JOB_CUSTOM_VALUES` FOREIGN KEY (`CUSTOM_VALUES_ID_`) REFERENCES `act_ge_bytearray` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_JOB_EXCEPTION` FOREIGN KEY (`EXCEPTION_STACK_ID_`) REFERENCES `act_ge_bytearray` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_JOB_EXECUTION` FOREIGN KEY (`EXECUTION_ID_`) REFERENCES `act_ru_execution` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_JOB_PROC_DEF` FOREIGN KEY (`PROC_DEF_ID_`) REFERENCES `act_re_procdef` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_JOB_PROCESS_INSTANCE` FOREIGN KEY (`PROCESS_INSTANCE_ID_`) REFERENCES `act_ru_execution` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_ru_job +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_ru_suspended_job +-- ---------------------------- +DROP TABLE IF EXISTS `act_ru_suspended_job`; +CREATE TABLE `act_ru_suspended_job` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT NULL, + `CATEGORY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `EXCLUSIVE_` tinyint(1) NULL DEFAULT NULL, + `EXECUTION_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROCESS_INSTANCE_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROC_DEF_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ELEMENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ELEMENT_NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SUB_SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_DEFINITION_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CORRELATION_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `RETRIES_` int NULL DEFAULT NULL, + `EXCEPTION_STACK_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `EXCEPTION_MSG_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DUEDATE_` timestamp(3) NULL DEFAULT NULL, + `REPEAT_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `HANDLER_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `HANDLER_CFG_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CUSTOM_VALUES_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CREATE_TIME_` timestamp(3) NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT '', + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_SUSPENDED_JOB_EXCEPTION_STACK_ID`(`EXCEPTION_STACK_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_SUSPENDED_JOB_CUSTOM_VALUES_ID`(`CUSTOM_VALUES_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_SUSPENDED_JOB_CORRELATION_ID`(`CORRELATION_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_SJOB_SCOPE`(`SCOPE_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_SJOB_SUB_SCOPE`(`SUB_SCOPE_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_SJOB_SCOPE_DEF`(`SCOPE_DEFINITION_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_FK_SUSPENDED_JOB_EXECUTION`(`EXECUTION_ID_` ASC) USING BTREE, + INDEX `ACT_FK_SUSPENDED_JOB_PROCESS_INSTANCE`(`PROCESS_INSTANCE_ID_` ASC) USING BTREE, + INDEX `ACT_FK_SUSPENDED_JOB_PROC_DEF`(`PROC_DEF_ID_` ASC) USING BTREE, + CONSTRAINT `ACT_FK_SUSPENDED_JOB_CUSTOM_VALUES` FOREIGN KEY (`CUSTOM_VALUES_ID_`) REFERENCES `act_ge_bytearray` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_SUSPENDED_JOB_EXCEPTION` FOREIGN KEY (`EXCEPTION_STACK_ID_`) REFERENCES `act_ge_bytearray` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_SUSPENDED_JOB_EXECUTION` FOREIGN KEY (`EXECUTION_ID_`) REFERENCES `act_ru_execution` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_SUSPENDED_JOB_PROC_DEF` FOREIGN KEY (`PROC_DEF_ID_`) REFERENCES `act_re_procdef` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_SUSPENDED_JOB_PROCESS_INSTANCE` FOREIGN KEY (`PROCESS_INSTANCE_ID_`) REFERENCES `act_ru_execution` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_ru_suspended_job +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_ru_task +-- ---------------------------- +DROP TABLE IF EXISTS `act_ru_task`; +CREATE TABLE `act_ru_task` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT NULL, + `EXECUTION_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROC_INST_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROC_DEF_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TASK_DEF_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SUB_SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_DEFINITION_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROPAGATED_STAGE_INST_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `STATE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PARENT_TASK_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DESCRIPTION_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TASK_DEF_KEY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `OWNER_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ASSIGNEE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DELEGATION_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PRIORITY_` int NULL DEFAULT NULL, + `CREATE_TIME_` timestamp(3) NULL DEFAULT NULL, + `IN_PROGRESS_TIME_` datetime(3) NULL DEFAULT NULL, + `IN_PROGRESS_STARTED_BY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CLAIM_TIME_` datetime(3) NULL DEFAULT NULL, + `CLAIMED_BY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SUSPENDED_TIME_` datetime(3) NULL DEFAULT NULL, + `SUSPENDED_BY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `IN_PROGRESS_DUE_DATE_` datetime(3) NULL DEFAULT NULL, + `DUE_DATE_` datetime(3) NULL DEFAULT NULL, + `CATEGORY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SUSPENSION_STATE_` int NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT '', + `FORM_KEY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `IS_COUNT_ENABLED_` tinyint NULL DEFAULT NULL, + `VAR_COUNT_` int NULL DEFAULT NULL, + `ID_LINK_COUNT_` int NULL DEFAULT NULL, + `SUB_TASK_COUNT_` int NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_TASK_CREATE`(`CREATE_TIME_` ASC) USING BTREE, + INDEX `ACT_IDX_TASK_SCOPE`(`SCOPE_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_TASK_SUB_SCOPE`(`SUB_SCOPE_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_TASK_SCOPE_DEF`(`SCOPE_DEFINITION_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_FK_TASK_EXE`(`EXECUTION_ID_` ASC) USING BTREE, + INDEX `ACT_FK_TASK_PROCINST`(`PROC_INST_ID_` ASC) USING BTREE, + INDEX `ACT_FK_TASK_PROCDEF`(`PROC_DEF_ID_` ASC) USING BTREE, + CONSTRAINT `ACT_FK_TASK_EXE` FOREIGN KEY (`EXECUTION_ID_`) REFERENCES `act_ru_execution` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_TASK_PROCDEF` FOREIGN KEY (`PROC_DEF_ID_`) REFERENCES `act_re_procdef` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_TASK_PROCINST` FOREIGN KEY (`PROC_INST_ID_`) REFERENCES `act_ru_execution` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_ru_task +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_ru_timer_job +-- ---------------------------- +DROP TABLE IF EXISTS `act_ru_timer_job`; +CREATE TABLE `act_ru_timer_job` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT NULL, + `CATEGORY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `LOCK_EXP_TIME_` timestamp(3) NULL DEFAULT NULL, + `LOCK_OWNER_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `EXCLUSIVE_` tinyint(1) NULL DEFAULT NULL, + `EXECUTION_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROCESS_INSTANCE_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROC_DEF_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ELEMENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `ELEMENT_NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SUB_SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_DEFINITION_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CORRELATION_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `RETRIES_` int NULL DEFAULT NULL, + `EXCEPTION_STACK_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `EXCEPTION_MSG_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DUEDATE_` timestamp(3) NULL DEFAULT NULL, + `REPEAT_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `HANDLER_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `HANDLER_CFG_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CUSTOM_VALUES_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CREATE_TIME_` timestamp(3) NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT '', + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_TIMER_JOB_EXCEPTION_STACK_ID`(`EXCEPTION_STACK_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_TIMER_JOB_CUSTOM_VALUES_ID`(`CUSTOM_VALUES_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_TIMER_JOB_CORRELATION_ID`(`CORRELATION_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_TIMER_JOB_DUEDATE`(`DUEDATE_` ASC) USING BTREE, + INDEX `ACT_IDX_TJOB_SCOPE`(`SCOPE_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_TJOB_SUB_SCOPE`(`SUB_SCOPE_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_TJOB_SCOPE_DEF`(`SCOPE_DEFINITION_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_FK_TIMER_JOB_EXECUTION`(`EXECUTION_ID_` ASC) USING BTREE, + INDEX `ACT_FK_TIMER_JOB_PROCESS_INSTANCE`(`PROCESS_INSTANCE_ID_` ASC) USING BTREE, + INDEX `ACT_FK_TIMER_JOB_PROC_DEF`(`PROC_DEF_ID_` ASC) USING BTREE, + CONSTRAINT `ACT_FK_TIMER_JOB_CUSTOM_VALUES` FOREIGN KEY (`CUSTOM_VALUES_ID_`) REFERENCES `act_ge_bytearray` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_TIMER_JOB_EXCEPTION` FOREIGN KEY (`EXCEPTION_STACK_ID_`) REFERENCES `act_ge_bytearray` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_TIMER_JOB_EXECUTION` FOREIGN KEY (`EXECUTION_ID_`) REFERENCES `act_ru_execution` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_TIMER_JOB_PROC_DEF` FOREIGN KEY (`PROC_DEF_ID_`) REFERENCES `act_re_procdef` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_TIMER_JOB_PROCESS_INSTANCE` FOREIGN KEY (`PROCESS_INSTANCE_ID_`) REFERENCES `act_ru_execution` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_ru_timer_job +-- ---------------------------- + +-- ---------------------------- +-- Table structure for act_ru_variable +-- ---------------------------- +DROP TABLE IF EXISTS `act_ru_variable`; +CREATE TABLE `act_ru_variable` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT NULL, + `TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `EXECUTION_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `PROC_INST_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TASK_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SUB_SCOPE_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `BYTEARRAY_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `DOUBLE_` double NULL DEFAULT NULL, + `LONG_` bigint NULL DEFAULT NULL, + `TEXT_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TEXT2_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `META_INFO_` varchar(4000) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `ACT_IDX_RU_VAR_SCOPE_ID_TYPE`(`SCOPE_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_IDX_RU_VAR_SUB_ID_TYPE`(`SUB_SCOPE_ID_` ASC, `SCOPE_TYPE_` ASC) USING BTREE, + INDEX `ACT_FK_VAR_BYTEARRAY`(`BYTEARRAY_ID_` ASC) USING BTREE, + INDEX `ACT_IDX_VARIABLE_TASK_ID`(`TASK_ID_` ASC) USING BTREE, + INDEX `ACT_FK_VAR_EXE`(`EXECUTION_ID_` ASC) USING BTREE, + INDEX `ACT_FK_VAR_PROCINST`(`PROC_INST_ID_` ASC) USING BTREE, + CONSTRAINT `ACT_FK_VAR_BYTEARRAY` FOREIGN KEY (`BYTEARRAY_ID_`) REFERENCES `act_ge_bytearray` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_VAR_EXE` FOREIGN KEY (`EXECUTION_ID_`) REFERENCES `act_ru_execution` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `ACT_FK_VAR_PROCINST` FOREIGN KEY (`PROC_INST_ID_`) REFERENCES `act_ru_execution` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of act_ru_variable +-- ---------------------------- + +-- ---------------------------- +-- Table structure for flw_channel_definition +-- ---------------------------- +DROP TABLE IF EXISTS `flw_channel_definition`; +CREATE TABLE `flw_channel_definition` ( + `ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `VERSION_` int NULL DEFAULT NULL, + `KEY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `CATEGORY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `DEPLOYMENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `CREATE_TIME_` datetime(3) NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `RESOURCE_NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `DESCRIPTION_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `TYPE_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `IMPLEMENTATION_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 1 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of flw_channel_definition +-- ---------------------------- + +-- ---------------------------- +-- Table structure for flw_event_definition +-- ---------------------------- +DROP TABLE IF EXISTS `flw_event_definition`; +CREATE TABLE `flw_event_definition` ( + `ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `VERSION_` int NULL DEFAULT NULL, + `KEY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `CATEGORY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `DEPLOYMENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `RESOURCE_NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `DESCRIPTION_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 1 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of flw_event_definition +-- ---------------------------- + +-- ---------------------------- +-- Table structure for flw_event_deployment +-- ---------------------------- +DROP TABLE IF EXISTS `flw_event_deployment`; +CREATE TABLE `flw_event_deployment` ( + `ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `CATEGORY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `DEPLOY_TIME_` datetime(3) NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `PARENT_DEPLOYMENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`ID_`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 1 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of flw_event_deployment +-- ---------------------------- + +-- ---------------------------- +-- Table structure for flw_event_resource +-- ---------------------------- +DROP TABLE IF EXISTS `flw_event_resource`; +CREATE TABLE `flw_event_resource` ( + `ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `NAME_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `DEPLOYMENT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL, + `RESOURCE_BYTES_` longblob NULL, + PRIMARY KEY (`ID_`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 1 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of flw_event_resource +-- ---------------------------- + +-- ---------------------------- +-- Table structure for flw_ru_batch +-- ---------------------------- +DROP TABLE IF EXISTS `flw_ru_batch`; +CREATE TABLE `flw_ru_batch` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT NULL, + `TYPE_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `SEARCH_KEY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SEARCH_KEY2_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CREATE_TIME_` datetime(3) NOT NULL, + `COMPLETE_TIME_` datetime(3) NULL DEFAULT NULL, + `STATUS_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `BATCH_DOC_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT '', + PRIMARY KEY (`ID_`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of flw_ru_batch +-- ---------------------------- + +-- ---------------------------- +-- Table structure for flw_ru_batch_part +-- ---------------------------- +DROP TABLE IF EXISTS `flw_ru_batch_part`; +CREATE TABLE `flw_ru_batch_part` ( + `ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `REV_` int NULL DEFAULT NULL, + `BATCH_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TYPE_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NOT NULL, + `SCOPE_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SUB_SCOPE_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SCOPE_TYPE_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SEARCH_KEY_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `SEARCH_KEY2_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `CREATE_TIME_` datetime(3) NOT NULL, + `COMPLETE_TIME_` datetime(3) NULL DEFAULT NULL, + `STATUS_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `RESULT_DOC_ID_` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL, + `TENANT_ID_` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT '', + PRIMARY KEY (`ID_`) USING BTREE, + INDEX `FLW_IDX_BATCH_PART`(`BATCH_ID_` ASC) USING BTREE, + CONSTRAINT `FLW_FK_BATCH_PART_PARENT` FOREIGN KEY (`BATCH_ID_`) REFERENCES `flw_ru_batch` (`ID_`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of flw_ru_batch_part +-- ---------------------------- + +-- ---------------------------- +-- Table structure for form_data +-- ---------------------------- +DROP TABLE IF EXISTS `form_data`; +CREATE TABLE `form_data` ( + `data_id` bigint NOT NULL AUTO_INCREMENT COMMENT '数据ID', + `form_id` bigint NOT NULL COMMENT '关联的表单ID', + `form_version` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '表单版本(与模板表版本一致)', + `data_content` json NOT NULL COMMENT '表单数据内容(JSON格式)', + `status` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'draft' COMMENT '数据状态(draft, submitted, approved, rejected)', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + PRIMARY KEY (`data_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '表单数据表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of form_data +-- ---------------------------- + +-- ---------------------------- +-- Table structure for form_template +-- ---------------------------- +DROP TABLE IF EXISTS `form_template`; +CREATE TABLE `form_template` ( + `form_id` bigint NOT NULL AUTO_INCREMENT COMMENT '表单ID', + `form_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '表单名称', + `form_schema` json NULL COMMENT '表单JSON Schema(vForm配置)', + `form_version` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '1.0.0' COMMENT '表单版本(语义化版本)', + `form_status` varchar(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '0' COMMENT '发布状态(0: 草稿, 1: 已发布, 2: 已停用)', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + PRIMARY KEY (`form_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '表单模板表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of form_template +-- ---------------------------- + +-- ---------------------------- +-- Table structure for gen_join_table +-- ---------------------------- +DROP TABLE IF EXISTS `gen_join_table`; +CREATE TABLE `gen_join_table` ( + `table_id` bigint NOT NULL COMMENT '表编号', + `left_table_id` bigint NOT NULL COMMENT '左表名称', + `right_table_id` bigint NOT NULL COMMENT '右表编号', + `left_table_alias` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '左表别名', + `right_table_alias` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '右表别名', + `left_table_fk` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '左表关联键', + `right_table_fk` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '右表关联键', + `join_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '关联类型', + `join_columns` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '关联字段', + `order_num` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '序号', + `new_table_id` bigint NOT NULL COMMENT '新表编号', + PRIMARY KEY (`table_id`, `right_table_id`, `left_table_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成关联表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of gen_join_table +-- ---------------------------- + +-- ---------------------------- +-- Table structure for gen_table +-- ---------------------------- +DROP TABLE IF EXISTS `gen_table`; +CREATE TABLE `gen_table` ( + `table_id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `table_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '表名称', + `table_alias` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '表别名', + `table_comment` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '表描述', + `have_sub_column` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '0' COMMENT '是否含有关联字段', + `sub_table_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '关联子表的表名', + `sub_table_fk_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '子表关联的外键名', + `class_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '实体类名称', + `tpl_category` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'crud' COMMENT '使用的模板(crud单表操作 tree树表操作)', + `tpl_web_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'element-plus' COMMENT '使用的模板类型', + `package_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '生成包路径', + `module_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '生成模块名', + `business_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '生成业务名', + `function_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '生成功能名', + `function_author` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '生成功能作者', + `gen_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '0' COMMENT '生成代码方式(0zip压缩包 1自定义路径)', + `gen_path` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '/' COMMENT '生成路径(不填默认项目路径)', + `options` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '其它生成选项', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`table_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成业务表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of gen_table +-- ---------------------------- +INSERT INTO `gen_table` VALUES (1, 'html_pages', 'hp', '司法局法律规定、典型案例、表单下载表', '0', NULL, NULL, 'HtmlPages', 'crud', 'element-plus', 'com.boyue.hasfj', 'hasfj', 'hasfjpages', '司法局法律规定、典型案例、单下载', 'boyue', '0', '/', '{\"parentMenuId\":2087}', 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02', NULL); + +-- ---------------------------- +-- Table structure for gen_table_column +-- ---------------------------- +DROP TABLE IF EXISTS `gen_table_column`; +CREATE TABLE `gen_table_column` ( + `column_id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `table_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '归属表编号', + `column_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '列名称', + `column_comment` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '列描述', + `column_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '列类型', + `java_type` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'JAVA类型', + `java_field` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'JAVA字段名', + `is_pk` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '是否主键(1是)', + `is_increment` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '是否自增(1是)', + `is_required` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '是否必填(1是)', + `is_insert` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '是否为插入字段(1是)', + `is_edit` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '是否编辑字段(1是)', + `is_list` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '是否列表字段(1是)', + `is_query` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '是否查询字段(1是)', + `query_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'EQ' COMMENT '查询方式(等于、不等于、大于、小于、范围)', + `html_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)', + `dict_type` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '字典类型', + `sort` int NULL DEFAULT NULL COMMENT '排序', + `sub_column_table_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '关联表名称', + `sub_column_fk_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '关联字段名称', + `sub_column_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '映射字段名称', + `sub_column_java_field` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '映射字段JAVA字段名', + `sub_column_java_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '映射字段JAVA类型', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`column_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 15 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '代码生成业务表字段' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of gen_table_column +-- ---------------------------- +INSERT INTO `gen_table_column` VALUES (1, '1', 'id', '序号', 'int(11)', 'Long', 'id', '1', '0', '0', '0', NULL, '1', NULL, 'EQ', 'input', '', 1, NULL, NULL, NULL, NULL, NULL, 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02'); +INSERT INTO `gen_table_column` VALUES (2, '1', 'format_id', '访问id', 'varchar(6)', 'String', 'formatId', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 2, NULL, NULL, NULL, NULL, NULL, 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02'); +INSERT INTO `gen_table_column` VALUES (3, '1', 'title', '页面标题', 'varchar(200)', 'String', 'title', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'input', '', 3, NULL, NULL, NULL, NULL, NULL, 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02'); +INSERT INTO `gen_table_column` VALUES (4, '1', 'content', 'HTML内容', 'text', 'String', 'content', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'editor', '', 4, NULL, NULL, NULL, NULL, NULL, 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02'); +INSERT INTO `gen_table_column` VALUES (5, '1', 'page_type', '页面类型:法律规定 law、典型案例 case、表单下载from', 'enum(\'law\',\'case\',\'form\')', 'String', 'pageType', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'select', '', 5, NULL, NULL, NULL, NULL, NULL, 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02'); +INSERT INTO `gen_table_column` VALUES (6, '1', 'page_url', '页面访问URL', 'varchar(255)', 'String', 'pageUrl', '0', '0', '1', '1', '1', '1', '0', 'EQ', 'input', '', 6, NULL, NULL, NULL, NULL, NULL, 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02'); +INSERT INTO `gen_table_column` VALUES (7, '1', 'create_time', '创建时间', 'datetime', 'LocalDate', 'createTime', '0', '0', '1', '1', NULL, '1', NULL, 'EQ', 'datetime', '', 7, NULL, NULL, NULL, NULL, NULL, 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02'); +INSERT INTO `gen_table_column` VALUES (8, '1', 'update_time', '更新时间', 'datetime', 'LocalDate', 'updateTime', '0', '0', '1', '1', '1', '1', NULL, 'EQ', 'datetime', '', 8, NULL, NULL, NULL, NULL, NULL, 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02'); +INSERT INTO `gen_table_column` VALUES (9, '1', 'status', '状态:1-启用,0-禁用', 'tinyint(4)', 'Integer', 'status', '0', '0', '1', '1', '1', '1', '1', 'EQ', 'radio', '', 9, NULL, NULL, NULL, NULL, NULL, 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02'); +INSERT INTO `gen_table_column` VALUES (10, '1', 'sort_order', '排序序号,数值越小排序越靠前,用于自定义显示顺序', 'int(11)', 'Long', 'sortOrder', '0', '0', '1', '1', '1', '1', '0', 'EQ', 'input', '', 10, NULL, NULL, NULL, NULL, NULL, 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02'); +INSERT INTO `gen_table_column` VALUES (11, '1', 'view_count', '浏览次数,记录页面被访问的总次数', 'int(11)', 'Long', 'viewCount', '0', '0', '1', '1', '1', '1', '0', 'EQ', 'input', '', 11, NULL, NULL, NULL, NULL, NULL, 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02'); +INSERT INTO `gen_table_column` VALUES (12, '1', 'author', '作者/负责人,记录页面的创建者或负责维护的人员', 'varchar(50)', 'String', 'author', '0', '0', '0', '1', '1', '1', '0', 'EQ', 'input', '', 12, NULL, NULL, NULL, NULL, NULL, 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02'); +INSERT INTO `gen_table_column` VALUES (13, '1', 'keywords', 'SEO关键词,用于搜索引擎优化', 'varchar(200)', 'String', 'keywords', '0', '0', '0', '1', '1', '1', '0', 'EQ', 'input', '', 13, NULL, NULL, NULL, NULL, NULL, 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02'); +INSERT INTO `gen_table_column` VALUES (14, '1', 'description', 'SEO描述,用于搜索引擎结果展示的页面摘要', 'varchar(500)', 'String', 'description', '0', '0', '0', '1', '1', '1', '0', 'EQ', 'textarea', '', 14, NULL, NULL, NULL, NULL, NULL, 'admin', '2025-05-28 18:57:49', '', '2025-05-28 19:09:02'); + +-- ---------------------------- +-- Table structure for html_pages +-- ---------------------------- +DROP TABLE IF EXISTS `html_pages`; +CREATE TABLE `html_pages` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `format_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `page_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `page_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, + `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL, + `attachment_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, + `author` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, + `status` int NULL DEFAULT 1, + `sort_order` bigint NULL DEFAULT 100, + `view_count` bigint NULL DEFAULT 0, + `keywords` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, + `description` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, + `update_time` datetime NULL DEFAULT NULL, + `create_time` datetime NULL DEFAULT NULL, + `multi_attachments` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '多附件URLs,JSON格式存储多个附件URL及名称', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_html_pages_format_id`(`format_id` ASC) USING BTREE, + INDEX `idx_html_pages_page_type`(`page_type` ASC) USING BTREE, + INDEX `idx_html_pages_status`(`status` ASC) USING BTREE, + INDEX `idx_html_pages_sort_order`(`sort_order` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 451 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '页面内容' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of html_pages +-- ---------------------------- +INSERT INTO `html_pages` VALUES (1, '000001', 'law', '/show.html?Id=000001', '如何确定企业形式', '

      《公司法》《个人独资企业法》《合伙企业法》

      ', '', '法律法规编辑部', 1, 1, 221, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-06-02 19:45:04', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (2, '000001', 'case', '/showcase.html?Id=000001', '如何确定企业形式', '

      李某与王某成立“普通合伙企业”经营餐饮,后因亏损负债100万元,法院判令李某、王某承担无限连带责任。

      ', NULL, '案例分析组', 1, 2, 4, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-29 15:08:06', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (3, '000001', 'form', '/table.html?Id=000001', '如何确定企业形式', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', '', '表单管理员', 1, 3, 11, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-06-01 11:03:49', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (4, '000002', 'law', '/show.html?Id=000002', '有限责任公司和股份有限公司的区别是什么?', '

      《证券法》第七十八条,发行人及法律、行政法规和国务院证券监督管理机构规定的其他信息披露义务人,应当及时依法履行信息披露义务。信息披露义务人披露的信息,应当真实、准确、完整,简明清晰,通俗易懂,不得有虚假记载、误导性陈述或者重大遗漏。

      ', NULL, '法律法规编辑部', 1, 4, 17, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-29 15:11:13', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (5, '000002', 'case', '/showcase.html?Id=000002', '有限责任公司和股份有限公司的区别是什么?', '

      某科技创业团队初期仅3名股东,误以为股份公司“更规范易融资”,直接注册为股份公司。因股份公司需设股东大会、董事会、监事会,团队人数不足,决策效率低下;同时需定期披露年报、重大事项,合规成本高。天使投资机构因其结构复杂、股权转让受限(需股东大会批准)放弃投资,最终公司因资金链断裂解散清算。

      ', NULL, '案例分析组', 1, 5, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-29 15:11:33', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (6, '000002', 'form', '/table.html?Id=000002', '有限责任公司和股份有限公司的区别是什么?', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 6, 1, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-29 15:09:11', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (7, '000003', 'law', '/show.html?Id=000003', '如何确定企业名称?', '

      《企业名称登记管理规定》《企业名称登记管理规定实施办法》。

      ', NULL, '法律法规编辑部', 1, 7, 15, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-29 15:11:54', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (8, '000003', 'case', '/showcase.html?Id=000003', '如何确定企业名称?', '

      甲公司注册名称为“上海特斯拉新能源科技有限公司”,未经特斯拉公司授权。法院认定其名称攀附知名品牌商誉,构成不正当竞争,判令更名并赔偿20万元。

      ', NULL, '案例分析组', 1, 8, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-29 15:12:12', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (9, '000003', 'form', '/table.html?Id=000003', '如何确定企业名称?', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 9, 2, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-29 15:09:50', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (10, '000004', 'law', '/show.html?Id=000004', '法定代表人的要求有哪些?', '

      《公司法》第十条,公司的法定代表人按照公司章程的规定,由代表公司执行公司事务的董事或者经理担任。担任法定代表人的董事或者经理辞任的,视为同时辞去法定代表人。法定代表人辞任的,公司应当在法定代表人辞任之日起三十日内确定新的法定代表人。第十一条:法定代表人以公司名义从事的民事活动,其法律后果由公司承受。公司章程或者股东会对法定代表人职权的限制,不得对抗善意相对人。法定代表人因执行职务造成他人损害的,由公司承担民事责任。公司承担民事责任后,依照法律或者公司章程的规定,可以向有过错的法定代表人追偿。

      ', NULL, '法律法规编辑部', 1, 10, 7, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-29 15:12:43', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (11, '000004', 'case', '/showcase.html?Id=000004', '法定代表人的要求有哪些?', '

      某公司法定代表人李某因个人债务被列为失信被执行人,法院限制其高消费。公司向银行申请贷款时,因李某信用瑕疵遭拒,资金链断裂导致停产。市场监管部门责令更换法定代表人,但因股东争议拖延,最终公司破产清算。

      ', NULL, '案例分析组', 1, 11, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-29 15:13:17', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (12, '000004', 'form', '/table.html?Id=000004', '法定代表人的要求有哪些?', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 12, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-29 15:13:35', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (13, '000005', 'law', '/show.html?Id=000005', '董事、监事、高级管理人员的资格限制有哪些?', '

      《公司法》第一百七十八条,有下列情形之一的,不得担任公司的董事、监事、高级管理人员:(一)无民事行为能力或者限制民事行为能力;(二)因贪污、贿赂、侵占财产、挪用财产或者破坏社会主义市场经济秩序,被判处刑罚,或者因犯罪被剥夺政治权利,执行期满未逾五年,被宣告缓刑的,自缓刑考验期满之日起未逾二年;(三)担任破产清算的公司、企业的董事或者厂长、经理,对该公司、企业的破产负有个人责任的,自该公司、企业破产清算完结之日起未逾三年;(四)担任因违法被吊销营业执照、责令关闭的公司、企业的法定代表人,并负有个人责任的,自该公司、企业被吊销营业执照、责令关闭之日起未逾三年;(五)个人因所负数额较大债务到期未清偿被人民法院列为失信被执行人。

      ', NULL, '法律法规编辑部', 1, 13, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-29 15:14:05', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (14, '000005', 'case', '/showcase.html?Id=000005', '董事、监事、高级管理人员的资格限制有哪些?', '

      某公司股东李某(公务员)通过亲属代持股权,后因公司债务纠纷被债权人揭发。法院认定代持协议无效,李某需退还全部股权收益,公司被处罚款10万元。

      ', NULL, '案例分析组', 1, 14, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-29 15:14:42', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (15, '000005', 'form', '/table.html?Id=000005', '董事、监事、高级管理人员的资格限制有哪些?', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 15, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-29 15:14:52', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (16, '000006', 'law', '/show.html?Id=000006', '隐名股东有哪些常见法律风险?', '

      最高人民法院关于适用《中华人民共和国公司法》若干问题的规定(三)第二十四条,有限责任公司的实际出资人与名义出资人订立合同,约定由实际出资人出资并享有投资权益,以名义出资人为名义股东,实际出资人与名义股东对该合同效力发生争议的,如无法律规定的无效情形,人民法院应当认定该合同有效。前款规定的实际出资人与名义股东因投资权益的归属发生争议,实际出资人以其实际履行了出资义务为由向名义股东主张权利的,人民法院应予支持。名义股东以公司股东名册记载、公司登记机关登记为由否认实际出资人权利的,人民法院不予支持。实际出资人未经公司其他股东半数以上同意,请求公司变更股东、签发出资证明书、记载于股东名册、记载于公司章程并办理公司登记机关登记的,人民法院不予支持。

      ', NULL, '法律法规编辑部', 1, 16, 1, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-29 15:15:42', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (17, '000006', 'case', '/showcase.html?Id=000006', '隐名股东有哪些常见法律风险?', '

      隐名股东李某委托张某代持某公司10%股权,张某未经同意将股权转让给王某,王某不知情且支付合理价款。王某善意取得股权,李某仅能要求张某赔偿损失。

      ', NULL, '案例分析组', 1, 17, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-29 15:16:04', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (18, '000006', 'form', '/table.html?Id=000006', '隐名股东有哪些常见法律风险?', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 18, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-29 15:16:19', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (19, '000007', 'law', '/show.html?Id=000007', '如何保持创始人在公司的控制权?', '

      《公司法》第六十五条,股东会会议由股东按照出资比例行使表决权;但是,公司章程另有规定的除外。

      ', NULL, '法律法规编辑部', 1, 19, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-29 15:16:52', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (20, '000007', 'case', '/showcase.html?Id=000007', '如何保持创始人在公司的控制权?', '

      某公司创始人通过“AB股”架构(1股10票投票权),即使持股比例仅15%,仍掌控董事会80%席位。公司上市后,其投票权超80%,确保战略决策权。

      ', NULL, '案例分析组', 1, 20, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-29 15:17:15', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (21, '000007', 'form', '/table.html?Id=000007', '如何保持创始人在公司的控制权?', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 21, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-29 15:17:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (22, '000008', 'law', '/show.html?Id=000008', '如何防止出现公司僵局?', '

      《公司法》第二百三十一条,公司经营管理发生严重困难,继续存续会使股东利益受到重大损失,通过其他途径不能解决的,持有公司百分之十以上表决权的股东,可以请求人民法院解散公司。

      最高人民法院关于适用《中华人民共和国公司法》若干问题的规定(二)第一条,单独或者合计持有公司全部股东表决权百分之十以上的股东,以下列事由之一提起解散公司诉讼,并符合公司法第一百八十二条规定的,人民法院应予受理:(一)公司持续两年以上无法召开股东会或者股东大会,公司经营管理发生严重困难的;(二)股东表决时无法达到法定或者公司章程规定的比例,持续两年以上不能做出有效的股东会或者股东大会决议,公司经营管理发生严重困难的;(三)公司董事长期冲突,且无法通过股东会或者股东大会解决,公司经营管理发生严重困难的;


      ', NULL, '法律法规编辑部', 1, 22, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-29 15:18:00', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (23, '000008', 'case', '/showcase.html?Id=000008', '如何防止出现公司僵局?', '

      某零售企业两位股东各持50%股权,因经营理念严重冲突,股东会无法通过重大决策(如拓展新店、引入投资),公司现金流枯竭,员工离职,被法院裁定解散。

      ', NULL, '案例分析组', 1, 23, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-29 15:18:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (24, '000008', 'form', '/table.html?Id=000008', '如何防止出现公司僵局?', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 24, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-29 15:18:40', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (25, '000009', 'law', '/show.html?Id=000009', '如何合理确定注册资本?', '

      《公司法》第四条,有限责任公司的股东以其认缴的出资额为限对公司承担责任;股份有限公司的股东以其认购的股份为限对公司承担责任。

      ', NULL, '法律法规编辑部', 1, 25, 1, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-29 15:19:12', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (26, '000009', 'case', '/showcase.html?Id=000009', '如何合理确定注册资本?', '

      某公司注册资本8000万元(认缴期30年),实际运营资金仅200万元。后因合同纠纷负债500万元,法院认定股东恶意设置超长认缴期,判令股东立即补缴。


      ', NULL, '案例分析组', 1, 26, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-29 15:19:32', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (27, '000009', 'form', '/table.html?Id=000009', '如何合理确定注册资本?', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 27, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-29 15:19:44', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (28, '000010', 'law', '/show.html?Id=000010', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 28, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (29, '000010', 'case', '/showcase.html?Id=000010', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 29, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (30, '000010', 'form', '/table.html?Id=000010', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 30, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (31, '000011', 'law', '/show.html?Id=000011', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 31, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (32, '000011', 'case', '/showcase.html?Id=000011', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 32, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (33, '000011', 'form', '/table.html?Id=000011', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 33, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (34, '000012', 'law', '/show.html?Id=000012', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 34, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (35, '000012', 'case', '/showcase.html?Id=000012', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 35, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (36, '000012', 'form', '/table.html?Id=000012', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 36, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (37, '000013', 'law', '/show.html?Id=000013', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 37, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (38, '000013', 'case', '/showcase.html?Id=000013', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 38, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (39, '000013', 'form', '/table.html?Id=000013', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 39, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (40, '000014', 'law', '/show.html?Id=000014', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 40, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (41, '000014', 'case', '/showcase.html?Id=000014', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 41, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (42, '000014', 'form', '/table.html?Id=000014', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 42, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (43, '000015', 'law', '/show.html?Id=000015', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 43, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (44, '000015', 'case', '/showcase.html?Id=000015', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 44, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (45, '000015', 'form', '/table.html?Id=000015', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 45, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (46, '000016', 'law', '/show.html?Id=000016', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 46, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (47, '000016', 'case', '/showcase.html?Id=000016', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 47, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (48, '000016', 'form', '/table.html?Id=000016', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 48, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (49, '000017', 'law', '/show.html?Id=000017', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 49, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (50, '000017', 'case', '/showcase.html?Id=000017', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 50, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (51, '000017', 'form', '/table.html?Id=000017', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 51, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (52, '000018', 'law', '/show.html?Id=000018', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 52, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (53, '000018', 'case', '/showcase.html?Id=000018', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 53, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (54, '000018', 'form', '/table.html?Id=000018', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 54, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (55, '000019', 'law', '/show.html?Id=000019', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 55, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (56, '000019', 'case', '/showcase.html?Id=000019', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 56, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (57, '000019', 'form', '/table.html?Id=000019', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 57, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (58, '000020', 'law', '/show.html?Id=000020', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 58, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (59, '000020', 'case', '/showcase.html?Id=000020', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 59, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (60, '000020', 'form', '/table.html?Id=000020', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 60, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (61, '000021', 'law', '/show.html?Id=000021', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 61, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (62, '000021', 'case', '/showcase.html?Id=000021', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 62, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (63, '000021', 'form', '/table.html?Id=000021', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 63, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (64, '000022', 'law', '/show.html?Id=000022', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 64, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (65, '000022', 'case', '/showcase.html?Id=000022', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 65, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (66, '000022', 'form', '/table.html?Id=000022', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 66, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (67, '000023', 'law', '/show.html?Id=000023', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 67, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (68, '000023', 'case', '/showcase.html?Id=000023', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 68, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (69, '000023', 'form', '/table.html?Id=000023', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 69, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (70, '000024', 'law', '/show.html?Id=000024', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 70, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (71, '000024', 'case', '/showcase.html?Id=000024', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 71, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (72, '000024', 'form', '/table.html?Id=000024', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 72, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (73, '000025', 'law', '/show.html?Id=000025', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 73, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (74, '000025', 'case', '/showcase.html?Id=000025', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 74, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (75, '000025', 'form', '/table.html?Id=000025', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 75, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (76, '000026', 'law', '/show.html?Id=000026', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 76, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (77, '000026', 'case', '/showcase.html?Id=000026', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 77, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (78, '000026', 'form', '/table.html?Id=000026', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 78, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (79, '000027', 'law', '/show.html?Id=000027', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 79, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (80, '000027', 'case', '/showcase.html?Id=000027', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 80, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (81, '000027', 'form', '/table.html?Id=000027', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 81, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (82, '000028', 'law', '/show.html?Id=000028', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 82, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (83, '000028', 'case', '/showcase.html?Id=000028', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 83, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (84, '000028', 'form', '/table.html?Id=000028', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 84, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (85, '000029', 'law', '/show.html?Id=000029', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 85, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (86, '000029', 'case', '/showcase.html?Id=000029', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 86, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (87, '000029', 'form', '/table.html?Id=000029', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 87, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (88, '000030', 'law', '/show.html?Id=000030', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 88, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (89, '000030', 'case', '/showcase.html?Id=000030', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 89, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (90, '000030', 'form', '/table.html?Id=000030', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 90, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (91, '000031', 'law', '/show.html?Id=000031', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 91, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (92, '000031', 'case', '/showcase.html?Id=000031', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 92, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (93, '000031', 'form', '/table.html?Id=000031', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 93, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (94, '000032', 'law', '/show.html?Id=000032', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 94, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (95, '000032', 'case', '/showcase.html?Id=000032', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 95, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (96, '000032', 'form', '/table.html?Id=000032', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 96, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (97, '000033', 'law', '/show.html?Id=000033', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 97, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (98, '000033', 'case', '/showcase.html?Id=000033', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 98, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (99, '000033', 'form', '/table.html?Id=000033', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 99, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (100, '000034', 'law', '/show.html?Id=000034', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 100, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (101, '000034', 'case', '/showcase.html?Id=000034', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 101, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (102, '000034', 'form', '/table.html?Id=000034', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 102, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (103, '000035', 'law', '/show.html?Id=000035', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 103, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (104, '000035', 'case', '/showcase.html?Id=000035', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 104, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (105, '000035', 'form', '/table.html?Id=000035', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 105, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (106, '000036', 'law', '/show.html?Id=000036', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 106, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (107, '000036', 'case', '/showcase.html?Id=000036', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 107, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (108, '000036', 'form', '/table.html?Id=000036', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 108, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (109, '000037', 'law', '/show.html?Id=000037', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 109, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (110, '000037', 'case', '/showcase.html?Id=000037', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 110, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (111, '000037', 'form', '/table.html?Id=000037', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 111, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (112, '000038', 'law', '/show.html?Id=000038', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 112, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (113, '000038', 'case', '/showcase.html?Id=000038', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 113, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (114, '000038', 'form', '/table.html?Id=000038', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 114, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (115, '000039', 'law', '/show.html?Id=000039', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 115, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (116, '000039', 'case', '/showcase.html?Id=000039', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 116, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (117, '000039', 'form', '/table.html?Id=000039', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 117, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (118, '000040', 'law', '/show.html?Id=000040', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 118, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (119, '000040', 'case', '/showcase.html?Id=000040', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 119, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (120, '000040', 'form', '/table.html?Id=000040', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 120, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (121, '000041', 'law', '/show.html?Id=000041', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 121, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (122, '000041', 'case', '/showcase.html?Id=000041', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 122, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (123, '000041', 'form', '/table.html?Id=000041', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 123, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (124, '000042', 'law', '/show.html?Id=000042', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 124, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (125, '000042', 'case', '/showcase.html?Id=000042', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 125, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (126, '000042', 'form', '/table.html?Id=000042', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 126, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (127, '000043', 'law', '/show.html?Id=000043', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 127, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (128, '000043', 'case', '/showcase.html?Id=000043', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 128, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (129, '000043', 'form', '/table.html?Id=000043', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 129, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (130, '000044', 'law', '/show.html?Id=000044', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 130, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (131, '000044', 'case', '/showcase.html?Id=000044', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 131, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (132, '000044', 'form', '/table.html?Id=000044', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 132, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (133, '000045', 'law', '/show.html?Id=000045', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 133, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (134, '000045', 'case', '/showcase.html?Id=000045', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 134, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (135, '000045', 'form', '/table.html?Id=000045', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 135, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (136, '000046', 'law', '/show.html?Id=000046', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 136, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (137, '000046', 'case', '/showcase.html?Id=000046', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 137, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (138, '000046', 'form', '/table.html?Id=000046', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 138, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (139, '000047', 'law', '/show.html?Id=000047', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 139, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (140, '000047', 'case', '/showcase.html?Id=000047', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 140, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (141, '000047', 'form', '/table.html?Id=000047', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 141, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (142, '000048', 'law', '/show.html?Id=000048', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 142, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (143, '000048', 'case', '/showcase.html?Id=000048', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 143, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (144, '000048', 'form', '/table.html?Id=000048', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 144, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (145, '000049', 'law', '/show.html?Id=000049', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 145, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (146, '000049', 'case', '/showcase.html?Id=000049', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 146, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (147, '000049', 'form', '/table.html?Id=000049', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 147, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (148, '000050', 'law', '/show.html?Id=000050', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 148, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (149, '000050', 'case', '/showcase.html?Id=000050', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 149, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (150, '000050', 'form', '/table.html?Id=000050', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 150, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (151, '000051', 'law', '/show.html?Id=000051', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 151, 1, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:11:40', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (152, '000051', 'case', '/showcase.html?Id=000051', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 152, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (153, '000051', 'form', '/table.html?Id=000051', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 153, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (154, '000052', 'law', '/show.html?Id=000052', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 154, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (155, '000052', 'case', '/showcase.html?Id=000052', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 155, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (156, '000052', 'form', '/table.html?Id=000052', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 156, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (157, '000053', 'law', '/show.html?Id=000053', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 157, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (158, '000053', 'case', '/showcase.html?Id=000053', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 158, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (159, '000053', 'form', '/table.html?Id=000053', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 159, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (160, '000054', 'law', '/show.html?Id=000054', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 160, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (161, '000054', 'case', '/showcase.html?Id=000054', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 161, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (162, '000054', 'form', '/table.html?Id=000054', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 162, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (163, '000055', 'law', '/show.html?Id=000055', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 163, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (164, '000055', 'case', '/showcase.html?Id=000055', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 164, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (165, '000055', 'form', '/table.html?Id=000055', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 165, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (166, '000056', 'law', '/show.html?Id=000056', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 166, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (167, '000056', 'case', '/showcase.html?Id=000056', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 167, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (168, '000056', 'form', '/table.html?Id=000056', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 168, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (169, '000057', 'law', '/show.html?Id=000057', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 169, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (170, '000057', 'case', '/showcase.html?Id=000057', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 170, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (171, '000057', 'form', '/table.html?Id=000057', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 171, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (172, '000058', 'law', '/show.html?Id=000058', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 172, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (173, '000058', 'case', '/showcase.html?Id=000058', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 173, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (174, '000058', 'form', '/table.html?Id=000058', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 174, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (175, '000059', 'law', '/show.html?Id=000059', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 175, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (176, '000059', 'case', '/showcase.html?Id=000059', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 176, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (177, '000059', 'form', '/table.html?Id=000059', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 177, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (178, '000060', 'law', '/show.html?Id=000060', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 178, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (179, '000060', 'case', '/showcase.html?Id=000060', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 179, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (180, '000060', 'form', '/table.html?Id=000060', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 180, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (181, '000061', 'law', '/show.html?Id=000061', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 181, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (182, '000061', 'case', '/showcase.html?Id=000061', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 182, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (183, '000061', 'form', '/table.html?Id=000061', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 183, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (184, '000062', 'law', '/show.html?Id=000062', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 184, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (185, '000062', 'case', '/showcase.html?Id=000062', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 185, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (186, '000062', 'form', '/table.html?Id=000062', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 186, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (187, '000063', 'law', '/show.html?Id=000063', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 187, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (188, '000063', 'case', '/showcase.html?Id=000063', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 188, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (189, '000063', 'form', '/table.html?Id=000063', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 189, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (190, '000064', 'law', '/show.html?Id=000064', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 190, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (191, '000064', 'case', '/showcase.html?Id=000064', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 191, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (192, '000064', 'form', '/table.html?Id=000064', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 192, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (193, '000065', 'law', '/show.html?Id=000065', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 193, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (194, '000065', 'case', '/showcase.html?Id=000065', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 194, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (195, '000065', 'form', '/table.html?Id=000065', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 195, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (196, '000066', 'law', '/show.html?Id=000066', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 196, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (197, '000066', 'case', '/showcase.html?Id=000066', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 197, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (198, '000066', 'form', '/table.html?Id=000066', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 198, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (199, '000067', 'law', '/show.html?Id=000067', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 199, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (200, '000067', 'case', '/showcase.html?Id=000067', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 200, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (201, '000067', 'form', '/table.html?Id=000067', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 201, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (202, '000068', 'law', '/show.html?Id=000068', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 202, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (203, '000068', 'case', '/showcase.html?Id=000068', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 203, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (204, '000068', 'form', '/table.html?Id=000068', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 204, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (205, '000069', 'law', '/show.html?Id=000069', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 205, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (206, '000069', 'case', '/showcase.html?Id=000069', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 206, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (207, '000069', 'form', '/table.html?Id=000069', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 207, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (208, '000070', 'law', '/show.html?Id=000070', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 208, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (209, '000070', 'case', '/showcase.html?Id=000070', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 209, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (210, '000070', 'form', '/table.html?Id=000070', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 210, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (211, '000071', 'law', '/show.html?Id=000071', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 211, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (212, '000071', 'case', '/showcase.html?Id=000071', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 212, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (213, '000071', 'form', '/table.html?Id=000071', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 213, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (214, '000072', 'law', '/show.html?Id=000072', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 214, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (215, '000072', 'case', '/showcase.html?Id=000072', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 215, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (216, '000072', 'form', '/table.html?Id=000072', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 216, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (217, '000073', 'law', '/show.html?Id=000073', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 217, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (218, '000073', 'case', '/showcase.html?Id=000073', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 218, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (219, '000073', 'form', '/table.html?Id=000073', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 219, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (220, '000074', 'law', '/show.html?Id=000074', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 220, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (221, '000074', 'case', '/showcase.html?Id=000074', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 221, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (222, '000074', 'form', '/table.html?Id=000074', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 222, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (223, '000075', 'law', '/show.html?Id=000075', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 223, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (224, '000075', 'case', '/showcase.html?Id=000075', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 224, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (225, '000075', 'form', '/table.html?Id=000075', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 225, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (226, '000076', 'law', '/show.html?Id=000076', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 226, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (227, '000076', 'case', '/showcase.html?Id=000076', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 227, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (228, '000076', 'form', '/table.html?Id=000076', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 228, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (229, '000077', 'law', '/show.html?Id=000077', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 229, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (230, '000077', 'case', '/showcase.html?Id=000077', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 230, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (231, '000077', 'form', '/table.html?Id=000077', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 231, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (232, '000078', 'law', '/show.html?Id=000078', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 232, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (233, '000078', 'case', '/showcase.html?Id=000078', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 233, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (234, '000078', 'form', '/table.html?Id=000078', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 234, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (235, '000079', 'law', '/show.html?Id=000079', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 235, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (236, '000079', 'case', '/showcase.html?Id=000079', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 236, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (237, '000079', 'form', '/table.html?Id=000079', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 237, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (238, '000080', 'law', '/show.html?Id=000080', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 238, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (239, '000080', 'case', '/showcase.html?Id=000080', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 239, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (240, '000080', 'form', '/table.html?Id=000080', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 240, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (241, '000081', 'law', '/show.html?Id=000081', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 241, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (242, '000081', 'case', '/showcase.html?Id=000081', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 242, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (243, '000081', 'form', '/table.html?Id=000081', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 243, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (244, '000082', 'law', '/show.html?Id=000082', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 244, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (245, '000082', 'case', '/showcase.html?Id=000082', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 245, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (246, '000082', 'form', '/table.html?Id=000082', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 246, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (247, '000083', 'law', '/show.html?Id=000083', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 247, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (248, '000083', 'case', '/showcase.html?Id=000083', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 248, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (249, '000083', 'form', '/table.html?Id=000083', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 249, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (250, '000084', 'law', '/show.html?Id=000084', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 250, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (251, '000084', 'case', '/showcase.html?Id=000084', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 251, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (252, '000084', 'form', '/table.html?Id=000084', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 252, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (253, '000085', 'law', '/show.html?Id=000085', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 253, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (254, '000085', 'case', '/showcase.html?Id=000085', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 254, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (255, '000085', 'form', '/table.html?Id=000085', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 255, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (256, '000086', 'law', '/show.html?Id=000086', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 256, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (257, '000086', 'case', '/showcase.html?Id=000086', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 257, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (258, '000086', 'form', '/table.html?Id=000086', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 258, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (259, '000087', 'law', '/show.html?Id=000087', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 259, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (260, '000087', 'case', '/showcase.html?Id=000087', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 260, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (261, '000087', 'form', '/table.html?Id=000087', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 261, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (262, '000088', 'law', '/show.html?Id=000088', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 262, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (263, '000088', 'case', '/showcase.html?Id=000088', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 263, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (264, '000088', 'form', '/table.html?Id=000088', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 264, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (265, '000089', 'law', '/show.html?Id=000089', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 265, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (266, '000089', 'case', '/showcase.html?Id=000089', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 266, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (267, '000089', 'form', '/table.html?Id=000089', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 267, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (268, '000090', 'law', '/show.html?Id=000090', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 268, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (269, '000090', 'case', '/showcase.html?Id=000090', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 269, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (270, '000090', 'form', '/table.html?Id=000090', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 270, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (271, '000091', 'law', '/show.html?Id=000091', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 271, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (272, '000091', 'case', '/showcase.html?Id=000091', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 272, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (273, '000091', 'form', '/table.html?Id=000091', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 273, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (274, '000092', 'law', '/show.html?Id=000092', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 274, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (275, '000092', 'case', '/showcase.html?Id=000092', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 275, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (276, '000092', 'form', '/table.html?Id=000092', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 276, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (277, '000093', 'law', '/show.html?Id=000093', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 277, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (278, '000093', 'case', '/showcase.html?Id=000093', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 278, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (279, '000093', 'form', '/table.html?Id=000093', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 279, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (280, '000094', 'law', '/show.html?Id=000094', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 280, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (281, '000094', 'case', '/showcase.html?Id=000094', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 281, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (282, '000094', 'form', '/table.html?Id=000094', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 282, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (283, '000095', 'law', '/show.html?Id=000095', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 283, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (284, '000095', 'case', '/showcase.html?Id=000095', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 284, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (285, '000095', 'form', '/table.html?Id=000095', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 285, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (286, '000096', 'law', '/show.html?Id=000096', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 286, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (287, '000096', 'case', '/showcase.html?Id=000096', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 287, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (288, '000096', 'form', '/table.html?Id=000096', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 288, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (289, '000097', 'law', '/show.html?Id=000097', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 289, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (290, '000097', 'case', '/showcase.html?Id=000097', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 290, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (291, '000097', 'form', '/table.html?Id=000097', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 291, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (292, '000098', 'law', '/show.html?Id=000098', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 292, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (293, '000098', 'case', '/showcase.html?Id=000098', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 293, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (294, '000098', 'form', '/table.html?Id=000098', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 294, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (295, '000099', 'law', '/show.html?Id=000099', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 295, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (296, '000099', 'case', '/showcase.html?Id=000099', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 296, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (297, '000099', 'form', '/table.html?Id=000099', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 297, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (298, '000100', 'law', '/show.html?Id=000100', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 298, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (299, '000100', 'case', '/showcase.html?Id=000100', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 299, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (300, '000100', 'form', '/table.html?Id=000100', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 300, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (301, '000101', 'law', '/show.html?Id=000101', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 301, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (302, '000101', 'case', '/showcase.html?Id=000101', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 302, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (303, '000101', 'form', '/table.html?Id=000101', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 303, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (304, '000102', 'law', '/show.html?Id=000102', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 304, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (305, '000102', 'case', '/showcase.html?Id=000102', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 305, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (306, '000102', 'form', '/table.html?Id=000102', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 306, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (307, '000103', 'law', '/show.html?Id=000103', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 307, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (308, '000103', 'case', '/showcase.html?Id=000103', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 308, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (309, '000103', 'form', '/table.html?Id=000103', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 309, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (310, '000104', 'law', '/show.html?Id=000104', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 310, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (311, '000104', 'case', '/showcase.html?Id=000104', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 311, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (312, '000104', 'form', '/table.html?Id=000104', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 312, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (313, '000105', 'law', '/show.html?Id=000105', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 313, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (314, '000105', 'case', '/showcase.html?Id=000105', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 314, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (315, '000105', 'form', '/table.html?Id=000105', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 315, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (316, '000106', 'law', '/show.html?Id=000106', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 316, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (317, '000106', 'case', '/showcase.html?Id=000106', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 317, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (318, '000106', 'form', '/table.html?Id=000106', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 318, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (319, '000107', 'law', '/show.html?Id=000107', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 319, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (320, '000107', 'case', '/showcase.html?Id=000107', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 320, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (321, '000107', 'form', '/table.html?Id=000107', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 321, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (322, '000108', 'law', '/show.html?Id=000108', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 322, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (323, '000108', 'case', '/showcase.html?Id=000108', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 323, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (324, '000108', 'form', '/table.html?Id=000108', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 324, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (325, '000109', 'law', '/show.html?Id=000109', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 325, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (326, '000109', 'case', '/showcase.html?Id=000109', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 326, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (327, '000109', 'form', '/table.html?Id=000109', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 327, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (328, '000110', 'law', '/show.html?Id=000110', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 328, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (329, '000110', 'case', '/showcase.html?Id=000110', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 329, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (330, '000110', 'form', '/table.html?Id=000110', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 330, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (331, '000111', 'law', '/show.html?Id=000111', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 331, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (332, '000111', 'case', '/showcase.html?Id=000111', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 332, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (333, '000111', 'form', '/table.html?Id=000111', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 333, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (334, '000112', 'law', '/show.html?Id=000112', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 334, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (335, '000112', 'case', '/showcase.html?Id=000112', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 335, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (336, '000112', 'form', '/table.html?Id=000112', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 336, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (337, '000113', 'law', '/show.html?Id=000113', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 337, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (338, '000113', 'case', '/showcase.html?Id=000113', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 338, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (339, '000113', 'form', '/table.html?Id=000113', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 339, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (340, '000114', 'law', '/show.html?Id=000114', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 340, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (341, '000114', 'case', '/showcase.html?Id=000114', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 341, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (342, '000114', 'form', '/table.html?Id=000114', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 342, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (343, '000115', 'law', '/show.html?Id=000115', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 343, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (344, '000115', 'case', '/showcase.html?Id=000115', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 344, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (345, '000115', 'form', '/table.html?Id=000115', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 345, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (346, '000116', 'law', '/show.html?Id=000116', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 346, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (347, '000116', 'case', '/showcase.html?Id=000116', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 347, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (348, '000116', 'form', '/table.html?Id=000116', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 348, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (349, '000117', 'law', '/show.html?Id=000117', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 349, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (350, '000117', 'case', '/showcase.html?Id=000117', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 350, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (351, '000117', 'form', '/table.html?Id=000117', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 351, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (352, '000118', 'law', '/show.html?Id=000118', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 352, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (353, '000118', 'case', '/showcase.html?Id=000118', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 353, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (354, '000118', 'form', '/table.html?Id=000118', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 354, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (355, '000119', 'law', '/show.html?Id=000119', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 355, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (356, '000119', 'case', '/showcase.html?Id=000119', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 356, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (357, '000119', 'form', '/table.html?Id=000119', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 357, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (358, '000120', 'law', '/show.html?Id=000120', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 358, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (359, '000120', 'case', '/showcase.html?Id=000120', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 359, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (360, '000120', 'form', '/table.html?Id=000120', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 360, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (361, '000121', 'law', '/show.html?Id=000121', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 361, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (362, '000121', 'case', '/showcase.html?Id=000121', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 362, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (363, '000121', 'form', '/table.html?Id=000121', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 363, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (364, '000122', 'law', '/show.html?Id=000122', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 364, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (365, '000122', 'case', '/showcase.html?Id=000122', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 365, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (366, '000122', 'form', '/table.html?Id=000122', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 366, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (367, '000123', 'law', '/show.html?Id=000123', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 367, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (368, '000123', 'case', '/showcase.html?Id=000123', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 368, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (369, '000123', 'form', '/table.html?Id=000123', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 369, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (370, '000124', 'law', '/show.html?Id=000124', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 370, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (371, '000124', 'case', '/showcase.html?Id=000124', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 371, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (372, '000124', 'form', '/table.html?Id=000124', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 372, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (373, '000125', 'law', '/show.html?Id=000125', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 373, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (374, '000125', 'case', '/showcase.html?Id=000125', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 374, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (375, '000125', 'form', '/table.html?Id=000125', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 375, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (376, '000126', 'law', '/show.html?Id=000126', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 376, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (377, '000126', 'case', '/showcase.html?Id=000126', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 377, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (378, '000126', 'form', '/table.html?Id=000126', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 378, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (379, '000127', 'law', '/show.html?Id=000127', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 379, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (380, '000127', 'case', '/showcase.html?Id=000127', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 380, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (381, '000127', 'form', '/table.html?Id=000127', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 381, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (382, '000128', 'law', '/show.html?Id=000128', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 382, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (383, '000128', 'case', '/showcase.html?Id=000128', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 383, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (384, '000128', 'form', '/table.html?Id=000128', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 384, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (385, '000129', 'law', '/show.html?Id=000129', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 385, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (386, '000129', 'case', '/showcase.html?Id=000129', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 386, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (387, '000129', 'form', '/table.html?Id=000129', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 387, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (388, '000130', 'law', '/show.html?Id=000130', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 388, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (389, '000130', 'case', '/showcase.html?Id=000130', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 389, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (390, '000130', 'form', '/table.html?Id=000130', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 390, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (391, '000131', 'law', '/show.html?Id=000131', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 391, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (392, '000131', 'case', '/showcase.html?Id=000131', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 392, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (393, '000131', 'form', '/table.html?Id=000131', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 393, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (394, '000132', 'law', '/show.html?Id=000132', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 394, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (395, '000132', 'case', '/showcase.html?Id=000132', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 395, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (396, '000132', 'form', '/table.html?Id=000132', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 396, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (397, '000133', 'law', '/show.html?Id=000133', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 397, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (398, '000133', 'case', '/showcase.html?Id=000133', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 398, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (399, '000133', 'form', '/table.html?Id=000133', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 399, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (400, '000134', 'law', '/show.html?Id=000134', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 400, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (401, '000134', 'case', '/showcase.html?Id=000134', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 401, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (402, '000134', 'form', '/table.html?Id=000134', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 402, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (403, '000135', 'law', '/show.html?Id=000135', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 403, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (404, '000135', 'case', '/showcase.html?Id=000135', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 404, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (405, '000135', 'form', '/table.html?Id=000135', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 405, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (406, '000136', 'law', '/show.html?Id=000136', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 406, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (407, '000136', 'case', '/showcase.html?Id=000136', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 407, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (408, '000136', 'form', '/table.html?Id=000136', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 408, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (409, '000137', 'law', '/show.html?Id=000137', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 409, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (410, '000137', 'case', '/showcase.html?Id=000137', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 410, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (411, '000137', 'form', '/table.html?Id=000137', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 411, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (412, '000138', 'law', '/show.html?Id=000138', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 412, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (413, '000138', 'case', '/showcase.html?Id=000138', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 413, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (414, '000138', 'form', '/table.html?Id=000138', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 414, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (415, '000139', 'law', '/show.html?Id=000139', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 415, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (416, '000139', 'case', '/showcase.html?Id=000139', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 416, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (417, '000139', 'form', '/table.html?Id=000139', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 417, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (418, '000140', 'law', '/show.html?Id=000140', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 418, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (419, '000140', 'case', '/showcase.html?Id=000140', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 419, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (420, '000140', 'form', '/table.html?Id=000140', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 420, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (421, '000141', 'law', '/show.html?Id=000141', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 421, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (422, '000141', 'case', '/showcase.html?Id=000141', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 422, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (423, '000141', 'form', '/table.html?Id=000141', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 423, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (424, '000142', 'law', '/show.html?Id=000142', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 424, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (425, '000142', 'case', '/showcase.html?Id=000142', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 425, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (426, '000142', 'form', '/table.html?Id=000142', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 426, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (427, '000143', 'law', '/show.html?Id=000143', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 427, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (428, '000143', 'case', '/showcase.html?Id=000143', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 428, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (429, '000143', 'form', '/table.html?Id=000143', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 429, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (430, '000144', 'law', '/show.html?Id=000144', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 430, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (431, '000144', 'case', '/showcase.html?Id=000144', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 431, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (432, '000144', 'form', '/table.html?Id=000144', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 432, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (433, '000145', 'law', '/show.html?Id=000145', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 433, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (434, '000145', 'case', '/showcase.html?Id=000145', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 434, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (435, '000145', 'form', '/table.html?Id=000145', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 435, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (436, '000146', 'law', '/show.html?Id=000146', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 436, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (437, '000146', 'case', '/showcase.html?Id=000146', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 437, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (438, '000146', 'form', '/table.html?Id=000146', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 438, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (439, '000147', 'law', '/show.html?Id=000147', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 439, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (440, '000147', 'case', '/showcase.html?Id=000147', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 440, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (441, '000147', 'form', '/table.html?Id=000147', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 441, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (442, '000148', 'law', '/show.html?Id=000148', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 442, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (443, '000148', 'case', '/showcase.html?Id=000148', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 443, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (444, '000148', 'form', '/table.html?Id=000148', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 444, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (445, '000149', 'law', '/show.html?Id=000149', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 445, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (446, '000149', 'case', '/showcase.html?Id=000149', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 446, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (447, '000149', 'form', '/table.html?Id=000149', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 447, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (448, '000150', 'law', '/show.html?Id=000150', '劳动法第四十一条解释', '

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      ', NULL, '法律法规编辑部', 1, 448, 0, '劳动法,工作时间,加班', '本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (449, '000150', 'case', '/showcase.html?Id=000150', '某公司劳资纠纷典型案例', '

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      ', NULL, '案例分析组', 1, 449, 0, '劳资纠纷,加班费,劳动仲裁', '典型劳资纠纷案例分析及处理结果', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); +INSERT INTO `html_pages` VALUES (450, '000150', 'form', '/table.html?Id=000150', '劳动仲裁申请表', '

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      ', NULL, '表单管理员', 1, 450, 0, '劳动仲裁,申请表,劳动争议', '劳动争议仲裁申请表格及填写说明', '2025-05-28 22:07:28', '2025-05-28 22:07:28', '[]'); + +-- ---------------------------- +-- Table structure for message_system +-- ---------------------------- +DROP TABLE IF EXISTS `message_system`; +CREATE TABLE `message_system` ( + `message_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `message_title` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '标题', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `send_mode` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '发送方式(0平台 1手机号 2 邮箱)', + `code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '号码', + `message_content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '消息内容', + `message_recipient` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '接收人', + `message_status` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '消息状态(0未读 1已读)', + `message_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '消息类型', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`message_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '消息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of message_system +-- ---------------------------- + +-- ---------------------------- +-- Table structure for message_template +-- ---------------------------- +DROP TABLE IF EXISTS `message_template`; +CREATE TABLE `message_template` ( + `template_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `template_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '模版名称', + `template_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '模版CODE', + `template_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '模版类型', + `template_content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '模版内容', + `template_variable` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '变量属性', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`template_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '模版表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of message_template +-- ---------------------------- + +-- ---------------------------- +-- Table structure for message_variable +-- ---------------------------- +DROP TABLE IF EXISTS `message_variable`; +CREATE TABLE `message_variable` ( + `variable_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `variable_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '变量名称', + `variable_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '变量类型', + `variable_content` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '变量内容', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`variable_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '变量表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of message_variable +-- ---------------------------- + +-- ---------------------------- +-- Table structure for oauth_user +-- ---------------------------- +DROP TABLE IF EXISTS `oauth_user`; +CREATE TABLE `oauth_user` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键', + `uuid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '第三方系统的唯一ID,详细解释请参考:名词解释', + `user_id` bigint NOT NULL COMMENT '用户ID', + `source` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '第三方用户来源,可选值:GITHUB、GITEE、QQ,更多请参考:AuthDefaultSource.java(opens new window)', + `access_token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户的授权令牌', + `expire_in` int NULL DEFAULT NULL COMMENT '第三方用户的授权令牌的有效期,部分平台可能没有', + `refresh_token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '刷新令牌,部分平台可能没有', + `open_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '第三方用户的 open id,部分平台可能没有', + `uid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '第三方用户的 ID,部分平台可能没有', + `access_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '个别平台的授权信息,部分平台可能没有', + `union_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '第三方用户的 union id,部分平台可能没有', + `scope` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '第三方用户授予的权限,部分平台可能没有', + `token_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '个别平台的授权信息,部分平台可能没有', + `id_token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'id token,部分平台可能没有', + `mac_algorithm` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '小米平台用户的附带属性,部分平台可能没有', + `mac_key` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '小米平台用户的附带属性,部分平台可能没有', + `code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '用户的授权code,部分平台可能没有', + `oauth_token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'Twitter平台用户的附带属性,部分平台可能没有', + `oauth_token_secret` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'Twitter平台用户的附带属性,部分平台可能没有', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '第三方登录' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of oauth_user +-- ---------------------------- + +-- ---------------------------- +-- Table structure for online_mb +-- ---------------------------- +DROP TABLE IF EXISTS `online_mb`; +CREATE TABLE `online_mb` ( + `mb_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `tag` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '标签名', + `tag_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '标签id', + `parameter_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '参数类型', + `result_map` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '结果类型', + `sql_text` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'sql语句', + `path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '请求路径', + `method` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '请求方式', + `result_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '响应类型', + `actuator` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '执行器', + `user_id` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '是否需要userId', + `dept_id` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '是否需要deptId', + `permission_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '许可类型', + `permission_value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '许可值', + `del_flag` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)', + PRIMARY KEY (`mb_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '在线接口' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of online_mb +-- ---------------------------- + +-- ---------------------------- +-- Table structure for pay_invoice +-- ---------------------------- +DROP TABLE IF EXISTS `pay_invoice`; +CREATE TABLE `pay_invoice` ( + `invoice_id` bigint NOT NULL AUTO_INCREMENT COMMENT '发票id', + `order_number` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '订单号', + `invoice_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '发票类型', + `invoice_header` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '发票抬头', + `invoice_number` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '纳税人识别号', + `invoice_phone` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '收票人手机号', + `invoice_email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '收票人邮箱', + `invoice_remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '发票备注', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`invoice_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '发票' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of pay_invoice +-- ---------------------------- + +-- ---------------------------- +-- Table structure for pay_order +-- ---------------------------- +DROP TABLE IF EXISTS `pay_order`; +CREATE TABLE `pay_order` ( + `order_id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单id', + `order_number` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '订单号', + `third_number` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '第三方订单号', + `order_status` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '订单状态', + `total_amount` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '订单总金额', + `actual_amount` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '实际支付金额', + `order_content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '订单内容', + `order_message` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '负载信息', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`order_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '订单' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of pay_order +-- ---------------------------- + +-- ---------------------------- +-- Table structure for qrtz_blob_triggers +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_blob_triggers`; +CREATE TABLE `qrtz_blob_triggers` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '调度名称', + `trigger_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'qrtz_triggers表trigger_name的外键', + `trigger_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键', + `blob_data` blob NULL COMMENT '存放持久化Trigger对象', + PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE, + CONSTRAINT `qrtz_blob_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Blob类型的触发器表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_blob_triggers +-- ---------------------------- + +-- ---------------------------- +-- Table structure for qrtz_calendars +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_calendars`; +CREATE TABLE `qrtz_calendars` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '调度名称', + `calendar_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '日历名称', + `calendar` blob NOT NULL COMMENT '存放持久化calendar对象', + PRIMARY KEY (`sched_name`, `calendar_name`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '日历信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_calendars +-- ---------------------------- + +-- ---------------------------- +-- Table structure for qrtz_cron_triggers +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_cron_triggers`; +CREATE TABLE `qrtz_cron_triggers` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '调度名称', + `trigger_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'qrtz_triggers表trigger_name的外键', + `trigger_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键', + `cron_expression` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'cron表达式', + `time_zone_id` varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '时区', + PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE, + CONSTRAINT `qrtz_cron_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Cron类型的触发器表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_cron_triggers +-- ---------------------------- + +-- ---------------------------- +-- Table structure for qrtz_fired_triggers +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_fired_triggers`; +CREATE TABLE `qrtz_fired_triggers` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '调度名称', + `entry_id` varchar(95) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '调度器实例id', + `trigger_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'qrtz_triggers表trigger_name的外键', + `trigger_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键', + `instance_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '调度器实例名', + `fired_time` bigint NOT NULL COMMENT '触发的时间', + `sched_time` bigint NOT NULL COMMENT '定时器制定的时间', + `priority` int NOT NULL COMMENT '优先级', + `state` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '状态', + `job_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '任务名称', + `job_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '任务组名', + `is_nonconcurrent` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '是否并发', + `requests_recovery` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '是否接受恢复执行', + PRIMARY KEY (`sched_name`, `entry_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '已触发的触发器表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_fired_triggers +-- ---------------------------- + +-- ---------------------------- +-- Table structure for qrtz_job_details +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_job_details`; +CREATE TABLE `qrtz_job_details` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '调度名称', + `job_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '任务名称', + `job_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '任务组名', + `description` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '相关介绍', + `job_class_name` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '执行任务类名称', + `is_durable` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '是否持久化', + `is_nonconcurrent` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '是否并发', + `is_update_data` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '是否更新数据', + `requests_recovery` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '是否接受恢复执行', + `job_data` blob NULL COMMENT '存放持久化job对象', + PRIMARY KEY (`sched_name`, `job_name`, `job_group`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '任务详细信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_job_details +-- ---------------------------- + +-- ---------------------------- +-- Table structure for qrtz_locks +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_locks`; +CREATE TABLE `qrtz_locks` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '调度名称', + `lock_name` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '悲观锁名称', + PRIMARY KEY (`sched_name`, `lock_name`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '存储的悲观锁信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_locks +-- ---------------------------- + +-- ---------------------------- +-- Table structure for qrtz_paused_trigger_grps +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_paused_trigger_grps`; +CREATE TABLE `qrtz_paused_trigger_grps` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '调度名称', + `trigger_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键', + PRIMARY KEY (`sched_name`, `trigger_group`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '暂停的触发器表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_paused_trigger_grps +-- ---------------------------- + +-- ---------------------------- +-- Table structure for qrtz_scheduler_state +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_scheduler_state`; +CREATE TABLE `qrtz_scheduler_state` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '调度名称', + `instance_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '实例名称', + `last_checkin_time` bigint NOT NULL COMMENT '上次检查时间', + `checkin_interval` bigint NOT NULL COMMENT '检查间隔时间', + PRIMARY KEY (`sched_name`, `instance_name`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '调度器状态表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_scheduler_state +-- ---------------------------- + +-- ---------------------------- +-- Table structure for qrtz_simple_triggers +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_simple_triggers`; +CREATE TABLE `qrtz_simple_triggers` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '调度名称', + `trigger_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'qrtz_triggers表trigger_name的外键', + `trigger_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键', + `repeat_count` bigint NOT NULL COMMENT '重复的次数统计', + `repeat_interval` bigint NOT NULL COMMENT '重复的间隔时间', + `times_triggered` bigint NOT NULL COMMENT '已经触发的次数', + PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE, + CONSTRAINT `qrtz_simple_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '简单触发器的信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_simple_triggers +-- ---------------------------- + +-- ---------------------------- +-- Table structure for qrtz_simprop_triggers +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_simprop_triggers`; +CREATE TABLE `qrtz_simprop_triggers` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '调度名称', + `trigger_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'qrtz_triggers表trigger_name的外键', + `trigger_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'qrtz_triggers表trigger_group的外键', + `str_prop_1` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'String类型的trigger的第一个参数', + `str_prop_2` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'String类型的trigger的第二个参数', + `str_prop_3` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'String类型的trigger的第三个参数', + `int_prop_1` int NULL DEFAULT NULL COMMENT 'int类型的trigger的第一个参数', + `int_prop_2` int NULL DEFAULT NULL COMMENT 'int类型的trigger的第二个参数', + `long_prop_1` bigint NULL DEFAULT NULL COMMENT 'long类型的trigger的第一个参数', + `long_prop_2` bigint NULL DEFAULT NULL COMMENT 'long类型的trigger的第二个参数', + `dec_prop_1` decimal(13, 4) NULL DEFAULT NULL COMMENT 'decimal类型的trigger的第一个参数', + `dec_prop_2` decimal(13, 4) NULL DEFAULT NULL COMMENT 'decimal类型的trigger的第二个参数', + `bool_prop_1` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'Boolean类型的trigger的第一个参数', + `bool_prop_2` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'Boolean类型的trigger的第二个参数', + PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE, + CONSTRAINT `qrtz_simprop_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '同步机制的行锁表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_simprop_triggers +-- ---------------------------- + +-- ---------------------------- +-- Table structure for qrtz_triggers +-- ---------------------------- +DROP TABLE IF EXISTS `qrtz_triggers`; +CREATE TABLE `qrtz_triggers` ( + `sched_name` varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '调度名称', + `trigger_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '触发器的名字', + `trigger_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '触发器所属组的名字', + `job_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'qrtz_job_details表job_name的外键', + `job_group` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'qrtz_job_details表job_group的外键', + `description` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '相关介绍', + `next_fire_time` bigint NULL DEFAULT NULL COMMENT '上一次触发时间(毫秒)', + `prev_fire_time` bigint NULL DEFAULT NULL COMMENT '下一次触发时间(默认为-1表示不触发)', + `priority` int NULL DEFAULT NULL COMMENT '优先级', + `trigger_state` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '触发器状态', + `trigger_type` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '触发器的类型', + `start_time` bigint NOT NULL COMMENT '开始时间', + `end_time` bigint NULL DEFAULT NULL COMMENT '结束时间', + `calendar_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '日程表名称', + `misfire_instr` smallint NULL DEFAULT NULL COMMENT '补偿执行的策略', + `job_data` blob NULL COMMENT '存放持久化job对象', + PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE, + INDEX `sched_name`(`sched_name` ASC, `job_name` ASC, `job_group` ASC) USING BTREE, + CONSTRAINT `qrtz_triggers_ibfk_1` FOREIGN KEY (`sched_name`, `job_name`, `job_group`) REFERENCES `qrtz_job_details` (`sched_name`, `job_name`, `job_group`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '触发器详细信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of qrtz_triggers +-- ---------------------------- + +-- ---------------------------- +-- Table structure for sys_config +-- ---------------------------- +DROP TABLE IF EXISTS `sys_config`; +CREATE TABLE `sys_config` ( + `config_id` int NOT NULL AUTO_INCREMENT COMMENT '参数主键', + `config_name` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '参数名称', + `config_key` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '参数键名', + `config_value` varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '参数键值', + `config_type` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT 'N' COMMENT '系统内置(Y是 N否)', + `create_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`config_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 106 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '参数配置表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_config +-- ---------------------------- +INSERT INTO `sys_config` VALUES (1, '主框架页-默认皮肤样式名称', 'sys.index.skinName', 'skin-green', 'Y', 'admin', '2023-04-13 20:46:20', 'admin', '2023-04-22 00:45:19', '蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow'); +INSERT INTO `sys_config` VALUES (2, '用户管理-账号初始密码', 'sys.user.initPassword', '123456', 'Y', 'admin', '2023-04-13 20:46:20', '', NULL, '初始化密码 123456'); +INSERT INTO `sys_config` VALUES (3, '主框架页-侧边栏主题', 'sys.index.sideTheme', 'theme-light', 'Y', 'admin', '2023-04-13 20:46:20', 'admin', '2023-04-22 00:45:25', '深色主题theme-dark,浅色主题theme-light'); +INSERT INTO `sys_config` VALUES (4, '账号自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', 'admin', '2023-04-13 20:46:20', '', NULL, '是否开启验证码功能(true开启,false关闭)'); +INSERT INTO `sys_config` VALUES (5, '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'true', 'Y', 'admin', '2023-04-13 20:46:20', 'admin', '2023-04-22 00:41:41', '是否开启注册用户功能(true开启,false关闭)'); +INSERT INTO `sys_config` VALUES (100, '主题颜色', 'sys.index.theme', '#11A983', 'Y', 'admin', '2023-04-22 00:57:18', 'admin', '2023-04-22 00:58:23', NULL); +INSERT INTO `sys_config` VALUES (101, '开启TopNav', 'sys.index.topNav', 'false', 'Y', 'admin', '2023-04-22 00:58:59', '', NULL, NULL); +INSERT INTO `sys_config` VALUES (102, '开启Tags-Views', 'sys.index.tagsView', 'true', 'Y', 'admin', '2023-04-22 00:59:40', '', NULL, NULL); +INSERT INTO `sys_config` VALUES (103, '显示Logo', 'sys.index.sidebarLogo', 'true', 'Y', 'admin', '2023-04-22 01:00:20', '', NULL, NULL); +INSERT INTO `sys_config` VALUES (104, '固定Header', 'sys.index.fixedHeader', 'true', 'Y', 'admin', '2023-04-22 01:00:53', '', NULL, NULL); +INSERT INTO `sys_config` VALUES (105, '动态标题', 'sys.index.dynamicTitle', 'true', 'Y', 'admin', '2023-04-22 01:01:26', 'admin', '2023-04-22 01:01:41', NULL); + +-- ---------------------------- +-- Table structure for sys_deploy_form +-- ---------------------------- +DROP TABLE IF EXISTS `sys_deploy_form`; +CREATE TABLE `sys_deploy_form` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', + `form_id` bigint NULL DEFAULT NULL COMMENT '表单主键', + `deploy_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '流程实例主键', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 9623 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '流程实例关联表单' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_deploy_form +-- ---------------------------- + +-- ---------------------------- +-- Table structure for sys_dept +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dept`; +CREATE TABLE `sys_dept` ( + `dept_id` bigint NOT NULL AUTO_INCREMENT COMMENT '部门id', + `parent_id` bigint NULL DEFAULT 0 COMMENT '父部门id', + `ancestors` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '祖级列表', + `dept_name` varchar(30) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '部门名称', + `order_num` int NULL DEFAULT 0 COMMENT '显示顺序', + `leader` varchar(20) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '负责人', + `phone` varchar(11) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '联系电话', + `email` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '邮箱', + `status` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '部门状态(0正常 1停用)', + `del_flag` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`dept_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 201 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '部门表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_dept +-- ---------------------------- +INSERT INTO `sys_dept` VALUES (100, 0, '0', '博越科技', 0, '博越', '15888888888', 'boyue@qq.com', '0', '0', 'admin', '2025-05-26 17:20:06', 'admin', '2025-05-29 08:42:28'); +INSERT INTO `sys_dept` VALUES (101, 100, '0,100', '淮安市', 1, '博越', '15888888888', 'boyue@qq.com', '0', '0', 'admin', '2025-05-26 17:20:06', 'admin', '2025-05-29 08:42:59'); +INSERT INTO `sys_dept` VALUES (102, 100, '0,100', '长沙分公司', 2, '若依', '15888888888', 'ry@qq.com', '0', '2', 'admin', '2025-05-26 17:20:06', '', NULL); +INSERT INTO `sys_dept` VALUES (103, 101, '0,100,101', '管理', 2, '管理', '15888888888', 'sfj@qq.com', '0', '0', 'admin', '2025-05-26 17:20:06', 'admin', '2025-05-29 08:44:34'); +INSERT INTO `sys_dept` VALUES (104, 101, '0,100,101', '市场部门', 2, '若依', '15888888888', 'ry@qq.com', '0', '2', 'admin', '2025-05-26 17:20:06', '', NULL); +INSERT INTO `sys_dept` VALUES (105, 101, '0,100,101', '测试部门', 3, '若依', '15888888888', 'ry@qq.com', '0', '2', 'admin', '2025-05-26 17:20:06', '', NULL); +INSERT INTO `sys_dept` VALUES (106, 101, '0,100,101', '财务部门', 4, '若依', '15888888888', 'ry@qq.com', '0', '2', 'admin', '2025-05-26 17:20:06', '', NULL); +INSERT INTO `sys_dept` VALUES (107, 101, '0,100,101', '运维部门', 5, '若依', '15888888888', 'ry@qq.com', '0', '2', 'admin', '2025-05-26 17:20:06', '', NULL); +INSERT INTO `sys_dept` VALUES (108, 102, '0,100,102', '市场部门', 1, '若依', '15888888888', 'ry@qq.com', '0', '2', 'admin', '2025-05-26 17:20:06', '', NULL); +INSERT INTO `sys_dept` VALUES (109, 102, '0,100,102', '财务部门', 2, '若依', '15888888888', 'ry@qq.com', '0', '2', 'admin', '2025-05-26 17:20:06', '', NULL); +INSERT INTO `sys_dept` VALUES (200, 101, '0,100,101', '司法局', 3, NULL, NULL, NULL, '0', '0', 'admin', '2025-05-29 08:44:51', '', NULL); + +-- ---------------------------- +-- Table structure for sys_dict_data +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dict_data`; +CREATE TABLE `sys_dict_data` ( + `dict_code` bigint NOT NULL AUTO_INCREMENT COMMENT '字典编码', + `dict_sort` int NULL DEFAULT 0 COMMENT '字典排序', + `dict_label` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '字典标签', + `dict_value` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '字典键值', + `dict_type` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '字典类型', + `css_class` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '样式属性(其他样式扩展)', + `list_class` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '表格回显样式', + `is_default` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT 'N' COMMENT '是否默认(Y是 N否)', + `status` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1停用)', + `create_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`dict_code`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 160 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '字典数据表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_dict_data +-- ---------------------------- +INSERT INTO `sys_dict_data` VALUES (1, 1, '男', '0', 'sys_user_sex', '', '', 'Y', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '性别男'); +INSERT INTO `sys_dict_data` VALUES (2, 2, '女', '1', 'sys_user_sex', '', '', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '性别女'); +INSERT INTO `sys_dict_data` VALUES (3, 3, '未知', '2', 'sys_user_sex', '', '', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '性别未知'); +INSERT INTO `sys_dict_data` VALUES (4, 1, '显示', '0', 'sys_show_hide', '', 'primary', 'Y', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '显示菜单'); +INSERT INTO `sys_dict_data` VALUES (5, 2, '隐藏', '1', 'sys_show_hide', '', 'danger', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '隐藏菜单'); +INSERT INTO `sys_dict_data` VALUES (6, 1, '正常', '0', 'sys_normal_disable', '', 'primary', 'Y', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '正常状态'); +INSERT INTO `sys_dict_data` VALUES (7, 2, '停用', '1', 'sys_normal_disable', '', 'danger', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '停用状态'); +INSERT INTO `sys_dict_data` VALUES (8, 1, '正常', '0', 'sys_job_status', '', 'primary', 'Y', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '正常状态'); +INSERT INTO `sys_dict_data` VALUES (9, 2, '暂停', '1', 'sys_job_status', '', 'danger', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '停用状态'); +INSERT INTO `sys_dict_data` VALUES (10, 1, '默认', 'DEFAULT', 'sys_job_group', '', '', 'Y', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '默认分组'); +INSERT INTO `sys_dict_data` VALUES (11, 2, '系统', 'SYSTEM', 'sys_job_group', '', '', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '系统分组'); +INSERT INTO `sys_dict_data` VALUES (12, 1, '是', 'Y', 'sys_yes_no', '', 'primary', 'Y', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '系统默认是'); +INSERT INTO `sys_dict_data` VALUES (13, 2, '否', 'N', 'sys_yes_no', '', 'danger', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '系统默认否'); +INSERT INTO `sys_dict_data` VALUES (14, 1, '通知', '1', 'sys_notice_type', '', 'warning', 'Y', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '通知'); +INSERT INTO `sys_dict_data` VALUES (15, 2, '公告', '2', 'sys_notice_type', '', 'success', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '公告'); +INSERT INTO `sys_dict_data` VALUES (16, 1, '正常', '0', 'sys_notice_status', '', 'primary', 'Y', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '正常状态'); +INSERT INTO `sys_dict_data` VALUES (17, 2, '关闭', '1', 'sys_notice_status', '', 'danger', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '关闭状态'); +INSERT INTO `sys_dict_data` VALUES (18, 99, '其他', '0', 'sys_oper_type', '', 'info', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '其他操作'); +INSERT INTO `sys_dict_data` VALUES (19, 1, '新增', '1', 'sys_oper_type', '', 'info', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '新增操作'); +INSERT INTO `sys_dict_data` VALUES (20, 2, '修改', '2', 'sys_oper_type', '', 'info', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '修改操作'); +INSERT INTO `sys_dict_data` VALUES (21, 3, '删除', '3', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '删除操作'); +INSERT INTO `sys_dict_data` VALUES (22, 4, '授权', '4', 'sys_oper_type', '', 'primary', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '授权操作'); +INSERT INTO `sys_dict_data` VALUES (23, 5, '导出', '5', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '导出操作'); +INSERT INTO `sys_dict_data` VALUES (24, 6, '导入', '6', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '导入操作'); +INSERT INTO `sys_dict_data` VALUES (25, 7, '强退', '7', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '强退操作'); +INSERT INTO `sys_dict_data` VALUES (26, 8, '生成代码', '8', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '生成操作'); +INSERT INTO `sys_dict_data` VALUES (27, 9, '清空数据', '9', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '清空操作'); +INSERT INTO `sys_dict_data` VALUES (28, 1, '成功', '0', 'sys_common_status', '', 'primary', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '正常状态'); +INSERT INTO `sys_dict_data` VALUES (29, 2, '失败', '1', 'sys_common_status', '', 'danger', 'N', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '停用状态'); +INSERT INTO `sys_dict_data` VALUES (100, 0, 'POST', 'POST', 'online_api_method', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:23:23', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (101, 0, 'GET', 'GET', 'online_api_method', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:23:30', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (102, 0, 'PUT', 'PUT', 'online_api_method', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:23:37', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (103, 0, 'DELETE', 'DELETE', 'online_api_method', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:23:49', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (104, 0, 'select', 'select', 'online_api_tag', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:24:06', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (105, 0, 'update', 'update', 'online_api_tag', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:24:12', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (106, 0, 'insert', 'insert', 'online_api_tag', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:24:18', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (107, 0, 'delete', 'delete', 'online_api_tag', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:24:26', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (108, 0, 'selectList', 'selectList', 'online_api_actuator', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:25:00', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (109, 0, 'insert', 'insert', 'online_api_actuator', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:25:05', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (110, 0, 'selectOne', 'selectOne', 'online_api_actuator', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:25:11', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (111, 0, 'update', 'update', 'online_api_actuator', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:25:16', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (112, 0, 'delete', 'delete', 'online_api_actuator', NULL, 'default', 'N', '0', 'admin', '2024-02-21 18:25:21', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (142, 0, '未读', '0', 'message_status', NULL, 'primary', 'N', '0', 'xl', '2024-12-21 15:13:02', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (143, 1, '已读', '1', 'message_status', NULL, 'success', 'N', '0', 'xl', '2024-12-21 15:13:15', 'xl', '2024-12-21 15:13:22', NULL); +INSERT INTO `sys_dict_data` VALUES (144, 0, '平台', '0', 'send_mode', NULL, 'primary', 'N', '0', 'xl', '2024-12-25 09:40:01', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (145, 1, '短信', '1', 'send_mode', NULL, 'success', 'N', '0', 'xl', '2024-12-25 09:40:16', 'xl', '2025-01-01 10:12:07', NULL); +INSERT INTO `sys_dict_data` VALUES (146, 2, '邮件', '2', 'send_mode', NULL, 'warning', 'N', '0', 'xl', '2024-12-25 09:40:28', 'xl', '2025-01-01 10:12:14', NULL); +INSERT INTO `sys_dict_data` VALUES (147, 0, '验证码', '0', 'template_type', NULL, 'primary', 'N', '0', 'xl', '2025-01-03 09:22:52', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (148, 0, '通知', '0', 'message_type', NULL, 'primary', 'N', '0', 'xl', '2025-01-03 15:12:29', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (149, 0, '提示', '1', 'message_type', NULL, 'success', 'N', '0', 'xl', '2025-01-03 15:12:41', 'xl', '2025-01-03 15:12:45', NULL); +INSERT INTO `sys_dict_data` VALUES (150, 1, '推广', '1', 'template_type', NULL, 'success', 'N', '0', 'xl', '2025-01-03 15:13:15', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (151, 0, '系统指定', 'fixed', 'exp_data_type', NULL, 'default', 'N', '0', 'admin', '2024-03-12 09:04:46', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (152, 0, '动态选择', 'dynamic', 'exp_data_type', NULL, 'default', 'N', '0', 'admin', '2024-03-12 09:05:02', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (153, 0, '任务监听', '1', 'sys_listener_type', NULL, 'default', 'N', '0', 'admin', '2022-12-25 11:47:26', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (154, 2, '执行监听', '2', 'sys_listener_type', NULL, 'default', 'N', '0', 'admin', '2022-12-25 11:47:37', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (155, 0, 'JAVA类', 'classListener', 'sys_listener_value_type', NULL, 'default', 'N', '0', 'admin', '2022-12-25 11:48:55', 'admin', '2024-09-05 21:38:02', NULL); +INSERT INTO `sys_dict_data` VALUES (156, 0, '表达式', 'expressionListener', 'sys_listener_value_type', NULL, 'default', 'N', '0', 'admin', '2022-12-25 11:49:05', 'admin', '2024-09-05 21:38:10', NULL); +INSERT INTO `sys_dict_data` VALUES (157, 0, '代理表达式', 'delegateExpressionListener', 'sys_listener_value_type', NULL, 'default', 'N', '0', 'admin', '2022-12-25 11:49:16', 'admin', '2024-09-05 21:38:16', NULL); +INSERT INTO `sys_dict_data` VALUES (158, 0, '请假', 'leave', 'sys_process_category', NULL, 'default', 'N', '0', 'admin', '2024-03-12 09:08:42', '', NULL, NULL); +INSERT INTO `sys_dict_data` VALUES (159, 0, '报销', 'expense', 'sys_process_category', NULL, 'default', 'N', '0', 'admin', '2024-03-12 09:09:02', '', NULL, NULL); + +-- ---------------------------- +-- Table structure for sys_dict_type +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dict_type`; +CREATE TABLE `sys_dict_type` ( + `dict_id` bigint NOT NULL AUTO_INCREMENT COMMENT '字典主键', + `dict_name` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '字典名称', + `dict_type` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '字典类型', + `status` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1停用)', + `create_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`dict_id`) USING BTREE, + UNIQUE INDEX `dict_type`(`dict_type` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 109 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '字典类型表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_dict_type +-- ---------------------------- +INSERT INTO `sys_dict_type` VALUES (1, '用户性别', 'sys_user_sex', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '用户性别列表'); +INSERT INTO `sys_dict_type` VALUES (2, '菜单状态', 'sys_show_hide', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '菜单状态列表'); +INSERT INTO `sys_dict_type` VALUES (3, '系统开关', 'sys_normal_disable', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '系统开关列表'); +INSERT INTO `sys_dict_type` VALUES (4, '任务状态', 'sys_job_status', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '任务状态列表'); +INSERT INTO `sys_dict_type` VALUES (5, '任务分组', 'sys_job_group', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '任务分组列表'); +INSERT INTO `sys_dict_type` VALUES (6, '系统是否', 'sys_yes_no', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '系统是否列表'); +INSERT INTO `sys_dict_type` VALUES (7, '通知类型', 'sys_notice_type', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '通知类型列表'); +INSERT INTO `sys_dict_type` VALUES (8, '通知状态', 'sys_notice_status', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '通知状态列表'); +INSERT INTO `sys_dict_type` VALUES (9, '操作类型', 'sys_oper_type', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '操作类型列表'); +INSERT INTO `sys_dict_type` VALUES (10, '系统状态', 'sys_common_status', '0', 'admin', '2025-05-26 17:20:07', '', NULL, '登录状态列表'); +INSERT INTO `sys_dict_type` VALUES (100, '请求方式', 'online_api_method', '0', 'admin', '2024-02-21 18:22:03', 'admin', '2024-02-21 18:22:13', NULL); +INSERT INTO `sys_dict_type` VALUES (101, '标签名', 'online_api_tag', '0', 'admin', '2024-02-21 18:22:29', '', NULL, NULL); +INSERT INTO `sys_dict_type` VALUES (102, '响应类型', 'online_api_result', '0', 'admin', '2024-02-21 18:22:46', '', NULL, NULL); +INSERT INTO `sys_dict_type` VALUES (103, '执行器', 'online_api_actuator', '0', 'admin', '2024-02-21 18:23:03', '', NULL, NULL); +INSERT INTO `sys_dict_type` VALUES (104, '表达式类型', 'exp_data_type', '0', 'admin', '2024-03-12 09:03:02', '', NULL, NULL); +INSERT INTO `sys_dict_type` VALUES (105, '监听类型', 'sys_listener_type', '0', 'admin', '2022-12-18 22:03:07', '', NULL, NULL); +INSERT INTO `sys_dict_type` VALUES (106, '监听值类型', 'sys_listener_value_type', '0', 'admin', '2022-12-18 22:03:39', '', NULL, NULL); +INSERT INTO `sys_dict_type` VALUES (107, '监听属性', 'sys_listener_event_type', '0', 'admin', '2022-12-18 22:04:29', '', NULL, NULL); +INSERT INTO `sys_dict_type` VALUES (108, '流程分类', 'sys_process_category', '0', 'admin', '2024-03-12 09:08:18', '', NULL, NULL); + +-- ---------------------------- +-- Table structure for sys_expression +-- ---------------------------- +DROP TABLE IF EXISTS `sys_expression`; +CREATE TABLE `sys_expression` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '表单主键', + `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '表达式名称', + `expression` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '表达式内容', + `data_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '表达式类型', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `create_by` bigint NULL DEFAULT NULL COMMENT '创建人员', + `update_by` bigint NULL DEFAULT NULL COMMENT '更新人员', + `status` tinyint NULL DEFAULT 0 COMMENT '状态', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 69 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '流程表达式' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_expression +-- ---------------------------- + +-- ---------------------------- +-- Table structure for sys_file_info +-- ---------------------------- +DROP TABLE IF EXISTS `sys_file_info`; +CREATE TABLE `sys_file_info` ( + `file_id` bigint NOT NULL AUTO_INCREMENT COMMENT '文件主键', + `file_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '原始文件名', + `file_path` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '统一逻辑路径(/开头)', + `storage_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '存储类型(local/minio/oss)', + `file_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '文件类型/后缀', + `file_size` bigint NULL DEFAULT NULL COMMENT '文件大小(字节)', + `md5` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '文件MD5', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + PRIMARY KEY (`file_id`) USING BTREE, + UNIQUE INDEX `uk_md5`(`md5` ASC) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_file_info +-- ---------------------------- + +-- ---------------------------- +-- Table structure for sys_job +-- ---------------------------- +DROP TABLE IF EXISTS `sys_job`; +CREATE TABLE `sys_job` ( + `job_id` bigint NOT NULL AUTO_INCREMENT COMMENT '任务ID', + `job_name` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT '' COMMENT '任务名称', + `job_group` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT 'DEFAULT' COMMENT '任务组名', + `invoke_target` varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '调用目标字符串', + `cron_expression` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT 'cron执行表达式', + `misfire_policy` varchar(20) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '3' COMMENT '计划执行错误策略(1立即执行 2执行一次 3放弃执行)', + `concurrent` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '1' COMMENT '是否并发执行(0允许 1禁止)', + `status` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '状态(0正常 1暂停)', + `create_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '备注信息', + PRIMARY KEY (`job_id`, `job_name`, `job_group`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 100 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '定时任务调度表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_job +-- ---------------------------- +INSERT INTO `sys_job` VALUES (1, '系统默认(无参)', 'DEFAULT', 'ryTask.ryNoParams', '0/10 * * * * ?', '3', '1', '1', 'admin', '2025-05-26 17:20:07', '', NULL, ''); +INSERT INTO `sys_job` VALUES (2, '系统默认(有参)', 'DEFAULT', 'ryTask.ryParams(\'ry\')', '0/15 * * * * ?', '3', '1', '1', 'admin', '2025-05-26 17:20:07', '', NULL, ''); +INSERT INTO `sys_job` VALUES (3, '系统默认(多参)', 'DEFAULT', 'ryTask.ryMultipleParams(\'ry\', true, 2000L, 316.50D, 100)', '0/20 * * * * ?', '3', '1', '1', 'admin', '2025-05-26 17:20:07', '', NULL, ''); + +-- ---------------------------- +-- Table structure for sys_job_log +-- ---------------------------- +DROP TABLE IF EXISTS `sys_job_log`; +CREATE TABLE `sys_job_log` ( + `job_log_id` bigint NOT NULL AUTO_INCREMENT COMMENT '任务日志ID', + `job_name` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '任务名称', + `job_group` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '任务组名', + `invoke_target` varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '调用目标字符串', + `job_message` varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '日志信息', + `status` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '执行状态(0正常 1失败)', + `exception_info` varchar(2000) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '异常信息', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`job_log_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '定时任务调度日志表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_job_log +-- ---------------------------- + +-- ---------------------------- +-- Table structure for sys_listener +-- ---------------------------- +DROP TABLE IF EXISTS `sys_listener`; +CREATE TABLE `sys_listener` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '表单主键', + `name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '名称', + `type` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '监听类型', + `event_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '事件类型', + `value_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '值类型', + `value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '执行内容', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `create_by` bigint NULL DEFAULT NULL COMMENT '创建人员', + `update_by` bigint NULL DEFAULT NULL COMMENT '更新人员', + `status` tinyint NULL DEFAULT 0 COMMENT '状态', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '流程监听' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_listener +-- ---------------------------- + +-- ---------------------------- +-- Table structure for sys_logininfor +-- ---------------------------- +DROP TABLE IF EXISTS `sys_logininfor`; +CREATE TABLE `sys_logininfor` ( + `info_id` bigint NOT NULL AUTO_INCREMENT COMMENT '访问ID', + `user_name` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '用户账号', + `ipaddr` varchar(128) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '登录IP地址', + `login_location` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '登录地点', + `browser` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '浏览器类型', + `os` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '操作系统', + `status` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '登录状态(0成功 1失败)', + `msg` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '提示消息', + `login_time` datetime NULL DEFAULT NULL COMMENT '访问时间', + PRIMARY KEY (`info_id`) USING BTREE, + INDEX `idx_sys_logininfor_s`(`status` ASC) USING BTREE, + INDEX `idx_sys_logininfor_lt`(`login_time` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 133 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '系统访问记录' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_logininfor +-- ---------------------------- +INSERT INTO `sys_logininfor` VALUES (100, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-26 18:37:16'); +INSERT INTO `sys_logininfor` VALUES (101, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '1', '验证码错误', '2025-05-28 18:36:01'); +INSERT INTO `sys_logininfor` VALUES (102, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-28 18:36:05'); +INSERT INTO `sys_logininfor` VALUES (103, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '退出成功', '2025-05-28 19:33:38'); +INSERT INTO `sys_logininfor` VALUES (104, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-28 19:33:47'); +INSERT INTO `sys_logininfor` VALUES (105, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-28 20:46:43'); +INSERT INTO `sys_logininfor` VALUES (106, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-28 21:18:44'); +INSERT INTO `sys_logininfor` VALUES (107, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-29 06:59:57'); +INSERT INTO `sys_logininfor` VALUES (108, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-29 08:41:00'); +INSERT INTO `sys_logininfor` VALUES (109, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '退出成功', '2025-05-29 10:46:05'); +INSERT INTO `sys_logininfor` VALUES (110, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-29 10:46:17'); +INSERT INTO `sys_logininfor` VALUES (111, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-29 12:33:00'); +INSERT INTO `sys_logininfor` VALUES (112, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '退出成功', '2025-05-29 12:33:12'); +INSERT INTO `sys_logininfor` VALUES (113, 'hasfj', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-29 12:33:26'); +INSERT INTO `sys_logininfor` VALUES (114, 'hasfj', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '退出成功', '2025-05-29 12:33:50'); +INSERT INTO `sys_logininfor` VALUES (115, 'admin', '49.74.40.190', 'XX XX', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-29 13:28:43'); +INSERT INTO `sys_logininfor` VALUES (116, 'admin', '49.74.40.190', 'XX XX', 'Chrome 13', 'Windows 10', '0', '退出成功', '2025-05-29 13:28:52'); +INSERT INTO `sys_logininfor` VALUES (117, 'admin', '49.74.40.190', 'XX XX', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-29 14:55:02'); +INSERT INTO `sys_logininfor` VALUES (118, 'hasfj', '49.74.40.190', 'XX XX', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-29 14:56:29'); +INSERT INTO `sys_logininfor` VALUES (119, 'hasfj', '114.238.34.181', 'XX XX', 'Safari', 'Mac OS X', '0', '登录成功', '2025-05-29 14:57:15'); +INSERT INTO `sys_logininfor` VALUES (120, 'admin', '49.82.111.128', 'XX XX', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-30 13:39:02'); +INSERT INTO `sys_logininfor` VALUES (121, 'admin', '49.82.111.128', 'XX XX', 'Chrome 13', 'Windows 10', '0', '退出成功', '2025-05-30 13:49:58'); +INSERT INTO `sys_logininfor` VALUES (122, 'admin', '49.82.111.128', 'XX XX', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-30 13:50:12'); +INSERT INTO `sys_logininfor` VALUES (123, 'admin', '180.125.41.129', 'XX XX', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-31 08:54:48'); +INSERT INTO `sys_logininfor` VALUES (124, 'admin', '49.87.176.188', 'XX XX', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-31 12:12:39'); +INSERT INTO `sys_logininfor` VALUES (125, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-31 14:44:08'); +INSERT INTO `sys_logininfor` VALUES (126, 'admin', '180.125.41.129', 'XX XX', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-31 15:04:25'); +INSERT INTO `sys_logininfor` VALUES (127, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-05-31 19:26:24'); +INSERT INTO `sys_logininfor` VALUES (128, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-06-01 10:15:29'); +INSERT INTO `sys_logininfor` VALUES (129, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-06-01 11:03:00'); +INSERT INTO `sys_logininfor` VALUES (130, 'admin', '127.0.0.1', '内网IP', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-06-02 13:51:42'); +INSERT INTO `sys_logininfor` VALUES (131, 'admin', '218.2.75.254', 'XX XX', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-06-02 19:18:18'); +INSERT INTO `sys_logininfor` VALUES (132, 'admin', '218.2.75.254', 'XX XX', 'Chrome 13', 'Windows 10', '0', '登录成功', '2025-06-02 19:33:32'); + +-- ---------------------------- +-- Table structure for sys_menu +-- ---------------------------- +DROP TABLE IF EXISTS `sys_menu`; +CREATE TABLE `sys_menu` ( + `menu_id` bigint NOT NULL AUTO_INCREMENT COMMENT '菜单ID', + `menu_name` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '菜单名称', + `parent_id` bigint NULL DEFAULT 0 COMMENT '父菜单ID', + `order_num` int NULL DEFAULT 0 COMMENT '显示顺序', + `path` varchar(200) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '路由地址', + `component` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '组件路径', + `query` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '路由参数', + `route_name` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '路由名称', + `is_frame` int NULL DEFAULT 1 COMMENT '是否为外链(0是 1否)', + `is_cache` int NULL DEFAULT 0 COMMENT '是否缓存(0缓存 1不缓存)', + `menu_type` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '菜单类型(M目录 C菜单 F按钮)', + `visible` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)', + `status` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '菜单状态(0正常 1停用)', + `perms` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '权限标识', + `icon` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '#' COMMENT '菜单图标', + `create_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '备注', + PRIMARY KEY (`menu_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 2105 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '菜单权限表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_menu +-- ---------------------------- +INSERT INTO `sys_menu` VALUES (1, '系统管理', 0, 1, 'system', NULL, '', '', 1, 0, 'M', '0', '0', '', 'system', 'admin', '2025-05-26 17:20:06', '', NULL, '系统管理目录'); +INSERT INTO `sys_menu` VALUES (2, '系统监控', 0, 2, 'monitor', NULL, '', '', 1, 0, 'M', '0', '0', '', 'monitor', 'admin', '2025-05-26 17:20:06', '', NULL, '系统监控目录'); +INSERT INTO `sys_menu` VALUES (3, '系统工具', 0, 3, 'tool', NULL, '', '', 1, 0, 'M', '0', '0', '', 'tool', 'admin', '2025-05-26 17:20:06', '', NULL, '系统工具目录'); +INSERT INTO `sys_menu` VALUES (100, '用户管理', 1, 1, 'user', 'system/user/index', '', '', 1, 0, 'C', '0', '0', 'system:user:list', 'user', 'admin', '2025-05-26 17:20:06', '', NULL, '用户管理菜单'); +INSERT INTO `sys_menu` VALUES (101, '角色管理', 1, 2, 'role', 'system/role/index', '', '', 1, 0, 'C', '0', '0', 'system:role:list', 'peoples', 'admin', '2025-05-26 17:20:06', '', NULL, '角色管理菜单'); +INSERT INTO `sys_menu` VALUES (102, '菜单管理', 1, 3, 'menu', 'system/menu/index', '', '', 1, 0, 'C', '0', '0', 'system:menu:list', 'tree-table', 'admin', '2025-05-26 17:20:06', '', NULL, '菜单管理菜单'); +INSERT INTO `sys_menu` VALUES (103, '部门管理', 1, 4, 'dept', 'system/dept/index', '', '', 1, 0, 'C', '0', '0', 'system:dept:list', 'tree', 'admin', '2025-05-26 17:20:06', '', NULL, '部门管理菜单'); +INSERT INTO `sys_menu` VALUES (104, '岗位管理', 1, 5, 'post', 'system/post/index', '', '', 1, 0, 'C', '0', '0', 'system:post:list', 'post', 'admin', '2025-05-26 17:20:06', '', NULL, '岗位管理菜单'); +INSERT INTO `sys_menu` VALUES (105, '字典管理', 1, 6, 'dict', 'system/dict/index', '', '', 1, 0, 'C', '0', '0', 'system:dict:list', 'dict', 'admin', '2025-05-26 17:20:06', '', NULL, '字典管理菜单'); +INSERT INTO `sys_menu` VALUES (106, '参数设置', 1, 7, 'config', 'system/config/index', '', '', 1, 0, 'C', '0', '0', 'system:config:list', 'edit', 'admin', '2025-05-26 17:20:06', '', NULL, '参数设置菜单'); +INSERT INTO `sys_menu` VALUES (107, '通知公告', 1, 8, 'notice', 'system/notice/index', '', '', 1, 0, 'C', '0', '0', 'system:notice:list', 'message', 'admin', '2025-05-26 17:20:06', '', NULL, '通知公告菜单'); +INSERT INTO `sys_menu` VALUES (108, '日志管理', 1, 9, 'log', '', '', '', 1, 0, 'M', '0', '0', '', 'log', 'admin', '2025-05-26 17:20:06', '', NULL, '日志管理菜单'); +INSERT INTO `sys_menu` VALUES (109, '在线用户', 2, 1, 'online', 'monitor/online/index', '', '', 1, 0, 'C', '0', '0', 'monitor:online:list', 'online', 'admin', '2025-05-26 17:20:06', '', NULL, '在线用户菜单'); +INSERT INTO `sys_menu` VALUES (110, '定时任务', 2, 2, 'job', 'monitor/job/index', '', '', 1, 0, 'C', '0', '0', 'monitor:job:list', 'job', 'admin', '2025-05-26 17:20:06', '', NULL, '定时任务菜单'); +INSERT INTO `sys_menu` VALUES (111, '数据监控', 2, 3, 'druid', 'monitor/druid/index', '', '', 1, 0, 'C', '0', '0', 'monitor:druid:list', 'druid', 'admin', '2025-05-26 17:20:06', '', NULL, '数据监控菜单'); +INSERT INTO `sys_menu` VALUES (112, '服务监控', 2, 4, 'server', 'monitor/server/index', '', '', 1, 0, 'C', '0', '0', 'monitor:server:list', 'server', 'admin', '2025-05-26 17:20:06', '', NULL, '服务监控菜单'); +INSERT INTO `sys_menu` VALUES (113, '缓存监控', 2, 5, 'cache', 'monitor/cache/index', '', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis', 'admin', '2025-05-26 17:20:06', '', NULL, '缓存监控菜单'); +INSERT INTO `sys_menu` VALUES (114, '缓存列表', 2, 6, 'cacheList', 'monitor/cache/list', '', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis-list', 'admin', '2025-05-26 17:20:06', '', NULL, '缓存列表菜单'); +INSERT INTO `sys_menu` VALUES (115, '表单构建', 3, 1, 'build', 'tool/build/index', '', '', 1, 0, 'C', '0', '0', 'tool:build:list', 'build', 'admin', '2025-05-26 17:20:06', '', NULL, '表单构建菜单'); +INSERT INTO `sys_menu` VALUES (116, '代码生成', 3, 2, 'gen', 'tool/gen/index', '', '', 1, 0, 'C', '0', '0', 'tool:gen:list', 'code', 'admin', '2025-05-26 18:42:26', '', NULL, '代码生成菜单'); +INSERT INTO `sys_menu` VALUES (117, '系统接口', 3, 3, 'swagger', 'tool/swagger/index', '', '', 1, 0, 'C', '0', '0', 'tool:swagger:list', 'swagger', 'admin', '2025-05-26 17:20:06', '', NULL, '系统接口菜单'); +INSERT INTO `sys_menu` VALUES (500, '操作日志', 108, 1, 'operlog', 'monitor/operlog/index', '', '', 1, 0, 'C', '0', '0', 'monitor:operlog:list', 'form', 'admin', '2025-05-26 17:20:06', '', NULL, '操作日志菜单'); +INSERT INTO `sys_menu` VALUES (501, '登录日志', 108, 2, 'logininfor', 'monitor/logininfor/index', '', '', 1, 0, 'C', '0', '0', 'monitor:logininfor:list', 'logininfor', 'admin', '2025-05-26 17:20:06', '', NULL, '登录日志菜单'); +INSERT INTO `sys_menu` VALUES (1000, '用户查询', 100, 1, '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:query', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1001, '用户新增', 100, 2, '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:add', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1002, '用户修改', 100, 3, '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:edit', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1003, '用户删除', 100, 4, '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:remove', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1004, '用户导出', 100, 5, '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:export', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1005, '用户导入', 100, 6, '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:import', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1006, '重置密码', 100, 7, '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:resetPwd', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1007, '角色查询', 101, 1, '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:query', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1008, '角色新增', 101, 2, '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:add', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1009, '角色修改', 101, 3, '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:edit', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1010, '角色删除', 101, 4, '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:remove', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1011, '角色导出', 101, 5, '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:export', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1012, '菜单查询', 102, 1, '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:query', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1013, '菜单新增', 102, 2, '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:add', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1014, '菜单修改', 102, 3, '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:edit', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1015, '菜单删除', 102, 4, '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:remove', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1016, '部门查询', 103, 1, '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:query', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1017, '部门新增', 103, 2, '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:add', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1018, '部门修改', 103, 3, '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:edit', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1019, '部门删除', 103, 4, '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:remove', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1020, '岗位查询', 104, 1, '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:query', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1021, '岗位新增', 104, 2, '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:add', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1022, '岗位修改', 104, 3, '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:edit', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1023, '岗位删除', 104, 4, '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:remove', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1024, '岗位导出', 104, 5, '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:export', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1025, '字典查询', 105, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:query', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1026, '字典新增', 105, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:add', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1027, '字典修改', 105, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:edit', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1028, '字典删除', 105, 4, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:remove', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1029, '字典导出', 105, 5, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:export', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1030, '参数查询', 106, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:query', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1031, '参数新增', 106, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:add', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1032, '参数修改', 106, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:edit', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1033, '参数删除', 106, 4, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:remove', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1034, '参数导出', 106, 5, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:export', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1035, '公告查询', 107, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:query', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1036, '公告新增', 107, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:add', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1037, '公告修改', 107, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:edit', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1038, '公告删除', 107, 4, '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:remove', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1039, '操作查询', 500, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:query', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1040, '操作删除', 500, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:remove', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1041, '日志导出', 500, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:export', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1042, '登录查询', 501, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:query', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1043, '登录删除', 501, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:remove', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1044, '日志导出', 501, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:export', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1045, '账户解锁', 501, 4, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:unlock', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1046, '在线查询', 109, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:query', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1047, '批量强退', 109, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:batchLogout', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1048, '单条强退', 109, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:forceLogout', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1049, '任务查询', 110, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:query', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1050, '任务新增', 110, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:add', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1051, '任务修改', 110, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:edit', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1052, '任务删除', 110, 4, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:remove', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1053, '状态修改', 110, 5, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:changeStatus', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1054, '任务导出', 110, 6, '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:export', '#', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1055, '生成查询', 116, 1, '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:query', '#', 'admin', '2025-05-26 18:42:26', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1056, '生成修改', 116, 2, '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:edit', '#', 'admin', '2025-05-26 18:42:26', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1057, '生成删除', 116, 3, '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:remove', '#', 'admin', '2025-05-26 18:42:26', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1058, '导入代码', 116, 4, '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:import', '#', 'admin', '2025-05-26 18:42:26', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1059, '预览代码', 116, 5, '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:preview', '#', 'admin', '2025-05-26 18:42:26', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (1060, '生成代码', 116, 6, '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:code', '#', 'admin', '2025-05-26 18:42:26', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2000, '支付管理', 0, 4, 'pay', NULL, NULL, '', 1, 0, 'M', '1', '1', '', 'money', 'admin', '2024-02-15 22:40:23', 'admin', '2025-05-28 18:37:31', ''); +INSERT INTO `sys_menu` VALUES (2001, '订单', 2000, 1, 'order', 'pay/order/index', NULL, '', 1, 0, 'C', '0', '0', 'pay:order:list', '#', 'admin', '2025-05-26 18:42:52', '', NULL, '订单菜单'); +INSERT INTO `sys_menu` VALUES (2002, '订单查询', 2001, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'pay:order:query', '#', 'admin', '2025-05-26 18:42:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2003, '订单新增', 2001, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'pay:order:add', '#', 'admin', '2025-05-26 18:42:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2004, '订单修改', 2001, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'pay:order:edit', '#', 'admin', '2025-05-26 18:42:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2005, '订单删除', 2001, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'pay:order:remove', '#', 'admin', '2025-05-26 18:42:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2006, '订单导出', 2001, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'pay:order:export', '#', 'admin', '2025-05-26 18:42:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2007, '发票', 2000, 1, 'invoice', 'pay/invoice/index', NULL, '', 1, 0, 'C', '0', '0', 'pay:invoice:list', '#', 'admin', '2025-05-26 18:42:52', '', NULL, '发票菜单'); +INSERT INTO `sys_menu` VALUES (2008, '发票查询', 2007, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'pay:invoice:query', '#', 'admin', '2025-05-26 18:42:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2009, '发票新增', 2007, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'pay:invoice:add', '#', 'admin', '2025-05-26 18:42:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2010, '发票修改', 2007, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'pay:invoice:edit', '#', 'admin', '2025-05-26 18:42:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2011, '发票删除', 2007, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'pay:invoice:remove', '#', 'admin', '2025-05-26 18:42:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2012, '发票导出', 2007, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'pay:invoice:export', '#', 'admin', '2025-05-26 18:42:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2013, 'Online', 0, 5, 'onlinedev', NULL, NULL, '', 1, 0, 'M', '1', '1', '', 'international', 'admin', '2024-03-07 19:38:34', 'admin', '2025-05-28 18:37:18', ''); +INSERT INTO `sys_menu` VALUES (2014, 'mybatis在线接口', 2013, 1, 'mb', 'online/mb/index', NULL, '', 1, 0, 'C', '0', '0', 'online:mb:list', 'code', 'admin', '2025-05-26 18:42:57', '', NULL, 'mybatis在线接口菜单'); +INSERT INTO `sys_menu` VALUES (2015, '数据库', 2013, 1, 'db', 'online/db/index', NULL, '', 1, 0, 'C', '0', '0', 'admin', 'table', 'admin', '2024-03-07 19:48:24', 'admin', '2024-03-07 19:54:46', ''); +INSERT INTO `sys_menu` VALUES (2016, 'mybatis在线接口查询', 2015, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'online:mb:query', '#', 'admin', '2025-05-26 18:42:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2017, 'mybatis在线接口新增', 2015, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'online:mb:add', '#', 'admin', '2025-05-26 18:42:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2018, 'mybatis在线接口修改', 2015, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'online:mb:edit', '#', 'admin', '2025-05-26 18:42:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2019, 'mybatis在线接口删除', 2015, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'online:mb:remove', '#', 'admin', '2025-05-26 18:42:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2020, 'mybatis在线接口导出', 2015, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'online:mb:export', '#', 'admin', '2025-05-26 18:42:57', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2021, '消息系统', 0, 6, 'modelMessage', NULL, NULL, '', 1, 0, 'M', '1', '1', '', 'message', 'admin', '2024-12-31 11:57:29', 'admin', '2025-05-28 18:37:14', ''); +INSERT INTO `sys_menu` VALUES (2022, '消息管理', 2021, 0, 'messageSystem', 'modelMessage/messageSystem/index', NULL, '', 1, 0, 'C', '0', '0', 'modelMessage:messageSystem:list', '#', 'admin', '2024-12-21 15:00:31', 'admin', '2024-12-31 15:04:49', '消息管理菜单'); +INSERT INTO `sys_menu` VALUES (2023, '消息管理查询', 2022, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:messageSystem:query', '#', 'admin', '2024-12-21 15:00:31', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2024, '消息管理新增', 2022, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:messageSystem:add', '#', 'admin', '2024-12-21 15:00:31', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2025, '消息管理修改', 2022, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:messageSystem:edit', '#', 'admin', '2024-12-21 15:00:31', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2026, '消息管理删除', 2022, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:messageSystem:remove', '#', 'admin', '2024-12-21 15:00:31', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2027, '消息管理导出', 2022, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:messageSystem:export', '#', 'admin', '2024-12-21 15:00:31', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2028, '模版管理', 2021, 1, 'template', 'modelMessage/template/index', NULL, '', 1, 0, 'C', '0', '0', 'modelMessage:template:list', '#', 'admin', '2024-12-31 14:59:52', '', NULL, '模版管理菜单'); +INSERT INTO `sys_menu` VALUES (2029, '模版管理查询', 2028, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:template:query', '#', 'admin', '2024-12-31 14:59:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2030, '模版管理新增', 2028, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:template:add', '#', 'admin', '2024-12-31 14:59:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2031, '模版管理修改', 2028, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:template:edit', '#', 'admin', '2024-12-31 14:59:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2032, '模版管理删除', 2028, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:template:remove', '#', 'admin', '2024-12-31 14:59:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2033, '模版管理导出', 2028, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:template:export', '#', 'admin', '2024-12-31 14:59:52', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2034, '变量管理', 2021, 2, 'variable', 'modelMessage/variable/index', NULL, '', 1, 0, 'C', '0', '0', 'modelMessage:variable:list', '#', 'admin', '2024-12-31 15:01:50', 'admin', '2024-12-31 15:04:56', '变量管理菜单'); +INSERT INTO `sys_menu` VALUES (2035, '变量管理查询', 2034, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:variable:query', '#', 'admin', '2024-12-31 15:01:50', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2036, '变量管理新增', 2034, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:variable:add', '#', 'admin', '2024-12-31 15:01:50', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2037, '变量管理修改', 2034, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:variable:edit', '#', 'admin', '2024-12-31 15:01:50', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2038, '变量管理删除', 2034, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:variable:remove', '#', 'admin', '2024-12-31 15:01:50', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2039, '变量管理导出', 2034, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'modelMessage:variable:export', '#', 'admin', '2024-12-31 15:01:50', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2040, '表单管理', 0, 4, 'formManagement', NULL, NULL, '', 1, 0, 'M', '1', '1', '', 'form', 'admin', '2024-02-15 22:40:23', 'admin', '2025-05-28 18:37:27', ''); +INSERT INTO `sys_menu` VALUES (2041, '表单模板', 2040, 1, 'formtemplate', 'form/template/index', NULL, '', 1, 0, 'C', '0', '0', 'form:template:list', '#', 'admin', '2025-05-26 18:43:09', '', NULL, '表单模板菜单'); +INSERT INTO `sys_menu` VALUES (2042, '表单模板查询', 2041, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'form:template:query', '#', 'admin', '2025-05-26 18:43:09', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2043, '表单模板新增', 2041, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'form:template:add', '#', 'admin', '2025-05-26 18:43:09', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2044, '表单模板修改', 2041, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'form:template:edit', '#', 'admin', '2025-05-26 18:43:09', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2045, '表单模板删除', 2041, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'form:template:remove', '#', 'admin', '2025-05-26 18:43:09', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2046, '表单模板导出', 2041, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'form:template:export', '#', 'admin', '2025-05-26 18:43:09', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2047, '表单数据', 2040, 1, 'formdata', 'form/data/index', NULL, '', 1, 0, 'C', '0', '0', 'form:data:list', '#', 'admin', '2025-05-26 18:43:09', '', NULL, '表单数据菜单'); +INSERT INTO `sys_menu` VALUES (2048, '表单数据查询', 2047, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'form:data:query', '#', 'admin', '2025-05-26 18:43:09', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2049, '表单数据新增', 2047, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'form:data:add', '#', 'admin', '2025-05-26 18:43:09', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2050, '表单数据修改', 2047, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'form:data:edit', '#', 'admin', '2025-05-26 18:43:09', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2051, '表单数据删除', 2047, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'form:data:remove', '#', 'admin', '2025-05-26 18:43:09', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2052, '表单数据导出', 2047, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'form:data:export', '#', 'admin', '2025-05-26 18:43:09', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2053, '流程管理', 0, 6, 'flowable', NULL, NULL, NULL, 1, 0, 'M', '1', '1', '', 'cascader', 'tony', '2021-03-25 11:35:09', 'admin', '2025-05-28 18:37:09', ''); +INSERT INTO `sys_menu` VALUES (2054, '流程定义', 2053, 2, 'definition', 'flowable/definition/index', NULL, NULL, 1, 0, 'C', '0', '0', '', 'job', 'tony', '2021-03-25 13:53:55', 'admin', '2022-12-29 17:40:39', ''); +INSERT INTO `sys_menu` VALUES (2055, '任务管理', 0, 7, 'task', NULL, NULL, NULL, 1, 0, 'M', '1', '1', '', 'dict', 'tony', '2021-03-26 10:53:10', 'admin', '2025-05-28 18:37:05', ''); +INSERT INTO `sys_menu` VALUES (2056, '待办任务', 2055, 2, 'todo', 'flowable/task/todo/index', NULL, NULL, 1, 1, 'C', '0', '0', '', 'cascader', 'admin', '2021-03-26 10:55:52', 'admin', '2021-03-30 09:26:36', ''); +INSERT INTO `sys_menu` VALUES (2057, '已办任务', 2055, 3, 'finished', 'flowable/task/finished/index', NULL, NULL, 1, 1, 'C', '0', '0', '', 'time-range', 'admin', '2021-03-26 10:57:54', 'admin', '2021-03-30 09:26:50', ''); +INSERT INTO `sys_menu` VALUES (2058, '已发任务', 2055, 1, 'process', 'flowable/task/myProcess/index', NULL, NULL, 1, 1, 'C', '0', '0', '', 'guide', 'admin', '2021-03-30 09:26:23', 'admin', '2022-12-12 09:58:07', ''); +INSERT INTO `sys_menu` VALUES (2059, '新增', 2058, 1, '', NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'system:deployment:add', '#', 'admin', '2021-07-07 14:25:22', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2060, '编辑', 2058, 2, '', NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'system:deployment:edit', '#', 'admin', '2021-07-07 14:25:47', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2061, '删除', 2058, 3, '', NULL, NULL, NULL, 1, 0, 'F', '0', '0', 'system:deployment:remove', '#', 'admin', '2021-07-07 14:26:02', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2062, '流程表达式', 2053, 3, 'expression', 'flowable/expression/index', NULL, NULL, 1, 1, 'C', '0', '0', 'system:expression:list', 'list', 'admin', '2022-12-12 17:12:19', 'admin', '2022-12-12 17:13:44', '流程达式菜单'); +INSERT INTO `sys_menu` VALUES (2063, '流程达式查询', 2062, 1, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:expression:query', '#', 'admin', '2022-12-12 17:12:19', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2064, '流程达式新增', 2062, 2, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:expression:add', '#', 'admin', '2022-12-12 17:12:19', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2065, '流程达式修改', 2062, 3, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:expression:edit', '#', 'admin', '2022-12-12 17:12:19', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2066, '流程达式删除', 2062, 4, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:expression:remove', '#', 'admin', '2022-12-12 17:12:19', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2067, '流程达式导出', 2062, 5, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:expression:export', '#', 'admin', '2022-12-12 17:12:19', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2068, '流程监听', 2053, 4, 'listener', 'flowable/listener/index', NULL, NULL, 1, 0, 'C', '0', '0', 'system:listener:list', 'monitor', 'admin', '2022-12-25 11:44:16', 'admin', '2022-12-29 08:59:21', '流程监听菜单'); +INSERT INTO `sys_menu` VALUES (2069, '流程监听查询', 2068, 1, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:listener:query', '#', 'admin', '2022-12-25 11:44:16', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2070, '流程监听新增', 2068, 2, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:listener:add', '#', 'admin', '2022-12-25 11:44:16', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2071, '流程监听修改', 2068, 3, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:listener:edit', '#', 'admin', '2022-12-25 11:44:16', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2072, '流程监听删除', 2068, 4, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:listener:remove', '#', 'admin', '2022-12-25 11:44:16', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2073, '流程监听导出', 2068, 5, '#', '', NULL, NULL, 1, 0, 'F', '0', '0', 'system:listener:export', '#', 'admin', '2022-12-25 11:44:16', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2074, '文件管理', 0, 4, 'file', NULL, NULL, '', 1, 0, 'M', '1', '1', '', 'excel', 'admin', '2024-02-15 22:40:23', 'admin', '2025-05-28 18:37:23', ''); +INSERT INTO `sys_menu` VALUES (2075, '文件信息', 2074, 1, 'info', 'file/info/index', NULL, '', 1, 0, 'C', '0', '0', 'file:info:list', 'excel', 'admin', '2025-05-26 18:43:20', '', NULL, '文件信息菜单'); +INSERT INTO `sys_menu` VALUES (2076, '文件信息查询', 2075, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'file:info:query', '#', 'admin', '2025-05-26 18:43:20', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2077, '文件信息新增', 2075, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'file:info:add', '#', 'admin', '2025-05-26 18:43:20', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2078, '文件信息修改', 2075, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'file:info:edit', '#', 'admin', '2025-05-26 18:43:20', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2079, '文件信息删除', 2075, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'file:info:remove', '#', 'admin', '2025-05-26 18:43:20', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2080, '文件信息导出', 2075, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'file:info:export', '#', 'admin', '2025-05-26 18:43:20', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2081, '第三方认证', 1, 1, 'oauth', 'system/oauth/index', NULL, '', 1, 0, 'C', '0', '0', 'system:oauth:list', 'checkbox', 'admin', '2025-05-26 18:43:24', '', NULL, '第三方认证菜单'); +INSERT INTO `sys_menu` VALUES (2082, '第三方认证查询', 2081, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'system:oauth:query', '#', 'admin', '2025-05-26 18:43:24', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2083, '第三方认证新增', 2081, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'system:oauth:add', '#', 'admin', '2025-05-26 18:43:24', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2084, '第三方认证修改', 2081, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'system:oauth:edit', '#', 'admin', '2025-05-26 18:43:24', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2085, '第三方认证删除', 2081, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'system:oauth:remove', '#', 'admin', '2025-05-26 18:43:24', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2086, '第三方认证导出', 2081, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'system:oauth:export', '#', 'admin', '2025-05-26 18:43:24', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2097, '淮安市司法局网站', 0, 40, 'hasfj', NULL, NULL, '', 1, 0, 'M', '0', '0', '', 'peoples', 'admin', '2025-05-28 21:46:52', '', NULL, '淮安市司法局网站管理'); +INSERT INTO `sys_menu` VALUES (2099, '页面内容管理', 2097, 1, 'hasfjpages', 'hasfj/hasfjpages/index', NULL, '', 1, 0, 'C', '0', '0', 'hasfj:hasfjpages:list', 'documentation', 'admin', '2025-05-28 21:48:22', '', NULL, '司法局法律规定、典型案例、单下载菜单'); +INSERT INTO `sys_menu` VALUES (2100, '页面内容查询', 2099, 1, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'hasfj:hasfjpages:query', '#', 'admin', '2025-05-28 21:48:22', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2101, '页面内容新增', 2099, 2, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'hasfj:hasfjpages:add', '#', 'admin', '2025-05-28 21:48:22', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2102, '页面内容修改', 2099, 3, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'hasfj:hasfjpages:edit', '#', 'admin', '2025-05-28 21:48:22', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2103, '页面内容删除', 2099, 4, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'hasfj:hasfjpages:remove', '#', 'admin', '2025-05-28 21:48:22', '', NULL, ''); +INSERT INTO `sys_menu` VALUES (2104, '页面内容导出', 2099, 5, '#', '', NULL, '', 1, 0, 'F', '0', '0', 'hasfj:hasfjpages:export', '#', 'admin', '2025-05-28 21:48:22', '', NULL, ''); + +-- ---------------------------- +-- Table structure for sys_notice +-- ---------------------------- +DROP TABLE IF EXISTS `sys_notice`; +CREATE TABLE `sys_notice` ( + `notice_id` int NOT NULL AUTO_INCREMENT COMMENT '公告ID', + `notice_title` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '公告标题', + `notice_type` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '公告类型(1通知 2公告)', + `notice_content` longblob NULL COMMENT '公告内容', + `status` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '公告状态(0正常 1关闭)', + `create_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`notice_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '通知公告表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_notice +-- ---------------------------- +INSERT INTO `sys_notice` VALUES (1, '温馨提醒:2018-07-01 若依新版本发布啦', '2', 0xE696B0E78988E69CACE58685E5AEB9, '0', 'admin', '2025-05-26 17:20:07', '', NULL, '管理员'); +INSERT INTO `sys_notice` VALUES (2, '维护通知:2018-07-01 若依系统凌晨维护', '1', 0xE7BBB4E68AA4E58685E5AEB9, '0', 'admin', '2025-05-26 17:20:07', '', NULL, '管理员'); + +-- ---------------------------- +-- Table structure for sys_oper_log +-- ---------------------------- +DROP TABLE IF EXISTS `sys_oper_log`; +CREATE TABLE `sys_oper_log` ( + `oper_id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志主键', + `title` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '模块标题', + `business_type` int NULL DEFAULT 0 COMMENT '业务类型(0其它 1新增 2修改 3删除)', + `method` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '方法名称', + `request_method` varchar(10) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '请求方式', + `operator_type` int NULL DEFAULT 0 COMMENT '操作类别(0其它 1后台用户 2手机端用户)', + `oper_name` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '操作人员', + `dept_name` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '部门名称', + `oper_url` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '请求URL', + `oper_ip` varchar(128) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '主机地址', + `oper_location` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '操作地点', + `oper_param` varchar(2000) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '请求参数', + `json_result` varchar(2000) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '返回参数', + `status` int NULL DEFAULT 0 COMMENT '操作状态(0正常 1异常)', + `error_msg` varchar(2000) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '错误消息', + `oper_time` datetime NULL DEFAULT NULL COMMENT '操作时间', + `cost_time` bigint NULL DEFAULT 0 COMMENT '消耗时间', + PRIMARY KEY (`oper_id`) USING BTREE, + INDEX `idx_sys_oper_log_bt`(`business_type` ASC) USING BTREE, + INDEX `idx_sys_oper_log_s`(`status` ASC) USING BTREE, + INDEX `idx_sys_oper_log_ot`(`oper_time` ASC) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 231 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '操作日志记录' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_oper_log +-- ---------------------------- +INSERT INTO `sys_oper_log` VALUES (100, '用户头像', 2, 'com.boyue.web.controller.system.SysProfileController.avatar()', 'POST', 1, 'admin', '研发部门', '/system/user/profile/avatar', '127.0.0.1', '内网IP', '', '{\"msg\":\"操作成功\",\"imgUrl\":\"http://localhost:8080/profile/files/master/avatar/admin/1/20250526183841-avatar.png\",\"code\":200}', 0, NULL, '2025-05-26 18:38:41', 103); +INSERT INTO `sys_oper_log` VALUES (101, '菜单管理', 3, 'com.boyue.web.controller.system.SysMenuController.remove()', 'DELETE', 1, 'admin', '研发部门', '/system/menu/4', '127.0.0.1', '内网IP', '4', '{\"msg\":\"菜单已分配,不允许删除\",\"code\":601}', 0, NULL, '2025-05-28 18:36:42', 16); +INSERT INTO `sys_oper_log` VALUES (102, '菜单管理', 2, 'com.boyue.web.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/menu', '127.0.0.1', '内网IP', '{\"children\":[],\"createTime\":\"2025-05-26 17:20:06\",\"icon\":\"guide\",\"isCache\":\"0\",\"isFrame\":\"0\",\"menuId\":4,\"menuName\":\"若依官网\",\"menuType\":\"M\",\"orderNum\":4,\"params\":{},\"parentId\":0,\"path\":\"http://boyue.vip\",\"perms\":\"\",\"query\":\"\",\"routeName\":\"\",\"status\":\"1\",\"updateBy\":\"admin\",\"visible\":\"1\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 18:36:51', 31); +INSERT INTO `sys_oper_log` VALUES (103, '菜单管理', 3, 'com.boyue.web.controller.system.SysMenuController.remove()', 'DELETE', 1, 'admin', '研发部门', '/system/menu/4', '127.0.0.1', '内网IP', '4', '{\"msg\":\"菜单已分配,不允许删除\",\"code\":601}', 0, NULL, '2025-05-28 18:36:54', 6); +INSERT INTO `sys_oper_log` VALUES (104, '菜单管理', 2, 'com.boyue.web.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/menu', '127.0.0.1', '内网IP', '{\"children\":[],\"createTime\":\"2021-03-26 10:53:10\",\"icon\":\"dict\",\"isCache\":\"0\",\"isFrame\":\"1\",\"menuId\":2055,\"menuName\":\"任务管理\",\"menuType\":\"M\",\"orderNum\":7,\"params\":{},\"parentId\":0,\"path\":\"task\",\"perms\":\"\",\"status\":\"1\",\"updateBy\":\"admin\",\"visible\":\"1\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 18:37:05', 62); +INSERT INTO `sys_oper_log` VALUES (105, '菜单管理', 2, 'com.boyue.web.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/menu', '127.0.0.1', '内网IP', '{\"children\":[],\"createTime\":\"2021-03-25 11:35:09\",\"icon\":\"cascader\",\"isCache\":\"0\",\"isFrame\":\"1\",\"menuId\":2053,\"menuName\":\"流程管理\",\"menuType\":\"M\",\"orderNum\":6,\"params\":{},\"parentId\":0,\"path\":\"flowable\",\"perms\":\"\",\"status\":\"1\",\"updateBy\":\"admin\",\"visible\":\"1\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 18:37:09', 27); +INSERT INTO `sys_oper_log` VALUES (106, '菜单管理', 2, 'com.boyue.web.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/menu', '127.0.0.1', '内网IP', '{\"children\":[],\"createTime\":\"2024-12-31 11:57:29\",\"icon\":\"message\",\"isCache\":\"0\",\"isFrame\":\"1\",\"menuId\":2021,\"menuName\":\"消息系统\",\"menuType\":\"M\",\"orderNum\":6,\"params\":{},\"parentId\":0,\"path\":\"modelMessage\",\"perms\":\"\",\"routeName\":\"\",\"status\":\"1\",\"updateBy\":\"admin\",\"visible\":\"1\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 18:37:14', 17); +INSERT INTO `sys_oper_log` VALUES (107, '菜单管理', 2, 'com.boyue.web.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/menu', '127.0.0.1', '内网IP', '{\"children\":[],\"createTime\":\"2024-03-07 19:38:34\",\"icon\":\"international\",\"isCache\":\"0\",\"isFrame\":\"1\",\"menuId\":2013,\"menuName\":\"Online\",\"menuType\":\"M\",\"orderNum\":5,\"params\":{},\"parentId\":0,\"path\":\"onlinedev\",\"perms\":\"\",\"routeName\":\"\",\"status\":\"1\",\"updateBy\":\"admin\",\"visible\":\"1\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 18:37:18', 23); +INSERT INTO `sys_oper_log` VALUES (108, '菜单管理', 2, 'com.boyue.web.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/menu', '127.0.0.1', '内网IP', '{\"children\":[],\"createTime\":\"2024-02-15 22:40:23\",\"icon\":\"excel\",\"isCache\":\"0\",\"isFrame\":\"1\",\"menuId\":2074,\"menuName\":\"文件管理\",\"menuType\":\"M\",\"orderNum\":4,\"params\":{},\"parentId\":0,\"path\":\"file\",\"perms\":\"\",\"routeName\":\"\",\"status\":\"1\",\"updateBy\":\"admin\",\"visible\":\"1\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 18:37:23', 29); +INSERT INTO `sys_oper_log` VALUES (109, '菜单管理', 2, 'com.boyue.web.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/menu', '127.0.0.1', '内网IP', '{\"children\":[],\"createTime\":\"2024-02-15 22:40:23\",\"icon\":\"form\",\"isCache\":\"0\",\"isFrame\":\"1\",\"menuId\":2040,\"menuName\":\"表单管理\",\"menuType\":\"M\",\"orderNum\":4,\"params\":{},\"parentId\":0,\"path\":\"formManagement\",\"perms\":\"\",\"routeName\":\"\",\"status\":\"1\",\"updateBy\":\"admin\",\"visible\":\"1\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 18:37:27', 26); +INSERT INTO `sys_oper_log` VALUES (110, '菜单管理', 2, 'com.boyue.web.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/menu', '127.0.0.1', '内网IP', '{\"children\":[],\"createTime\":\"2024-02-15 22:40:23\",\"icon\":\"money\",\"isCache\":\"0\",\"isFrame\":\"1\",\"menuId\":2000,\"menuName\":\"支付管理\",\"menuType\":\"M\",\"orderNum\":4,\"params\":{},\"parentId\":0,\"path\":\"pay\",\"perms\":\"\",\"routeName\":\"\",\"status\":\"1\",\"updateBy\":\"admin\",\"visible\":\"1\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 18:37:31', 20); +INSERT INTO `sys_oper_log` VALUES (111, '代码生成', 6, 'com.boyue.generator.controller.GenController.importTableSave()', 'POST', 1, 'admin', '研发部门', '/tool/gen/importTable', '127.0.0.1', '内网IP', '{\"tables\":\"html_pages\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 18:57:49', 51); +INSERT INTO `sys_oper_log` VALUES (112, '代码生成', 2, 'com.boyue.generator.controller.GenController.synchDb()', 'GET', 1, 'admin', '研发部门', '/tool/gen/synchDb/html_pages', '127.0.0.1', '内网IP', '{}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 19:04:38', 48); +INSERT INTO `sys_oper_log` VALUES (113, '代码生成', 2, 'com.boyue.generator.controller.GenController.editSave()', 'PUT', 1, 'admin', '研发部门', '/tool/gen', '127.0.0.1', '内网IP', '{\"allGenTableColumns\":[{\"capJavaField\":\"Id\",\"columnComment\":\"序号\",\"columnId\":1,\"columnName\":\"id\",\"columnType\":\"int(11)\",\"dictType\":\"\",\"edit\":false,\"htmlType\":\"input\",\"increment\":false,\"insert\":false,\"isIncrement\":\"0\",\"isInsert\":\"0\",\"isList\":\"1\",\"isPk\":\"1\",\"isRequired\":\"0\",\"javaField\":\"id\",\"javaType\":\"Long\",\"list\":true,\"params\":{},\"pk\":true,\"query\":false,\"queryType\":\"EQ\",\"required\":false,\"sort\":1,\"superColumn\":false,\"tableId\":1,\"usableColumn\":false},{\"capJavaField\":\"FormatId\",\"columnComment\":\"访问id\",\"columnId\":2,\"columnName\":\"format_id\",\"columnType\":\"varchar(6)\",\"dictType\":\"\",\"edit\":true,\"htmlType\":\"input\",\"increment\":false,\"insert\":true,\"isEdit\":\"1\",\"isIncrement\":\"0\",\"isInsert\":\"1\",\"isList\":\"1\",\"isPk\":\"0\",\"isQuery\":\"1\",\"isRequired\":\"1\",\"javaField\":\"formatId\",\"javaType\":\"String\",\"list\":true,\"params\":{},\"pk\":false,\"query\":true,\"queryType\":\"EQ\",\"required\":true,\"sort\":2,\"superColumn\":false,\"tableId\":1,\"usableColumn\":false},{\"capJavaField\":\"Title\",\"columnComment\":\"页面标题\",\"columnId\":3,\"columnName\":\"title\",\"columnType\":\"varchar(200)\",\"dictType\":\"\",\"edit\":true,\"htmlType\":\"input\",\"increment\":false,\"insert\":true,\"isEdit\":\"1\",\"isIncrement\":\"0\",\"isInsert\":\"1\",\"isList\":\"1\",\"isPk\":\"0\",\"isQuery\":\"1\",\"isRequired\":\"1\",\"javaField\":\"title\",\"javaType\":\"String\",\"list\":true,\"params\":{},\"pk\":false,\"query\":true,\"queryType\":\"EQ\",\"required\":true,\"sort\":3,\"superColumn\":false,\"tableId\":1,\"usableColumn\":false},{\"capJavaField\":\"Content\",\"columnComment\":\"HTML内容\",\"columnId\":4,\"columnName\":\"content\",\"columnType\":\"text\",\"dictType\":\"\",\"edit\":true,\"htmlType\":\"editor\",\"increment\":false,\"insert\":true,\"isEdit\":\"1\",\"isIncrement\":\"0\",\"isInsert\":\"1\",\"isList\":\"1\",\"isPk\":\"0\",\"isQuery\":\"1\",\"isRequired\":\"1\",\"javaField\":\"content\",\"javaType\":\"String\",\"list\":true,\"params\":{},\"pk\":false,\"query\":true,\"queryType\":\"EQ\",\"required\":true,\"sort\":4,\"superColumn\":false,\"tableId\":1,\"usableColumn\":false},{\"capJavaField\":\"PageType\",\"columnComment\":\"页面类型:法律规定 law、典型案例 case、表单下载from\",\"columnId\":5,\"columnName\":\"page_type\",\"columnTyp', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 19:06:45', 105); +INSERT INTO `sys_oper_log` VALUES (114, '代码生成', 8, 'com.boyue.generator.controller.GenController.batchGenCode()', 'GET', 1, 'admin', '研发部门', '/tool/gen/batchGenCode', '127.0.0.1', '内网IP', '{\"tables\":\"html_pages\"}', NULL, 0, NULL, '2025-05-28 19:06:51', 29); +INSERT INTO `sys_oper_log` VALUES (115, '菜单管理', 1, 'com.boyue.web.controller.system.SysMenuController.add()', 'POST', 1, 'admin', '研发部门', '/system/menu', '127.0.0.1', '内网IP', '{\"children\":[],\"createBy\":\"admin\",\"isCache\":\"0\",\"isFrame\":\"1\",\"menuName\":\"司法局\",\"menuType\":\"M\",\"orderNum\":4,\"params\":{},\"parentId\":0,\"path\":\"hasfj\",\"status\":\"0\",\"visible\":\"0\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 19:08:03', 28); +INSERT INTO `sys_oper_log` VALUES (116, '代码生成', 2, 'com.boyue.generator.controller.GenController.editSave()', 'PUT', 1, 'admin', '研发部门', '/tool/gen', '127.0.0.1', '内网IP', '{\"allGenTableColumns\":[{\"capJavaField\":\"Id\",\"columnComment\":\"序号\",\"columnId\":1,\"columnName\":\"id\",\"columnType\":\"int(11)\",\"dictType\":\"\",\"edit\":false,\"htmlType\":\"input\",\"increment\":false,\"insert\":false,\"isIncrement\":\"0\",\"isInsert\":\"0\",\"isList\":\"1\",\"isPk\":\"1\",\"isRequired\":\"0\",\"javaField\":\"id\",\"javaType\":\"Long\",\"list\":true,\"params\":{},\"pk\":true,\"query\":false,\"queryType\":\"EQ\",\"required\":false,\"sort\":1,\"superColumn\":false,\"tableId\":1,\"usableColumn\":false},{\"capJavaField\":\"FormatId\",\"columnComment\":\"访问id\",\"columnId\":2,\"columnName\":\"format_id\",\"columnType\":\"varchar(6)\",\"dictType\":\"\",\"edit\":true,\"htmlType\":\"input\",\"increment\":false,\"insert\":true,\"isEdit\":\"1\",\"isIncrement\":\"0\",\"isInsert\":\"1\",\"isList\":\"1\",\"isPk\":\"0\",\"isQuery\":\"1\",\"isRequired\":\"1\",\"javaField\":\"formatId\",\"javaType\":\"String\",\"list\":true,\"params\":{},\"pk\":false,\"query\":true,\"queryType\":\"EQ\",\"required\":true,\"sort\":2,\"superColumn\":false,\"tableId\":1,\"usableColumn\":false},{\"capJavaField\":\"Title\",\"columnComment\":\"页面标题\",\"columnId\":3,\"columnName\":\"title\",\"columnType\":\"varchar(200)\",\"dictType\":\"\",\"edit\":true,\"htmlType\":\"input\",\"increment\":false,\"insert\":true,\"isEdit\":\"1\",\"isIncrement\":\"0\",\"isInsert\":\"1\",\"isList\":\"1\",\"isPk\":\"0\",\"isQuery\":\"1\",\"isRequired\":\"1\",\"javaField\":\"title\",\"javaType\":\"String\",\"list\":true,\"params\":{},\"pk\":false,\"query\":true,\"queryType\":\"EQ\",\"required\":true,\"sort\":3,\"superColumn\":false,\"tableId\":1,\"usableColumn\":false},{\"capJavaField\":\"Content\",\"columnComment\":\"HTML内容\",\"columnId\":4,\"columnName\":\"content\",\"columnType\":\"text\",\"dictType\":\"\",\"edit\":true,\"htmlType\":\"editor\",\"increment\":false,\"insert\":true,\"isEdit\":\"1\",\"isIncrement\":\"0\",\"isInsert\":\"1\",\"isList\":\"1\",\"isPk\":\"0\",\"isQuery\":\"1\",\"isRequired\":\"1\",\"javaField\":\"content\",\"javaType\":\"String\",\"list\":true,\"params\":{},\"pk\":false,\"query\":true,\"queryType\":\"EQ\",\"required\":true,\"sort\":4,\"superColumn\":false,\"tableId\":1,\"usableColumn\":false},{\"capJavaField\":\"PageType\",\"columnComment\":\"页面类型:法律规定 law、典型案例 case、表单下载from\",\"columnId\":5,\"columnName\":\"page_type\",\"columnTyp', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 19:09:02', 55); +INSERT INTO `sys_oper_log` VALUES (117, '代码生成', 8, 'com.boyue.generator.controller.GenController.batchGenCode()', 'GET', 1, 'admin', '研发部门', '/tool/gen/batchGenCode', '127.0.0.1', '内网IP', '{\"tables\":\"html_pages\"}', NULL, 0, NULL, '2025-05-28 19:09:04', 24); +INSERT INTO `sys_oper_log` VALUES (118, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"content\":\"
      表单下载内容...
      \",\"createTime\":\"2025-05-28 18:55:57\",\"formatId\":\"000001\",\"id\":7,\"pageType\":\"from\",\"pageUrl\":\"http://localhost/table.html?Id=000001\",\"params\":{},\"sortOrder\":30,\"status\":1,\"title\":\"劳动仲裁申请表\",\"updateTime\":\"2025-05-28 20:23:58\",\"viewCount\":0}', NULL, 1, '\r\n### Error updating database. Cause: java.sql.SQLException: Data truncated for column \'page_type\' at row 1\r\n### The error may exist in file [E:\\WORK\\boyue-kfcode\\boyue-java\\boyue-models\\boyue-hasfj\\target\\classes\\mapper\\hasfj\\HtmlPagesMapper.xml]\r\n### The error may involve com.boyue.hasfj.mapper.HtmlPagesMapper.updateHtmlPages-Inline\r\n### The error occurred while setting parameters\r\n### SQL: update html_pages SET format_id = ?, title = ?, content = ?, page_type = ?, page_url = ?, create_time = ?, update_time = ?, status = ?, sort_order = ?, view_count = ? where id = ?\r\n### Cause: java.sql.SQLException: Data truncated for column \'page_type\' at row 1\n; Data truncated for column \'page_type\' at row 1', '2025-05-28 20:23:58', 144); +INSERT INTO `sys_oper_log` VALUES (119, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"content\":\"
      劳动法第四十条内容及解读...
      \",\"createTime\":\"2025-05-28 18:55:57\",\"formatId\":\"000001\",\"id\":1,\"pageType\":\"law\",\"pageUrl\":\"http://localhost/show.html?Id=000001\",\"params\":{},\"sortOrder\":10,\"status\":1,\"title\":\"劳动法第四十条解读\",\"updateTime\":\"2025-05-28 20:53:47\",\"viewCount\":26}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 20:53:47', 12); +INSERT INTO `sys_oper_log` VALUES (120, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"content\":\"
      表单下载内容...
      \",\"createTime\":\"2025-05-28 18:55:57\",\"formatId\":\"000002\",\"id\":8,\"pageType\":\"from\",\"pageUrl\":\"http://localhost/table.html?Id=000002\",\"params\":{},\"sortOrder\":31,\"status\":1,\"title\":\"劳动合同模板\",\"updateTime\":\"2025-05-28 20:54:06\",\"viewCount\":0}', NULL, 1, '\r\n### Error updating database. Cause: java.sql.SQLException: Data truncated for column \'page_type\' at row 1\r\n### The error may exist in file [E:\\WORK\\boyue-kfcode\\boyue-java\\boyue-models\\boyue-hasfj\\target\\classes\\mapper\\hasfj\\HtmlPagesMapper.xml]\r\n### The error may involve com.boyue.hasfj.mapper.HtmlPagesMapper.updateHtmlPages-Inline\r\n### The error occurred while setting parameters\r\n### SQL: update html_pages SET format_id = ?, title = ?, content = ?, page_type = ?, page_url = ?, create_time = ?, update_time = ?, status = ?, sort_order = ?, view_count = ? where id = ?\r\n### Cause: java.sql.SQLException: Data truncated for column \'page_type\' at row 1\n; Data truncated for column \'page_type\' at row 1', '2025-05-28 20:54:06', 5); +INSERT INTO `sys_oper_log` VALUES (121, '菜单管理', 2, 'com.boyue.web.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/menu', '127.0.0.1', '内网IP', '{\"children\":[],\"createTime\":\"2025-05-28 19:08:03\",\"icon\":\"#\",\"isCache\":\"0\",\"isFrame\":\"1\",\"menuId\":2087,\"menuName\":\"司法局百问百答\",\"menuType\":\"M\",\"orderNum\":4,\"params\":{},\"parentId\":0,\"path\":\"hasfj\",\"perms\":\"\",\"routeName\":\"\",\"status\":\"0\",\"updateBy\":\"admin\",\"visible\":\"0\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 21:19:06', 26); +INSERT INTO `sys_oper_log` VALUES (122, '菜单管理', 2, 'com.boyue.web.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/menu', '127.0.0.1', '内网IP', '{\"children\":[],\"createTime\":\"2025-05-28 19:08:03\",\"icon\":\"example\",\"isCache\":\"0\",\"isFrame\":\"1\",\"menuId\":2087,\"menuName\":\"司法局百问百答\",\"menuType\":\"M\",\"orderNum\":4,\"params\":{},\"parentId\":0,\"path\":\"hasfj\",\"perms\":\"\",\"routeName\":\"\",\"status\":\"0\",\"updateBy\":\"admin\",\"visible\":\"0\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 21:19:27', 13); +INSERT INTO `sys_oper_log` VALUES (123, '菜单管理', 2, 'com.boyue.web.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/menu', '127.0.0.1', '内网IP', '{\"children\":[],\"component\":\"hasfj/hasfjpages/index\",\"createTime\":\"2025-05-28 19:09:34\",\"icon\":\"#\",\"isCache\":\"0\",\"isFrame\":\"1\",\"menuId\":2088,\"menuName\":\"司法局百问百答内容配置\",\"menuType\":\"C\",\"orderNum\":1,\"params\":{},\"parentId\":2087,\"path\":\"hasfjpages\",\"perms\":\"hasfj:hasfjpages:list\",\"routeName\":\"\",\"status\":\"0\",\"updateBy\":\"admin\",\"visible\":\"0\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 21:21:31', 100); +INSERT INTO `sys_oper_log` VALUES (124, '菜单管理', 2, 'com.boyue.web.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/menu', '127.0.0.1', '内网IP', '{\"children\":[],\"component\":\"hasfj/hasfjpages/index\",\"createTime\":\"2025-05-28 19:09:34\",\"icon\":\"#\",\"isCache\":\"0\",\"isFrame\":\"1\",\"menuId\":2088,\"menuName\":\"内容配置\",\"menuType\":\"C\",\"orderNum\":1,\"params\":{},\"parentId\":2087,\"path\":\"hasfjpages\",\"perms\":\"hasfj:hasfjpages:list\",\"routeName\":\"\",\"status\":\"0\",\"updateBy\":\"admin\",\"visible\":\"0\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 21:22:07', 26); +INSERT INTO `sys_oper_log` VALUES (125, '菜单管理', 2, 'com.boyue.web.controller.system.SysMenuController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/menu', '127.0.0.1', '内网IP', '{\"children\":[],\"component\":\"hasfj/hasfjpages/index\",\"createTime\":\"2025-05-28 19:09:34\",\"icon\":\"edit\",\"isCache\":\"0\",\"isFrame\":\"1\",\"menuId\":2088,\"menuName\":\"内容配置\",\"menuType\":\"C\",\"orderNum\":1,\"params\":{},\"parentId\":2087,\"path\":\"hasfjpages\",\"perms\":\"hasfj:hasfjpages:list\",\"routeName\":\"\",\"status\":\"0\",\"updateBy\":\"admin\",\"visible\":\"0\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 21:22:15', 10); +INSERT INTO `sys_oper_log` VALUES (126, '司法局法律规定、典型案例、单下载', 1, 'com.boyue.hasfj.controller.HtmlPagesController.add()', 'POST', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"content\":\"

      000004

      \",\"createTime\":\"2025-05-28 21:31:01\",\"formatId\":\"000004\",\"pageType\":\"law\",\"pageUrl\":\"000004\",\"params\":{},\"sortOrder\":10,\"status\":1,\"title\":\"000004\",\"viewCount\":0}', NULL, 1, '\r\n### Error updating database. Cause: java.sql.SQLException: Field \'update_time\' doesn\'t have a default value\r\n### The error may exist in file [E:\\WORK\\boyue-kfcode\\boyue-java\\boyue-models\\boyue-hasfj\\target\\classes\\mapper\\hasfj\\HtmlPagesMapper.xml]\r\n### The error may involve com.boyue.hasfj.mapper.HtmlPagesMapper.insertHtmlPages-Inline\r\n### The error occurred while setting parameters\r\n### SQL: insert into html_pages ( format_id, title, content, page_type, page_url, create_time, status, sort_order, view_count ) values ( ?, ?, ?, ?, ?, ?, ?, ?, ? )\r\n### Cause: java.sql.SQLException: Field \'update_time\' doesn\'t have a default value\n; Field \'update_time\' doesn\'t have a default value', '2025-05-28 21:31:01', 4); +INSERT INTO `sys_oper_log` VALUES (127, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"1\",\"content\":\"
      劳动法第四十条内容及解读...
      \",\"createTime\":\"2025-05-28 18:55:57\",\"formatId\":\"000001\",\"id\":1,\"pageType\":\"law\",\"pageUrl\":\"http://localhost/show.html?Id=000001\",\"params\":{},\"sortOrder\":10,\"status\":1,\"title\":\"劳动法第四十条解读\",\"updateTime\":\"2025-05-28 21:32:08\",\"viewCount\":35}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 21:32:08', 7); +INSERT INTO `sys_oper_log` VALUES (128, '菜单管理', 3, 'com.boyue.web.controller.system.SysMenuController.remove()', 'DELETE', 1, 'admin', '研发部门', '/system/menu/2098', '127.0.0.1', '内网IP', '2098', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 21:48:55', 128); +INSERT INTO `sys_oper_log` VALUES (129, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 21:43:55\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-28 21:49:48\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 21:49:48', 122); +INSERT INTO `sys_oper_log` VALUES (130, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"案例分析组\",\"content\":\"

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      \",\"createTime\":\"2025-05-28 21:43:55\",\"description\":\"典型劳资纠纷案例分析及处理结果\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳资纠纷,加班费,劳动仲裁\",\"pageType\":\"case\",\"pageUrl\":\"/showcase.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"某公司劳资纠纷典型案例\",\"updateTime\":\"2025-05-28 21:50:00\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 21:50:00', 52); +INSERT INTO `sys_oper_log` VALUES (131, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 21:43:55\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":4,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动仲裁申请表\",\"updateTime\":\"2025-05-28 21:50:07\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 21:50:07', 18); +INSERT INTO `sys_oper_log` VALUES (132, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"法律法规编辑部\",\"content\":\"

      合同法关于订立合同的规定

      合同是平等主体的自然人、法人、其他组织之间设立、变更、终止民事权利义务关系的协议。婚姻、收养、监护等有关身份关系的协议,适用其他法律的规定。

      \",\"createTime\":\"2025-05-28 21:43:55\",\"description\":\"合同法对于订立合同的基本规定和解释\",\"formatId\":\"000002\",\"id\":2,\"keywords\":\"合同法,合同订立,民事权利\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000002\",\"params\":{},\"sortOrder\":2,\"status\":1,\"title\":\"合同法关于订立合同的规定\",\"updateTime\":\"2025-05-28 21:50:19\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 21:50:19', 84); +INSERT INTO `sys_oper_log` VALUES (133, '司法局法律规定、典型案例、单下载', 5, 'com.boyue.hasfj.controller.HtmlPagesController.export()', 'POST', 1, 'admin', '研发部门', '/hasfj/hasfjpages/export', '127.0.0.1', '内网IP', '{\"pageSize\":\"10\",\"pageNum\":\"1\"}', NULL, 0, NULL, '2025-05-28 22:14:32', 617); +INSERT INTO `sys_oper_log` VALUES (134, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-28 22:19:39\",\"viewCount\":3}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-28 22:19:39', 82); +INSERT INTO `sys_oper_log` VALUES (135, '角色管理', 1, 'com.boyue.web.controller.system.SysRoleController.add()', 'POST', 1, 'admin', '研发部门', '/system/role', '127.0.0.1', '内网IP', '{\"admin\":false,\"createBy\":\"admin\",\"deptCheckStrictly\":true,\"deptIds\":[],\"flag\":false,\"menuCheckStrictly\":true,\"menuIds\":[1,100,1000,1001,1002,1003,1004,1005,1006,2097,2099,2100,2101,2102,2103,2104],\"params\":{},\"roleId\":100,\"roleKey\":\"sfj\",\"roleName\":\"司法局\",\"roleSort\":3,\"status\":\"0\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:41:41', 74); +INSERT INTO `sys_oper_log` VALUES (136, '用户管理', 3, 'com.boyue.web.controller.system.SysUserController.remove()', 'DELETE', 1, 'admin', '研发部门', '/system/user/2', '127.0.0.1', '内网IP', '[2]', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:41:50', 49); +INSERT INTO `sys_oper_log` VALUES (137, '部门管理', 2, 'com.boyue.web.controller.system.SysDeptController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/dept', '127.0.0.1', '内网IP', '{\"ancestors\":\"0\",\"children\":[],\"deptId\":100,\"deptName\":\"博越科技\",\"email\":\"boyue@qq.com\",\"leader\":\"博越\",\"orderNum\":0,\"params\":{},\"parentId\":0,\"phone\":\"15888888888\",\"status\":\"0\",\"updateBy\":\"admin\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:42:28', 120); +INSERT INTO `sys_oper_log` VALUES (138, '部门管理', 3, 'com.boyue.web.controller.system.SysDeptController.remove()', 'DELETE', 1, 'admin', '研发部门', '/system/dept/102', '127.0.0.1', '内网IP', '102', '{\"msg\":\"存在下级部门,不允许删除\",\"code\":601}', 0, NULL, '2025-05-29 08:42:32', 4); +INSERT INTO `sys_oper_log` VALUES (139, '部门管理', 3, 'com.boyue.web.controller.system.SysDeptController.remove()', 'DELETE', 1, 'admin', '研发部门', '/system/dept/109', '127.0.0.1', '内网IP', '109', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:42:34', 25); +INSERT INTO `sys_oper_log` VALUES (140, '部门管理', 3, 'com.boyue.web.controller.system.SysDeptController.remove()', 'DELETE', 1, 'admin', '研发部门', '/system/dept/108', '127.0.0.1', '内网IP', '108', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:42:36', 91); +INSERT INTO `sys_oper_log` VALUES (141, '部门管理', 3, 'com.boyue.web.controller.system.SysDeptController.remove()', 'DELETE', 1, 'admin', '研发部门', '/system/dept/102', '127.0.0.1', '内网IP', '102', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:42:38', 22); +INSERT INTO `sys_oper_log` VALUES (142, '部门管理', 3, 'com.boyue.web.controller.system.SysDeptController.remove()', 'DELETE', 1, 'admin', '研发部门', '/system/dept/107', '127.0.0.1', '内网IP', '107', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:42:43', 29); +INSERT INTO `sys_oper_log` VALUES (143, '部门管理', 3, 'com.boyue.web.controller.system.SysDeptController.remove()', 'DELETE', 1, 'admin', '研发部门', '/system/dept/106', '127.0.0.1', '内网IP', '106', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:42:44', 30); +INSERT INTO `sys_oper_log` VALUES (144, '部门管理', 2, 'com.boyue.web.controller.system.SysDeptController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/dept', '127.0.0.1', '内网IP', '{\"ancestors\":\"0,100\",\"children\":[],\"deptId\":101,\"deptName\":\"淮安市\",\"email\":\"boyue@qq.com\",\"leader\":\"博越\",\"orderNum\":1,\"params\":{},\"parentId\":100,\"parentName\":\"博越科技\",\"phone\":\"15888888888\",\"status\":\"0\",\"updateBy\":\"admin\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:42:59', 76); +INSERT INTO `sys_oper_log` VALUES (145, '部门管理', 3, 'com.boyue.web.controller.system.SysDeptController.remove()', 'DELETE', 1, 'admin', '研发部门', '/system/dept/105', '127.0.0.1', '内网IP', '105', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:43:02', 16); +INSERT INTO `sys_oper_log` VALUES (146, '部门管理', 3, 'com.boyue.web.controller.system.SysDeptController.remove()', 'DELETE', 1, 'admin', '研发部门', '/system/dept/104', '127.0.0.1', '内网IP', '104', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:43:04', 20); +INSERT INTO `sys_oper_log` VALUES (147, '部门管理', 2, 'com.boyue.web.controller.system.SysDeptController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/dept', '127.0.0.1', '内网IP', '{\"ancestors\":\"0,100,101\",\"children\":[],\"deptId\":103,\"deptName\":\"司法局部门\",\"email\":\"sfj@qq.com\",\"leader\":\"司法局\",\"orderNum\":1,\"params\":{},\"parentId\":101,\"parentName\":\"淮安市\",\"phone\":\"15888888888\",\"status\":\"0\",\"updateBy\":\"admin\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:43:41', 130); +INSERT INTO `sys_oper_log` VALUES (148, '部门管理', 2, 'com.boyue.web.controller.system.SysDeptController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/dept', '127.0.0.1', '内网IP', '{\"ancestors\":\"0,100,101\",\"children\":[],\"deptId\":103,\"deptName\":\"司法局部门\",\"email\":\"sfj@qq.com\",\"leader\":\"司法局\",\"orderNum\":2,\"params\":{},\"parentId\":101,\"parentName\":\"淮安市\",\"phone\":\"15888888888\",\"status\":\"0\",\"updateBy\":\"admin\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:43:46', 29); +INSERT INTO `sys_oper_log` VALUES (149, '部门管理', 2, 'com.boyue.web.controller.system.SysDeptController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/dept', '127.0.0.1', '内网IP', '{\"ancestors\":\"0,100,101\",\"children\":[],\"deptId\":103,\"deptName\":\"管理\",\"email\":\"sfj@qq.com\",\"leader\":\"管理\",\"orderNum\":2,\"params\":{},\"parentId\":101,\"parentName\":\"淮安市\",\"phone\":\"15888888888\",\"status\":\"0\",\"updateBy\":\"admin\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:44:34', 66); +INSERT INTO `sys_oper_log` VALUES (150, '部门管理', 1, 'com.boyue.web.controller.system.SysDeptController.add()', 'POST', 1, 'admin', '研发部门', '/system/dept', '127.0.0.1', '内网IP', '{\"ancestors\":\"0,100,101\",\"children\":[],\"createBy\":\"admin\",\"deptName\":\"司法局\",\"orderNum\":3,\"params\":{},\"parentId\":101,\"status\":\"0\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:44:51', 109); +INSERT INTO `sys_oper_log` VALUES (151, '用户管理', 1, 'com.boyue.web.controller.system.SysUserController.add()', 'POST', 1, 'admin', '研发部门', '/system/user', '127.0.0.1', '内网IP', '{\"admin\":false,\"createBy\":\"admin\",\"deptId\":103,\"nickName\":\"淮安市司法局\",\"params\":{},\"postIds\":[],\"roleIds\":[100],\"status\":\"0\",\"userId\":100,\"userName\":\"hasfj\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:45:27', 97); +INSERT INTO `sys_oper_log` VALUES (152, '用户管理', 2, 'com.boyue.web.controller.system.SysUserController.edit()', 'PUT', 1, 'admin', '研发部门', '/system/user', '127.0.0.1', '内网IP', '{\"admin\":false,\"avatar\":\"\",\"createBy\":\"admin\",\"createTime\":\"2025-05-29 08:45:27\",\"delFlag\":\"0\",\"dept\":{\"ancestors\":\"0,100,101\",\"children\":[],\"deptId\":103,\"deptName\":\"管理\",\"leader\":\"管理\",\"orderNum\":2,\"params\":{},\"parentId\":101,\"status\":\"0\"},\"deptId\":200,\"email\":\"\",\"loginIp\":\"\",\"nickName\":\"淮安市司法局\",\"params\":{},\"phonenumber\":\"\",\"postIds\":[],\"roleIds\":[100],\"roles\":[{\"admin\":false,\"dataScope\":\"1\",\"deptCheckStrictly\":false,\"flag\":false,\"menuCheckStrictly\":false,\"params\":{},\"roleId\":100,\"roleKey\":\"sfj\",\"roleName\":\"司法局\",\"roleSort\":3,\"status\":\"0\"}],\"sex\":\"0\",\"status\":\"0\",\"updateBy\":\"admin\",\"userId\":100,\"userName\":\"hasfj\"}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 08:45:53', 101); +INSERT INTO `sys_oper_log` VALUES (153, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"attachmentUrl\":\"http://localhost:9799/profile/files/master/2025/05/29/20250529090531_b976.png\",\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-29 09:05:34\",\"viewCount\":6}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:05:34', 98); +INSERT INTO `sys_oper_log` VALUES (154, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"attachmentUrl\":\"http://localhost:9799/profile/files/master/2025/05/29/20250529090601_37f6.xls\",\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"劳动仲裁申请表\",\"updateTime\":\"2025-05-29 09:06:03\",\"viewCount\":2}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:06:03', 50); +INSERT INTO `sys_oper_log` VALUES (155, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"attachmentUrl\":\"http://localhost:9799/profile/files/master/2025/05/29/20250529090531_b976.png\",\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-29 09:06:36\",\"viewCount\":7}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:06:36', 33); +INSERT INTO `sys_oper_log` VALUES (156, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"attachmentUrl\":\"http://localhost:9799/profile/files/master/2025/05/29/20250529090531_b976.png\",\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-29 09:06:55\",\"viewCount\":7}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:06:56', 114); +INSERT INTO `sys_oper_log` VALUES (157, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"attachmentUrl\":\"http://localhost:9799/profile/files/master/2025/05/29/20250529090531_b976.png\",\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-29 09:09:59\",\"viewCount\":7}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:09:59', 118); +INSERT INTO `sys_oper_log` VALUES (158, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"attachmentUrl\":\"http://localhost:9799/profile/files/master/2025/05/29/20250529090531_b976.png\",\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-29 09:10:41\",\"viewCount\":7}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:10:41', 130); +INSERT INTO `sys_oper_log` VALUES (159, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-29 09:17:00\",\"viewCount\":7}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:17:00', 144); +INSERT INTO `sys_oper_log` VALUES (160, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[{\\\"name\\\":\\\"hasfjpages_1748441672046.xlsx\\\",\\\"url\\\":\\\"http://localhost:9799/profile/files/master/2025/05/29/20250529091727_6301.xlsx\\\"},{\\\"name\\\":\\\"boyue.zip\\\",\\\"url\\\":\\\"http://localhost:9799/profile/files/master/2025/05/29/20250529092056_cc9b.zip\\\"}]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-29 09:20:57\",\"viewCount\":7}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:20:58', 187); +INSERT INTO `sys_oper_log` VALUES (161, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"attachmentUrl\":\"http://localhost:9799/profile/files/master/2025/05/29/20250529090531_b976.png\",\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[{\\\"name\\\":\\\"hasfjpages_1748441672046.xlsx\\\",\\\"url\\\":\\\"http://localhost:9799/profile/files/master/2025/05/29/20250529091727_6301.xlsx\\\"}]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-29 09:21:13\",\"viewCount\":7}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:21:13', 51); +INSERT INTO `sys_oper_log` VALUES (162, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[{\\\"name\\\":\\\"hasfjpages_1748441672046.xlsx\\\",\\\"url\\\":\\\"http://localhost:9799/profile/files/master/2025/05/29/20250529091727_6301.xlsx\\\"}]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-29 09:21:22\",\"viewCount\":7}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:21:22', 44); +INSERT INTO `sys_oper_log` VALUES (163, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"attachmentUrl\":\"http://localhost:9799/profile/files/master/2025/05/29/20250529090531_b976.png\",\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[{\\\"name\\\":\\\"hasfjpages_1748441672046.xlsx\\\",\\\"url\\\":\\\"http://localhost:9799/profile/files/master/2025/05/29/20250529091727_6301.xlsx\\\"},{\\\"name\\\":\\\"boyue.zip\\\",\\\"url\\\":\\\"http://localhost:9799/profile/files/master/2025/05/29/20250529092056_cc9b.zip\\\"}]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-29 09:23:39\",\"viewCount\":7}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:23:39', 122); +INSERT INTO `sys_oper_log` VALUES (164, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"attachmentUrl\":\"\",\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-29 09:24:17\",\"viewCount\":7}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:24:17', 91); +INSERT INTO `sys_oper_log` VALUES (165, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"hasfjpages_1748441672046.xlsx\\\",\\\"url\\\":\\\"http://localhost:9799/profile/files/master/2025/05/29/20250529091727_6301.xlsx\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"劳动仲裁申请表\",\"updateTime\":\"2025-05-29 09:28:48\",\"viewCount\":4}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:28:48', 31); +INSERT INTO `sys_oper_log` VALUES (166, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"hasfjpages_1748441672046.xlsx\\\",\\\"url\\\":\\\"http://localhost:9799/profile/files/master/2025/05/29/20250529091727_6301.xlsx\\\"},{\\\"name\\\":\\\"boyue.zip\\\",\\\"url\\\":\\\"http://localhost:9799/profile/files/master/2025/05/29/20250529092056_cc9b.zip\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"劳动仲裁申请表\",\"updateTime\":\"2025-05-29 09:29:02\",\"viewCount\":5}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:29:02', 95); +INSERT INTO `sys_oper_log` VALUES (167, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"劳动仲裁申请表\",\"updateTime\":\"2025-05-29 09:29:19\",\"viewCount\":6}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:29:19', 23); +INSERT INTO `sys_oper_log` VALUES (168, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '研发部门', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"法律法规编辑部\",\"content\":\"

      测试

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-29 09:38:39\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 09:38:39', 64); +INSERT INTO `sys_oper_log` VALUES (169, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"boyue.zip\\\",\\\"url\\\":\\\"http://localhost:9799/profile/files/master/2025/05/29/20250529092056_cc9b.zip\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"劳动仲裁申请表\",\"updateTime\":\"2025-05-29 11:30:45\",\"viewCount\":9}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 11:30:45', 23); +INSERT INTO `sys_oper_log` VALUES (170, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"法律法规编辑部\",\"content\":\"

      《公司法》《个人独资企业法》《合伙企业法》

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"劳动法第四十一条解释\",\"updateTime\":\"2025-05-29 07:02:45\",\"viewCount\":215}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:02:45', 104); +INSERT INTO `sys_oper_log` VALUES (171, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"案例分析组\",\"content\":\"

      李某与王某成立“普通合伙企业”经营餐饮,后因亏损负债100万元,法院判令李某、王某承担无限连带责任。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"典型劳资纠纷案例分析及处理结果\",\"formatId\":\"000001\",\"id\":2,\"keywords\":\"劳资纠纷,加班费,劳动仲裁\",\"multiAttachments\":\"[]\",\"pageType\":\"case\",\"pageUrl\":\"/showcase.html?Id=000001\",\"params\":{},\"sortOrder\":2,\"status\":1,\"title\":\"某公司劳资纠纷典型案例\",\"updateTime\":\"2025-05-29 07:03:38\",\"viewCount\":4}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:03:38', 115); +INSERT INTO `sys_oper_log` VALUES (172, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"boyue.zip\\\",\\\"url\\\":\\\"http://localhost:9799/profile/files/master/2025/05/29/20250529092056_cc9b.zip\\\"},{\\\"name\\\":\\\"淮安市“七五”普法调查问卷.doc\\\",\\\"url\\\":\\\"http://hj.haxy.com.cn/profile/files/master/2025/05/29/20250529070442_e2a0.doc\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"劳动仲裁申请表\",\"updateTime\":\"2025-05-29 07:04:51\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:04:51', 60); +INSERT INTO `sys_oper_log` VALUES (173, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"法律法规编辑部\",\"content\":\"

      《公司法》《个人独资企业法》《合伙企业法》

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000001\",\"id\":1,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000001\",\"params\":{},\"sortOrder\":1,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-29 07:07:55\",\"viewCount\":215}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:07:55', 82); +INSERT INTO `sys_oper_log` VALUES (174, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"案例分析组\",\"content\":\"

      李某与王某成立“普通合伙企业”经营餐饮,后因亏损负债100万元,法院判令李某、王某承担无限连带责任。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"典型劳资纠纷案例分析及处理结果\",\"formatId\":\"000001\",\"id\":2,\"keywords\":\"劳资纠纷,加班费,劳动仲裁\",\"multiAttachments\":\"[]\",\"pageType\":\"case\",\"pageUrl\":\"/showcase.html?Id=000001\",\"params\":{},\"sortOrder\":2,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-29 07:08:06\",\"viewCount\":4}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:08:06', 46); +INSERT INTO `sys_oper_log` VALUES (175, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"boyue.zip\\\",\\\"url\\\":\\\"http://localhost:9799/profile/files/master/2025/05/29/20250529092056_cc9b.zip\\\"},{\\\"name\\\":\\\"淮安市“七五”普法调查问卷.doc\\\",\\\"url\\\":\\\"http://hj.haxy.com.cn/profile/files/master/2025/05/29/20250529070442_e2a0.doc\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-29 07:08:16\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:08:16', 38); +INSERT INTO `sys_oper_log` VALUES (176, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000002\",\"id\":4,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000002\",\"params\":{},\"sortOrder\":4,\"status\":1,\"title\":\"有限责任公司和股份有限公司的区别是什么?\",\"updateTime\":\"2025-05-29 07:08:49\",\"viewCount\":17}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:08:49', 56); +INSERT INTO `sys_oper_log` VALUES (177, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"案例分析组\",\"content\":\"

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"典型劳资纠纷案例分析及处理结果\",\"formatId\":\"000002\",\"id\":5,\"keywords\":\"劳资纠纷,加班费,劳动仲裁\",\"multiAttachments\":\"[]\",\"pageType\":\"case\",\"pageUrl\":\"/showcase.html?Id=000002\",\"params\":{},\"sortOrder\":5,\"status\":1,\"title\":\"有限责任公司和股份有限公司的区别是什么?\",\"updateTime\":\"2025-05-29 07:08:59\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:08:59', 52); +INSERT INTO `sys_oper_log` VALUES (178, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000002\",\"id\":6,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000002\",\"params\":{},\"sortOrder\":6,\"status\":1,\"title\":\"有限责任公司和股份有限公司的区别是什么?\",\"updateTime\":\"2025-05-29 07:09:10\",\"viewCount\":1}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:09:11', 41); +INSERT INTO `sys_oper_log` VALUES (179, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"法律法规编辑部\",\"content\":\"

      劳动法第四十一条解释

      用人单位由于生产经营需要,经与工会和劳动者协商后可以延长工作时间,一般每日不得超过一小时;因特殊原因需要延长工作时间的,在保障劳动者身体健康的条件下延长工作时间每日不得超过三小时,但是每月不得超过三十六小时。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000003\",\"id\":7,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000003\",\"params\":{},\"sortOrder\":7,\"status\":1,\"title\":\"如何确定企业名称?\",\"updateTime\":\"2025-05-29 07:09:30\",\"viewCount\":15}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:09:30', 36); +INSERT INTO `sys_oper_log` VALUES (180, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"案例分析组\",\"content\":\"

      某公司劳资纠纷典型案例

      本案例涉及员工加班费纠纷,公司未按照劳动法规定支付加班工资,经劳动仲裁委员会调解后,公司补发了加班工资并进行了赔偿。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"典型劳资纠纷案例分析及处理结果\",\"formatId\":\"000003\",\"id\":8,\"keywords\":\"劳资纠纷,加班费,劳动仲裁\",\"multiAttachments\":\"[]\",\"pageType\":\"case\",\"pageUrl\":\"/showcase.html?Id=000003\",\"params\":{},\"sortOrder\":8,\"status\":1,\"title\":\"如何确定企业名称?\",\"updateTime\":\"2025-05-29 07:09:40\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:09:40', 27); +INSERT INTO `sys_oper_log` VALUES (181, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000003\",\"id\":9,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000003\",\"params\":{},\"sortOrder\":9,\"status\":1,\"title\":\"如何确定企业名称?\",\"updateTime\":\"2025-05-29 07:09:50\",\"viewCount\":2}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:09:50', 17); +INSERT INTO `sys_oper_log` VALUES (182, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"法律法规编辑部\",\"content\":\"

      《证券法》第七十八条,发行人及法律、行政法规和国务院证券监督管理机构规定的其他信息披露义务人,应当及时依法履行信息披露义务。信息披露义务人披露的信息,应当真实、准确、完整,简明清晰,通俗易懂,不得有虚假记载、误导性陈述或者重大遗漏。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000002\",\"id\":4,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000002\",\"params\":{},\"sortOrder\":4,\"status\":1,\"title\":\"有限责任公司和股份有限公司的区别是什么?\",\"updateTime\":\"2025-05-29 07:11:12\",\"viewCount\":17}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:11:12', 92); +INSERT INTO `sys_oper_log` VALUES (183, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"案例分析组\",\"content\":\"

      某科技创业团队初期仅3名股东,误以为股份公司“更规范易融资”,直接注册为股份公司。因股份公司需设股东大会、董事会、监事会,团队人数不足,决策效率低下;同时需定期披露年报、重大事项,合规成本高。天使投资机构因其结构复杂、股权转让受限(需股东大会批准)放弃投资,最终公司因资金链断裂解散清算。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"典型劳资纠纷案例分析及处理结果\",\"formatId\":\"000002\",\"id\":5,\"keywords\":\"劳资纠纷,加班费,劳动仲裁\",\"multiAttachments\":\"[]\",\"pageType\":\"case\",\"pageUrl\":\"/showcase.html?Id=000002\",\"params\":{},\"sortOrder\":5,\"status\":1,\"title\":\"有限责任公司和股份有限公司的区别是什么?\",\"updateTime\":\"2025-05-29 07:11:33\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:11:33', 65); +INSERT INTO `sys_oper_log` VALUES (184, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"法律法规编辑部\",\"content\":\"

      《企业名称登记管理规定》《企业名称登记管理规定实施办法》。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000003\",\"id\":7,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000003\",\"params\":{},\"sortOrder\":7,\"status\":1,\"title\":\"如何确定企业名称?\",\"updateTime\":\"2025-05-29 07:11:54\",\"viewCount\":15}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:11:54', 41); +INSERT INTO `sys_oper_log` VALUES (185, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"案例分析组\",\"content\":\"

      甲公司注册名称为“上海特斯拉新能源科技有限公司”,未经特斯拉公司授权。法院认定其名称攀附知名品牌商誉,构成不正当竞争,判令更名并赔偿20万元。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"典型劳资纠纷案例分析及处理结果\",\"formatId\":\"000003\",\"id\":8,\"keywords\":\"劳资纠纷,加班费,劳动仲裁\",\"multiAttachments\":\"[]\",\"pageType\":\"case\",\"pageUrl\":\"/showcase.html?Id=000003\",\"params\":{},\"sortOrder\":8,\"status\":1,\"title\":\"如何确定企业名称?\",\"updateTime\":\"2025-05-29 07:12:11\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:12:11', 44); +INSERT INTO `sys_oper_log` VALUES (186, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"法律法规编辑部\",\"content\":\"

      《公司法》第十条,公司的法定代表人按照公司章程的规定,由代表公司执行公司事务的董事或者经理担任。担任法定代表人的董事或者经理辞任的,视为同时辞去法定代表人。法定代表人辞任的,公司应当在法定代表人辞任之日起三十日内确定新的法定代表人。第十一条:法定代表人以公司名义从事的民事活动,其法律后果由公司承受。公司章程或者股东会对法定代表人职权的限制,不得对抗善意相对人。法定代表人因执行职务造成他人损害的,由公司承担民事责任。公司承担民事责任后,依照法律或者公司章程的规定,可以向有过错的法定代表人追偿。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000004\",\"id\":10,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000004\",\"params\":{},\"sortOrder\":10,\"status\":1,\"title\":\"法定代表人的要求有哪些?\",\"updateTime\":\"2025-05-29 07:12:43\",\"viewCount\":7}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:12:43', 51); +INSERT INTO `sys_oper_log` VALUES (187, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"案例分析组\",\"content\":\"

      某公司法定代表人李某因个人债务被列为失信被执行人,法院限制其高消费。公司向银行申请贷款时,因李某信用瑕疵遭拒,资金链断裂导致停产。市场监管部门责令更换法定代表人,但因股东争议拖延,最终公司破产清算。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"典型劳资纠纷案例分析及处理结果\",\"formatId\":\"000004\",\"id\":11,\"keywords\":\"劳资纠纷,加班费,劳动仲裁\",\"multiAttachments\":\"[]\",\"pageType\":\"case\",\"pageUrl\":\"/showcase.html?Id=000004\",\"params\":{},\"sortOrder\":11,\"status\":1,\"title\":\"法定代表人的要求有哪些?\",\"updateTime\":\"2025-05-29 07:13:16\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:13:16', 75); +INSERT INTO `sys_oper_log` VALUES (188, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000004\",\"id\":12,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000004\",\"params\":{},\"sortOrder\":12,\"status\":1,\"title\":\"法定代表人的要求有哪些?\",\"updateTime\":\"2025-05-29 07:13:34\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:13:35', 75); +INSERT INTO `sys_oper_log` VALUES (189, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"法律法规编辑部\",\"content\":\"

      《公司法》第一百七十八条,有下列情形之一的,不得担任公司的董事、监事、高级管理人员:(一)无民事行为能力或者限制民事行为能力;(二)因贪污、贿赂、侵占财产、挪用财产或者破坏社会主义市场经济秩序,被判处刑罚,或者因犯罪被剥夺政治权利,执行期满未逾五年,被宣告缓刑的,自缓刑考验期满之日起未逾二年;(三)担任破产清算的公司、企业的董事或者厂长、经理,对该公司、企业的破产负有个人责任的,自该公司、企业破产清算完结之日起未逾三年;(四)担任因违法被吊销营业执照、责令关闭的公司、企业的法定代表人,并负有个人责任的,自该公司、企业被吊销营业执照、责令关闭之日起未逾三年;(五)个人因所负数额较大债务到期未清偿被人民法院列为失信被执行人。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000005\",\"id\":13,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000005\",\"params\":{},\"sortOrder\":13,\"status\":1,\"title\":\"董事、监事、高级管理人员的资格限制有哪些?\",\"updateTime\":\"2025-05-29 07:14:05\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:14:05', 56); +INSERT INTO `sys_oper_log` VALUES (190, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"案例分析组\",\"content\":\"

      某公司股东李某(公务员)通过亲属代持股权,后因公司债务纠纷被债权人揭发。法院认定代持协议无效,李某需退还全部股权收益,公司被处罚款10万元。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"典型劳资纠纷案例分析及处理结果\",\"formatId\":\"000005\",\"id\":14,\"keywords\":\"劳资纠纷,加班费,劳动仲裁\",\"multiAttachments\":\"[]\",\"pageType\":\"case\",\"pageUrl\":\"/showcase.html?Id=000005\",\"params\":{},\"sortOrder\":14,\"status\":1,\"title\":\"董事、监事、高级管理人员的资格限制有哪些?\",\"updateTime\":\"2025-05-29 07:14:42\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:14:42', 51); +INSERT INTO `sys_oper_log` VALUES (191, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000005\",\"id\":15,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000005\",\"params\":{},\"sortOrder\":15,\"status\":1,\"title\":\"董事、监事、高级管理人员的资格限制有哪些?\",\"updateTime\":\"2025-05-29 07:14:52\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:14:52', 39); +INSERT INTO `sys_oper_log` VALUES (192, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"法律法规编辑部\",\"content\":\"

      最高人民法院关于适用《中华人民共和国公司法》若干问题的规定(三)第二十四条,有限责任公司的实际出资人与名义出资人订立合同,约定由实际出资人出资并享有投资权益,以名义出资人为名义股东,实际出资人与名义股东对该合同效力发生争议的,如无法律规定的无效情形,人民法院应当认定该合同有效。前款规定的实际出资人与名义股东因投资权益的归属发生争议,实际出资人以其实际履行了出资义务为由向名义股东主张权利的,人民法院应予支持。名义股东以公司股东名册记载、公司登记机关登记为由否认实际出资人权利的,人民法院不予支持。实际出资人未经公司其他股东半数以上同意,请求公司变更股东、签发出资证明书、记载于股东名册、记载于公司章程并办理公司登记机关登记的,人民法院不予支持。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000006\",\"id\":16,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000006\",\"params\":{},\"sortOrder\":16,\"status\":1,\"title\":\"隐名股东有哪些常见法律风险?\",\"updateTime\":\"2025-05-29 07:15:42\",\"viewCount\":1}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:15:42', 48); +INSERT INTO `sys_oper_log` VALUES (193, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"案例分析组\",\"content\":\"

      隐名股东李某委托张某代持某公司10%股权,张某未经同意将股权转让给王某,王某不知情且支付合理价款。王某善意取得股权,李某仅能要求张某赔偿损失。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"典型劳资纠纷案例分析及处理结果\",\"formatId\":\"000006\",\"id\":17,\"keywords\":\"劳资纠纷,加班费,劳动仲裁\",\"multiAttachments\":\"[]\",\"pageType\":\"case\",\"pageUrl\":\"/showcase.html?Id=000006\",\"params\":{},\"sortOrder\":17,\"status\":1,\"title\":\"隐名股东有哪些常见法律风险?\",\"updateTime\":\"2025-05-29 07:16:04\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:16:04', 57); +INSERT INTO `sys_oper_log` VALUES (194, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000006\",\"id\":18,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000006\",\"params\":{},\"sortOrder\":18,\"status\":1,\"title\":\"隐名股东有哪些常见法律风险?\",\"updateTime\":\"2025-05-29 07:16:18\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:16:18', 72); +INSERT INTO `sys_oper_log` VALUES (195, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"法律法规编辑部\",\"content\":\"

      《公司法》第六十五条,股东会会议由股东按照出资比例行使表决权;但是,公司章程另有规定的除外。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000007\",\"id\":19,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000007\",\"params\":{},\"sortOrder\":19,\"status\":1,\"title\":\"如何保持创始人在公司的控制权?\",\"updateTime\":\"2025-05-29 07:16:51\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:16:51', 74); +INSERT INTO `sys_oper_log` VALUES (196, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"案例分析组\",\"content\":\"

      某公司创始人通过“AB股”架构(1股10票投票权),即使持股比例仅15%,仍掌控董事会80%席位。公司上市后,其投票权超80%,确保战略决策权。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"典型劳资纠纷案例分析及处理结果\",\"formatId\":\"000007\",\"id\":20,\"keywords\":\"劳资纠纷,加班费,劳动仲裁\",\"multiAttachments\":\"[]\",\"pageType\":\"case\",\"pageUrl\":\"/showcase.html?Id=000007\",\"params\":{},\"sortOrder\":20,\"status\":1,\"title\":\"如何保持创始人在公司的控制权?\",\"updateTime\":\"2025-05-29 07:17:15\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:17:15', 54); +INSERT INTO `sys_oper_log` VALUES (197, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000007\",\"id\":21,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000007\",\"params\":{},\"sortOrder\":21,\"status\":1,\"title\":\"如何保持创始人在公司的控制权?\",\"updateTime\":\"2025-05-29 07:17:28\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:17:28', 49); +INSERT INTO `sys_oper_log` VALUES (198, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"法律法规编辑部\",\"content\":\"

      \\t《公司法》第二百三十一条,公司经营管理发生严重困难,继续存续会使股东利益受到重大损失,通过其他途径不能解决的,持有公司百分之十以上表决权的股东,可以请求人民法院解散公司。

      \\t最高人民法院关于适用《中华人民共和国公司法》若干问题的规定(二)第一条,单独或者合计持有公司全部股东表决权百分之十以上的股东,以下列事由之一提起解散公司诉讼,并符合公司法第一百八十二条规定的,人民法院应予受理:(一)公司持续两年以上无法召开股东会或者股东大会,公司经营管理发生严重困难的;(二)股东表决时无法达到法定或者公司章程规定的比例,持续两年以上不能做出有效的股东会或者股东大会决议,公司经营管理发生严重困难的;(三)公司董事长期冲突,且无法通过股东会或者股东大会解决,公司经营管理发生严重困难的;


      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000008\",\"id\":22,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000008\",\"params\":{},\"sortOrder\":22,\"status\":1,\"title\":\"如何防止出现公司僵局?\",\"updateTime\":\"2025-05-29 07:17:59\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:17:59', 68); +INSERT INTO `sys_oper_log` VALUES (199, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"案例分析组\",\"content\":\"

      某零售企业两位股东各持50%股权,因经营理念严重冲突,股东会无法通过重大决策(如拓展新店、引入投资),公司现金流枯竭,员工离职,被法院裁定解散。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"典型劳资纠纷案例分析及处理结果\",\"formatId\":\"000008\",\"id\":23,\"keywords\":\"劳资纠纷,加班费,劳动仲裁\",\"multiAttachments\":\"[]\",\"pageType\":\"case\",\"pageUrl\":\"/showcase.html?Id=000008\",\"params\":{},\"sortOrder\":23,\"status\":1,\"title\":\"如何防止出现公司僵局?\",\"updateTime\":\"2025-05-29 07:18:27\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:18:27', 61); +INSERT INTO `sys_oper_log` VALUES (200, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000008\",\"id\":24,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000008\",\"params\":{},\"sortOrder\":24,\"status\":1,\"title\":\"如何防止出现公司僵局?\",\"updateTime\":\"2025-05-29 07:18:40\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:18:40', 48); +INSERT INTO `sys_oper_log` VALUES (201, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"法律法规编辑部\",\"content\":\"

      《公司法》第四条,有限责任公司的股东以其认缴的出资额为限对公司承担责任;股份有限公司的股东以其认购的股份为限对公司承担责任。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"本文解释了《中华人民共和国劳动法》第四十一条关于加班时间的规定\",\"formatId\":\"000009\",\"id\":25,\"keywords\":\"劳动法,工作时间,加班\",\"multiAttachments\":\"[]\",\"pageType\":\"law\",\"pageUrl\":\"/show.html?Id=000009\",\"params\":{},\"sortOrder\":25,\"status\":1,\"title\":\"如何合理确定注册资本?\",\"updateTime\":\"2025-05-29 07:19:12\",\"viewCount\":1}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:19:12', 51); +INSERT INTO `sys_oper_log` VALUES (202, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"案例分析组\",\"content\":\"

      \\t某公司注册资本8000万元(认缴期30年),实际运营资金仅200万元。后因合同纠纷负债500万元,法院认定股东恶意设置超长认缴期,判令股东立即补缴。


      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"典型劳资纠纷案例分析及处理结果\",\"formatId\":\"000009\",\"id\":26,\"keywords\":\"劳资纠纷,加班费,劳动仲裁\",\"multiAttachments\":\"[]\",\"pageType\":\"case\",\"pageUrl\":\"/showcase.html?Id=000009\",\"params\":{},\"sortOrder\":26,\"status\":1,\"title\":\"如何合理确定注册资本?\",\"updateTime\":\"2025-05-29 07:19:32\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:19:32', 69); +INSERT INTO `sys_oper_log` VALUES (203, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'hasfj', '司法局', '/hasfj/hasfjpages', '114.238.34.181', 'XX XX', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000009\",\"id\":27,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000009\",\"params\":{},\"sortOrder\":27,\"status\":1,\"title\":\"如何合理确定注册资本?\",\"updateTime\":\"2025-05-29 07:19:44\",\"viewCount\":0}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-29 15:19:44', 117); +INSERT INTO `sys_oper_log` VALUES (204, '司法局法律规定、典型案例、单下载-删除附件', 3, 'com.boyue.hasfj.controller.HtmlPagesController.deleteAttachment()', 'POST', 1, 'admin', '管理', '/hasfj/hasfjpages/deleteAttachment', '49.87.176.188', 'XX XX', '{\"attachmentUrl\":\"http://localhost:9799/profile/files/master/2025/05/29/20250529092056_cc9b.zip\"}', '{\"msg\":\"删除附件失败\",\"code\":500}', 0, NULL, '2025-05-31 12:12:52', 47); +INSERT INTO `sys_oper_log` VALUES (205, '司法局法律规定、典型案例、单下载-删除附件', 3, 'com.boyue.hasfj.controller.HtmlPagesController.deleteAttachment()', 'POST', 1, 'admin', '管理', '/hasfj/hasfjpages/deleteAttachment', '49.87.176.188', 'XX XX', '{\"attachmentUrl\":\"http://hj.haxy.com.cn/profile/files/master/2025/05/29/20250529070442_e2a0.doc\"}', '{\"msg\":\"删除附件失败\",\"code\":500}', 0, NULL, '2025-05-31 12:12:56', 0); +INSERT INTO `sys_oper_log` VALUES (206, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '49.87.176.188', 'XX XX', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"boyue.zip\\\",\\\"url\\\":\\\"http://localhost:9799/profile/files/master/2025/05/29/20250529092056_cc9b.zip\\\"},{\\\"name\\\":\\\"淮安市“七五”普法调查问卷.doc\\\",\\\"url\\\":\\\"http://hj.haxy.com.cn/profile/files/master/2025/05/29/20250529070442_e2a0.doc\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 04:12:58\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 12:12:58', 41); +INSERT INTO `sys_oper_log` VALUES (207, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '49.87.176.188', 'XX XX', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 14:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"01应某某不服市城管局行政处罚行政复议决定书.docx\\\",\\\"url\\\":\\\"http://hj.haxy.com.cn/profile/files/master/2025/05/31/20250531041536_0786.docx\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 04:15:40\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 12:15:40', 74); +INSERT INTO `sys_oper_log` VALUES (208, '司法局法律规定、典型案例、单下载-删除附件', 3, 'com.boyue.hasfj.controller.HtmlPagesController.deleteAttachment()', 'POST', 1, 'admin', '管理', '/hasfj/hasfjpages/deleteAttachment', '127.0.0.1', '内网IP', '{\"attachmentUrl\":\"http://hj.haxy.com.cn/profile/files/master/2025/05/31/20250531041536_0786.docx\"}', '{\"msg\":\"删除附件失败\",\"code\":500}', 0, NULL, '2025-05-31 14:50:57', 10); +INSERT INTO `sys_oper_log` VALUES (209, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"01应某某不服市城管局行政处罚行政复议决定书.docx\\\",\\\"url\\\":\\\"http://hj.haxy.com.cn/profile/files/master/2025/05/31/20250531041536_0786.docx\\\"},{\\\"name\\\":\\\"2025-05-28会议室预约记录 (2).xls\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531145236_2193.xls\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 14:52:38\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 14:52:39', 69); +INSERT INTO `sys_oper_log` VALUES (210, '司法局法律规定、典型案例、单下载-删除附件', 3, 'com.boyue.hasfj.controller.HtmlPagesController.deleteAttachment()', 'POST', 1, 'admin', '管理', '/hasfj/hasfjpages/deleteAttachment', '180.125.41.129', 'XX XX', '{\"attachmentUrl\":\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531145236_2193.xls\"}', '{\"msg\":\"删除附件失败\",\"code\":500}', 0, NULL, '2025-05-31 15:04:35', 19); +INSERT INTO `sys_oper_log` VALUES (211, '司法局法律规定、典型案例、单下载-删除附件', 3, 'com.boyue.hasfj.controller.HtmlPagesController.deleteAttachment()', 'POST', 1, 'admin', '管理', '/hasfj/hasfjpages/deleteAttachment', '180.125.41.129', 'XX XX', '{\"attachmentUrl\":\"http://hj.haxy.com.cn/profile/files/master/2025/05/31/20250531041536_0786.docx\"}', '{\"msg\":\"删除附件失败\",\"code\":500}', 0, NULL, '2025-05-31 15:04:37', 0); +INSERT INTO `sys_oper_log` VALUES (212, '司法局法律规定、典型案例、单下载-删除附件', 3, 'com.boyue.hasfj.controller.HtmlPagesController.deleteAttachment()', 'POST', 1, 'admin', '管理', '/hasfj/hasfjpages/deleteAttachment', '218.2.75.254', 'XX XX', '{\"attachmentUrl\":\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531145236_2193.xls\"}', '{\"msg\":\"删除附件失败\",\"code\":500}', 0, NULL, '2025-05-31 15:05:18', 0); +INSERT INTO `sys_oper_log` VALUES (213, '司法局法律规定、典型案例、单下载-删除附件', 3, 'com.boyue.hasfj.controller.HtmlPagesController.deleteAttachment()', 'POST', 1, 'admin', '管理', '/hasfj/hasfjpages/deleteAttachment', '218.2.75.254', 'XX XX', '{\"attachmentUrl\":\"http://hj.haxy.com.cn/profile/files/master/2025/05/31/20250531041536_0786.docx\"}', '{\"msg\":\"删除附件失败\",\"code\":500}', 0, NULL, '2025-05-31 15:05:20', 1); +INSERT INTO `sys_oper_log` VALUES (214, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 15:08:12\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 15:08:14', 569); +INSERT INTO `sys_oper_log` VALUES (215, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"2025创新大赛提交.zip\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531150820_1da0.zip\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 15:08:22\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 15:08:22', 38); +INSERT INTO `sys_oper_log` VALUES (216, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"boyue.zip\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531151028_e27c.zip\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 15:10:30\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 15:10:31', 73); +INSERT INTO `sys_oper_log` VALUES (217, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"boyue.zip\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531151028_e27c.zip\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 15:13:20\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 15:13:21', 301); +INSERT INTO `sys_oper_log` VALUES (218, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"2025-05-28至2025-05-28会议室预约记录 (1).xls\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531151741_e50b.xls\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 15:17:43\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 15:17:43', 109); +INSERT INTO `sys_oper_log` VALUES (219, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"boyue.zip\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531151028_e27c.zip\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 15:18:59\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 15:19:00', 71); +INSERT INTO `sys_oper_log` VALUES (220, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"2025-05-28至2025-05-28会议室预约记录.xls\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531152447_76e9.xls\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 15:24:49\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 15:24:49', 104); +INSERT INTO `sys_oper_log` VALUES (221, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"2025-05-28至2025-05-28会议室预约记录.xls\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531152447_76e9.xls\\\"},{\\\"name\\\":\\\"2025创新大赛提交.zip\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531150820_1da0.zip\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 15:25:50\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 15:25:51', 81); +INSERT INTO `sys_oper_log` VALUES (222, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 15:32:04\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 15:32:05', 81); +INSERT INTO `sys_oper_log` VALUES (223, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"2025创新大赛提交.zip\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531150820_1da0.zip\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 15:32:16\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 15:32:18', 641); +INSERT INTO `sys_oper_log` VALUES (224, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 19:33:31\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 19:33:32', 151); +INSERT INTO `sys_oper_log` VALUES (225, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"boyue.zip\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531151028_e27c.zip\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 19:33:52\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 19:33:53', 104); +INSERT INTO `sys_oper_log` VALUES (226, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"2025创新大赛提交.zip\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531150820_1da0.zip\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 19:46:19\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 19:46:20', 112); +INSERT INTO `sys_oper_log` VALUES (227, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"2025创新大赛提交.zip\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531150820_1da0.zip\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 20:04:39\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 20:04:40', 83); +INSERT INTO `sys_oper_log` VALUES (228, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"2025创新大赛提交.zip\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531150820_1da0.zip\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 20:14:42\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 20:14:43', 84); +INSERT INTO `sys_oper_log` VALUES (229, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"2025-05-23至2025-05-23会议室预约记录.xls\\\",\\\"url\\\":\\\"/profile/2025/05/31\\\\\\\\20250531203546_d135.xls\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-05-31 20:35:48\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-05-31 20:35:49', 100); +INSERT INTO `sys_oper_log` VALUES (230, '司法局法律规定、典型案例、单下载', 2, 'com.boyue.hasfj.controller.HtmlPagesController.edit()', 'PUT', 1, 'admin', '管理', '/hasfj/hasfjpages', '127.0.0.1', '内网IP', '{\"author\":\"表单管理员\",\"content\":\"

      劳动仲裁申请表

      劳动者在与用人单位发生劳动争议时,可填写此表申请劳动仲裁。

      \",\"createTime\":\"2025-05-28 22:07:28\",\"description\":\"劳动争议仲裁申请表格及填写说明\",\"formatId\":\"000001\",\"id\":3,\"keywords\":\"劳动仲裁,申请表,劳动争议\",\"multiAttachments\":\"[{\\\"name\\\":\\\"boyue.zip\\\",\\\"url\\\":\\\"http://127.0.0.1:9799/profile/files/master/2025/05/31/20250531151028_e27c.zip\\\"}]\",\"pageType\":\"form\",\"pageUrl\":\"/table.html?Id=000001\",\"params\":{},\"sortOrder\":3,\"status\":1,\"title\":\"如何确定企业形式\",\"updateTime\":\"2025-06-01 11:03:48\",\"viewCount\":11}', '{\"msg\":\"操作成功\",\"code\":200}', 0, NULL, '2025-06-01 11:03:49', 346); + +-- ---------------------------- +-- Table structure for sys_post +-- ---------------------------- +DROP TABLE IF EXISTS `sys_post`; +CREATE TABLE `sys_post` ( + `post_id` bigint NOT NULL AUTO_INCREMENT COMMENT '岗位ID', + `post_code` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '岗位编码', + `post_name` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '岗位名称', + `post_sort` int NOT NULL COMMENT '显示顺序', + `status` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '状态(0正常 1停用)', + `create_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`post_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '岗位信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_post +-- ---------------------------- +INSERT INTO `sys_post` VALUES (1, 'ceo', '董事长', 1, '0', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_post` VALUES (2, 'se', '项目经理', 2, '0', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_post` VALUES (3, 'hr', '人力资源', 3, '0', 'admin', '2025-05-26 17:20:06', '', NULL, ''); +INSERT INTO `sys_post` VALUES (4, 'user', '普通员工', 4, '0', 'admin', '2025-05-26 17:20:06', '', NULL, ''); + +-- ---------------------------- +-- Table structure for sys_role +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role`; +CREATE TABLE `sys_role` ( + `role_id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID', + `role_name` varchar(30) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '角色名称', + `role_key` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '角色权限字符串', + `role_sort` int NOT NULL COMMENT '显示顺序', + `data_scope` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '1' COMMENT '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)', + `menu_check_strictly` tinyint(1) NULL DEFAULT 1 COMMENT '菜单树选择项是否关联显示', + `dept_check_strictly` tinyint(1) NULL DEFAULT 1 COMMENT '部门树选择项是否关联显示', + `status` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '角色状态(0正常 1停用)', + `del_flag` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `create_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`role_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 101 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '角色信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_role +-- ---------------------------- +INSERT INTO `sys_role` VALUES (1, '超级管理员', 'admin', 1, '1', 1, 1, '0', '0', 'admin', '2025-05-26 17:20:06', '', NULL, '超级管理员'); +INSERT INTO `sys_role` VALUES (2, '普通角色', 'common', 2, '2', 1, 1, '0', '0', 'admin', '2025-05-26 17:20:06', '', NULL, '普通角色'); +INSERT INTO `sys_role` VALUES (100, '司法局', 'sfj', 3, '1', 1, 1, '0', '0', 'admin', '2025-05-29 08:41:41', '', NULL, NULL); + +-- ---------------------------- +-- Table structure for sys_role_dept +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role_dept`; +CREATE TABLE `sys_role_dept` ( + `role_id` bigint NOT NULL COMMENT '角色ID', + `dept_id` bigint NOT NULL COMMENT '部门ID', + PRIMARY KEY (`role_id`, `dept_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '角色和部门关联表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_role_dept +-- ---------------------------- +INSERT INTO `sys_role_dept` VALUES (2, 100); +INSERT INTO `sys_role_dept` VALUES (2, 101); +INSERT INTO `sys_role_dept` VALUES (2, 105); + +-- ---------------------------- +-- Table structure for sys_role_menu +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role_menu`; +CREATE TABLE `sys_role_menu` ( + `role_id` bigint NOT NULL COMMENT '角色ID', + `menu_id` bigint NOT NULL COMMENT '菜单ID', + PRIMARY KEY (`role_id`, `menu_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '角色和菜单关联表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_role_menu +-- ---------------------------- +INSERT INTO `sys_role_menu` VALUES (2, 1); +INSERT INTO `sys_role_menu` VALUES (2, 2); +INSERT INTO `sys_role_menu` VALUES (2, 3); +INSERT INTO `sys_role_menu` VALUES (2, 4); +INSERT INTO `sys_role_menu` VALUES (2, 100); +INSERT INTO `sys_role_menu` VALUES (2, 101); +INSERT INTO `sys_role_menu` VALUES (2, 102); +INSERT INTO `sys_role_menu` VALUES (2, 103); +INSERT INTO `sys_role_menu` VALUES (2, 104); +INSERT INTO `sys_role_menu` VALUES (2, 105); +INSERT INTO `sys_role_menu` VALUES (2, 106); +INSERT INTO `sys_role_menu` VALUES (2, 107); +INSERT INTO `sys_role_menu` VALUES (2, 108); +INSERT INTO `sys_role_menu` VALUES (2, 109); +INSERT INTO `sys_role_menu` VALUES (2, 110); +INSERT INTO `sys_role_menu` VALUES (2, 111); +INSERT INTO `sys_role_menu` VALUES (2, 112); +INSERT INTO `sys_role_menu` VALUES (2, 113); +INSERT INTO `sys_role_menu` VALUES (2, 114); +INSERT INTO `sys_role_menu` VALUES (2, 115); +INSERT INTO `sys_role_menu` VALUES (2, 116); +INSERT INTO `sys_role_menu` VALUES (2, 117); +INSERT INTO `sys_role_menu` VALUES (2, 500); +INSERT INTO `sys_role_menu` VALUES (2, 501); +INSERT INTO `sys_role_menu` VALUES (2, 1000); +INSERT INTO `sys_role_menu` VALUES (2, 1001); +INSERT INTO `sys_role_menu` VALUES (2, 1002); +INSERT INTO `sys_role_menu` VALUES (2, 1003); +INSERT INTO `sys_role_menu` VALUES (2, 1004); +INSERT INTO `sys_role_menu` VALUES (2, 1005); +INSERT INTO `sys_role_menu` VALUES (2, 1006); +INSERT INTO `sys_role_menu` VALUES (2, 1007); +INSERT INTO `sys_role_menu` VALUES (2, 1008); +INSERT INTO `sys_role_menu` VALUES (2, 1009); +INSERT INTO `sys_role_menu` VALUES (2, 1010); +INSERT INTO `sys_role_menu` VALUES (2, 1011); +INSERT INTO `sys_role_menu` VALUES (2, 1012); +INSERT INTO `sys_role_menu` VALUES (2, 1013); +INSERT INTO `sys_role_menu` VALUES (2, 1014); +INSERT INTO `sys_role_menu` VALUES (2, 1015); +INSERT INTO `sys_role_menu` VALUES (2, 1016); +INSERT INTO `sys_role_menu` VALUES (2, 1017); +INSERT INTO `sys_role_menu` VALUES (2, 1018); +INSERT INTO `sys_role_menu` VALUES (2, 1019); +INSERT INTO `sys_role_menu` VALUES (2, 1020); +INSERT INTO `sys_role_menu` VALUES (2, 1021); +INSERT INTO `sys_role_menu` VALUES (2, 1022); +INSERT INTO `sys_role_menu` VALUES (2, 1023); +INSERT INTO `sys_role_menu` VALUES (2, 1024); +INSERT INTO `sys_role_menu` VALUES (2, 1025); +INSERT INTO `sys_role_menu` VALUES (2, 1026); +INSERT INTO `sys_role_menu` VALUES (2, 1027); +INSERT INTO `sys_role_menu` VALUES (2, 1028); +INSERT INTO `sys_role_menu` VALUES (2, 1029); +INSERT INTO `sys_role_menu` VALUES (2, 1030); +INSERT INTO `sys_role_menu` VALUES (2, 1031); +INSERT INTO `sys_role_menu` VALUES (2, 1032); +INSERT INTO `sys_role_menu` VALUES (2, 1033); +INSERT INTO `sys_role_menu` VALUES (2, 1034); +INSERT INTO `sys_role_menu` VALUES (2, 1035); +INSERT INTO `sys_role_menu` VALUES (2, 1036); +INSERT INTO `sys_role_menu` VALUES (2, 1037); +INSERT INTO `sys_role_menu` VALUES (2, 1038); +INSERT INTO `sys_role_menu` VALUES (2, 1039); +INSERT INTO `sys_role_menu` VALUES (2, 1040); +INSERT INTO `sys_role_menu` VALUES (2, 1041); +INSERT INTO `sys_role_menu` VALUES (2, 1042); +INSERT INTO `sys_role_menu` VALUES (2, 1043); +INSERT INTO `sys_role_menu` VALUES (2, 1044); +INSERT INTO `sys_role_menu` VALUES (2, 1045); +INSERT INTO `sys_role_menu` VALUES (2, 1046); +INSERT INTO `sys_role_menu` VALUES (2, 1047); +INSERT INTO `sys_role_menu` VALUES (2, 1048); +INSERT INTO `sys_role_menu` VALUES (2, 1049); +INSERT INTO `sys_role_menu` VALUES (2, 1050); +INSERT INTO `sys_role_menu` VALUES (2, 1051); +INSERT INTO `sys_role_menu` VALUES (2, 1052); +INSERT INTO `sys_role_menu` VALUES (2, 1053); +INSERT INTO `sys_role_menu` VALUES (2, 1054); +INSERT INTO `sys_role_menu` VALUES (2, 1055); +INSERT INTO `sys_role_menu` VALUES (2, 1056); +INSERT INTO `sys_role_menu` VALUES (2, 1057); +INSERT INTO `sys_role_menu` VALUES (2, 1058); +INSERT INTO `sys_role_menu` VALUES (2, 1059); +INSERT INTO `sys_role_menu` VALUES (2, 1060); +INSERT INTO `sys_role_menu` VALUES (100, 1); +INSERT INTO `sys_role_menu` VALUES (100, 100); +INSERT INTO `sys_role_menu` VALUES (100, 1000); +INSERT INTO `sys_role_menu` VALUES (100, 1001); +INSERT INTO `sys_role_menu` VALUES (100, 1002); +INSERT INTO `sys_role_menu` VALUES (100, 1003); +INSERT INTO `sys_role_menu` VALUES (100, 1004); +INSERT INTO `sys_role_menu` VALUES (100, 1005); +INSERT INTO `sys_role_menu` VALUES (100, 1006); +INSERT INTO `sys_role_menu` VALUES (100, 2097); +INSERT INTO `sys_role_menu` VALUES (100, 2099); +INSERT INTO `sys_role_menu` VALUES (100, 2100); +INSERT INTO `sys_role_menu` VALUES (100, 2101); +INSERT INTO `sys_role_menu` VALUES (100, 2102); +INSERT INTO `sys_role_menu` VALUES (100, 2103); +INSERT INTO `sys_role_menu` VALUES (100, 2104); + +-- ---------------------------- +-- Table structure for sys_user +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user`; +CREATE TABLE `sys_user` ( + `user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID', + `dept_id` bigint NULL DEFAULT NULL COMMENT '部门ID', + `user_name` varchar(30) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '用户账号', + `nick_name` varchar(30) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '用户昵称', + `user_type` varchar(2) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '00' COMMENT '用户类型(00系统用户)', + `email` varchar(50) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '用户邮箱', + `phonenumber` varchar(11) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '手机号码', + `sex` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)', + `avatar` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '头像地址', + `password` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '密码', + `status` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '帐号状态(0正常 1停用)', + `del_flag` char(1) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)', + `login_ip` varchar(128) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '最后登录IP', + `login_date` datetime NULL DEFAULT NULL COMMENT '最后登录时间', + `create_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_by` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + `remark` varchar(500) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`user_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 101 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '用户信息表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_user +-- ---------------------------- +INSERT INTO `sys_user` VALUES (1, 103, 'admin', '若依', '00', 'ry@163.com', '15888888888', '1', 'avatar/admin/1/20250526183841-avatar.png', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '218.2.75.254', '2025-06-02 19:33:32', 'admin', '2025-05-26 17:20:06', '', '2025-06-02 19:33:32', '管理员'); +INSERT INTO `sys_user` VALUES (2, 105, 'ry', '若依', '00', 'ry@qq.com', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '2', '127.0.0.1', '2025-05-26 17:20:06', 'admin', '2025-05-26 17:20:06', '', NULL, '测试员'); +INSERT INTO `sys_user` VALUES (100, 200, 'hasfj', '淮安市司法局', '00', '', '', '0', '', '$2a$10$.ZiW8u/mWmHPannD4ewWOenMlUR/BD6lYUrVJopOWfB69..5BJEx2', '0', '0', '114.238.34.181', '2025-05-29 14:57:16', 'admin', '2025-05-29 08:45:27', 'admin', '2025-05-29 14:57:15', NULL); + +-- ---------------------------- +-- Table structure for sys_user_post +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user_post`; +CREATE TABLE `sys_user_post` ( + `user_id` bigint NOT NULL COMMENT '用户ID', + `post_id` bigint NOT NULL COMMENT '岗位ID', + PRIMARY KEY (`user_id`, `post_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '用户与岗位关联表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_user_post +-- ---------------------------- +INSERT INTO `sys_user_post` VALUES (1, 1); + +-- ---------------------------- +-- Table structure for sys_user_role +-- ---------------------------- +DROP TABLE IF EXISTS `sys_user_role`; +CREATE TABLE `sys_user_role` ( + `user_id` bigint NOT NULL COMMENT '用户ID', + `role_id` bigint NOT NULL COMMENT '角色ID', + PRIMARY KEY (`user_id`, `role_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '用户和角色关联表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_user_role +-- ---------------------------- +INSERT INTO `sys_user_role` VALUES (1, 1); +INSERT INTO `sys_user_role` VALUES (100, 100); + +SET FOREIGN_KEY_CHECKS = 1;

      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 +HtCx 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 Rli)_iWGz7fMupr%QDEgRC> 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%0DfC-!Au^tv1-R|XemvERC8O1`5!u;Z5%iD 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

  • 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