Repository: pig-mesh/pig Branch: master Commit: 6721dd17df59 Files: 490 Total size: 1.2 MB Directory structure: gitextract_fxd_qqx7/ ├── .editorconfig ├── .gitee/ │ └── ISSUE_TEMPLATE/ │ ├── config.yml │ └── issue.yml ├── .github/ │ ├── renovate.json │ └── workflows/ │ ├── github-release.yml │ ├── image.yml │ ├── maven.yml │ └── mirror.yml ├── .gitignore ├── LICENSE ├── README.md ├── db/ │ ├── Dockerfile │ ├── pig.sql │ └── pig_config.sql ├── docker-compose.yml ├── pig-auth/ │ ├── Dockerfile │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── pig4cloud/ │ │ └── pig/ │ │ └── auth/ │ │ ├── PigAuthApplication.java │ │ ├── config/ │ │ │ └── AuthorizationServerConfiguration.java │ │ ├── endpoint/ │ │ │ ├── ImageCodeEndpoint.java │ │ │ └── PigTokenEndpoint.java │ │ └── support/ │ │ ├── CustomeOAuth2AccessTokenGenerator.java │ │ ├── base/ │ │ │ ├── OAuth2ResourceOwnerBaseAuthenticationConverter.java │ │ │ ├── OAuth2ResourceOwnerBaseAuthenticationProvider.java │ │ │ ├── OAuth2ResourceOwnerBaseAuthenticationToken.java │ │ │ └── package-info.java │ │ ├── core/ │ │ │ ├── CustomeOAuth2TokenCustomizer.java │ │ │ ├── FormIdentityLoginConfigurer.java │ │ │ └── PigDaoAuthenticationProvider.java │ │ ├── filter/ │ │ │ ├── AuthSecurityConfigProperties.java │ │ │ ├── PasswordDecoderFilter.java │ │ │ └── ValidateCodeFilter.java │ │ ├── handler/ │ │ │ ├── FormAuthenticationFailureHandler.java │ │ │ ├── PigAuthenticationFailureEventHandler.java │ │ │ ├── PigAuthenticationSuccessEventHandler.java │ │ │ ├── PigLogoutSuccessEventHandler.java │ │ │ └── SsoLogoutSuccessHandler.java │ │ ├── password/ │ │ │ ├── OAuth2ResourceOwnerPasswordAuthenticationConverter.java │ │ │ ├── OAuth2ResourceOwnerPasswordAuthenticationProvider.java │ │ │ ├── OAuth2ResourceOwnerPasswordAuthenticationToken.java │ │ │ └── package-info.java │ │ └── sms/ │ │ ├── OAuth2ResourceOwnerSmsAuthenticationConverter.java │ │ ├── OAuth2ResourceOwnerSmsAuthenticationProvider.java │ │ ├── OAuth2ResourceOwnerSmsAuthenticationToken.java │ │ └── package-info.java │ └── resources/ │ ├── application.yml │ ├── logback-spring.xml │ └── templates/ │ └── ftl/ │ ├── confirm.ftl │ ├── layout/ │ │ └── base.ftl │ └── login.ftl ├── pig-boot/ │ ├── Dockerfile │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── pig4cloud/ │ │ └── pig/ │ │ └── PigBootApplication.java │ └── resources/ │ ├── application-dev.yml │ ├── application.yml │ └── logback-spring.xml ├── pig-common/ │ ├── pig-common-bom/ │ │ └── pom.xml │ ├── pig-common-core/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── pig4cloud/ │ │ │ └── pig/ │ │ │ └── common/ │ │ │ └── core/ │ │ │ ├── config/ │ │ │ │ ├── JacksonConfiguration.java │ │ │ │ ├── RedisTemplateConfiguration.java │ │ │ │ ├── RestTemplateConfiguration.java │ │ │ │ └── WebMvcConfiguration.java │ │ │ ├── constant/ │ │ │ │ ├── CacheConstants.java │ │ │ │ ├── CommonConstants.java │ │ │ │ ├── SecurityConstants.java │ │ │ │ ├── ServiceNameConstants.java │ │ │ │ └── enums/ │ │ │ │ ├── DictTypeEnum.java │ │ │ │ ├── LoginTypeEnum.java │ │ │ │ └── MenuTypeEnum.java │ │ │ ├── exception/ │ │ │ │ ├── CheckedException.java │ │ │ │ ├── ErrorCodes.java │ │ │ │ ├── PigDeniedException.java │ │ │ │ └── ValidateCodeException.java │ │ │ ├── factory/ │ │ │ │ └── YamlPropertySourceFactory.java │ │ │ ├── jackson/ │ │ │ │ └── PigJavaTimeModule.java │ │ │ ├── servlet/ │ │ │ │ └── RepeatBodyRequestWrapper.java │ │ │ └── util/ │ │ │ ├── ClassUtils.java │ │ │ ├── MsgUtils.java │ │ │ ├── R.java │ │ │ ├── RedisUtils.java │ │ │ ├── RetOps.java │ │ │ ├── SpringContextHolder.java │ │ │ └── WebUtils.java │ │ └── resources/ │ │ ├── META-INF/ │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ ├── banner.txt │ │ ├── i18n/ │ │ │ └── messages_zh_CN.properties │ │ └── logback-spring.xml │ ├── pig-common-datasource/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── pig4cloud/ │ │ └── pig/ │ │ └── common/ │ │ └── datasource/ │ │ ├── DynamicDataSourceAutoConfiguration.java │ │ ├── annotation/ │ │ │ └── EnableDynamicDataSource.java │ │ ├── config/ │ │ │ ├── ClearTtlDataSourceFilter.java │ │ │ ├── DataSourceProperties.java │ │ │ ├── JdbcDynamicDataSourceProvider.java │ │ │ ├── LastParamDsProcessor.java │ │ │ └── MasterDataSourceProvider.java │ │ ├── support/ │ │ │ └── DataSourceConstants.java │ │ └── util/ │ │ ├── DsConfTypeEnum.java │ │ └── DsJdbcUrlEnum.java │ ├── pig-common-excel/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── pig4cloud/ │ │ │ └── pig/ │ │ │ └── common/ │ │ │ └── excel/ │ │ │ ├── ExcelAutoConfiguration.java │ │ │ └── provider/ │ │ │ ├── RemoteDictApiService.java │ │ │ └── RemoteDictDataProvider.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── spring/ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ ├── pig-common-feign/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ ├── com/ │ │ │ │ └── pig4cloud/ │ │ │ │ └── pig/ │ │ │ │ └── common/ │ │ │ │ └── feign/ │ │ │ │ ├── PigFeignAutoConfiguration.java │ │ │ │ ├── annotation/ │ │ │ │ │ ├── EnablePigFeignClients.java │ │ │ │ │ └── NoToken.java │ │ │ │ ├── core/ │ │ │ │ │ ├── PigFeignInnerRequestInterceptor.java │ │ │ │ │ └── PigFeignRequestCloseInterceptor.java │ │ │ │ └── sentinel/ │ │ │ │ ├── SentinelAutoConfiguration.java │ │ │ │ ├── ext/ │ │ │ │ │ ├── PigSentinelFeign.java │ │ │ │ │ └── PigSentinelInvocationHandler.java │ │ │ │ ├── handle/ │ │ │ │ │ ├── GlobalBizExceptionHandler.java │ │ │ │ │ └── PigUrlBlockHandler.java │ │ │ │ └── parser/ │ │ │ │ └── PigHeaderRequestOriginParser.java │ │ │ └── org/ │ │ │ └── springframework/ │ │ │ └── cloud/ │ │ │ └── openfeign/ │ │ │ └── PigFeignClientsRegistrar.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── spring/ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ ├── pig-common-log/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── pig4cloud/ │ │ │ └── pig/ │ │ │ └── common/ │ │ │ └── log/ │ │ │ ├── LogAutoConfiguration.java │ │ │ ├── annotation/ │ │ │ │ └── SysLog.java │ │ │ ├── aspect/ │ │ │ │ └── SysLogAspect.java │ │ │ ├── config/ │ │ │ │ └── PigLogProperties.java │ │ │ ├── event/ │ │ │ │ ├── SysLogEvent.java │ │ │ │ ├── SysLogEventSource.java │ │ │ │ └── SysLogListener.java │ │ │ ├── init/ │ │ │ │ └── ApplicationLoggerInitializer.java │ │ │ └── util/ │ │ │ ├── LogTypeEnum.java │ │ │ └── SysLogUtils.java │ │ └── resources/ │ │ └── META-INF/ │ │ ├── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ ├── spring-configuration-metadata.json │ │ └── spring.factories │ ├── pig-common-mybatis/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── pig4cloud/ │ │ │ └── pig/ │ │ │ └── common/ │ │ │ └── mybatis/ │ │ │ ├── MybatisAutoConfiguration.java │ │ │ ├── base/ │ │ │ │ └── BaseEntity.java │ │ │ ├── config/ │ │ │ │ └── MybatisPlusMetaObjectHandler.java │ │ │ ├── handler/ │ │ │ │ ├── JsonLongArrayTypeHandler.java │ │ │ │ └── JsonStringArrayTypeHandler.java │ │ │ ├── plugins/ │ │ │ │ └── PigPaginationInnerInterceptor.java │ │ │ └── resolver/ │ │ │ └── SqlFilterArgumentResolver.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── spring/ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ ├── pig-common-oss/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── pig4cloud/ │ │ │ └── pig/ │ │ │ └── common/ │ │ │ └── file/ │ │ │ ├── FileAutoConfiguration.java │ │ │ ├── core/ │ │ │ │ ├── FileProperties.java │ │ │ │ └── FileTemplate.java │ │ │ ├── local/ │ │ │ │ ├── LocalFileAutoConfiguration.java │ │ │ │ ├── LocalFileProperties.java │ │ │ │ └── LocalFileTemplate.java │ │ │ └── oss/ │ │ │ ├── OssAutoConfiguration.java │ │ │ ├── OssProperties.java │ │ │ ├── http/ │ │ │ │ └── OssEndpoint.java │ │ │ └── service/ │ │ │ └── OssTemplate.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── spring/ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ ├── pig-common-seata/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── pig4cloud/ │ │ │ └── pig/ │ │ │ └── common/ │ │ │ └── seata/ │ │ │ └── config/ │ │ │ └── SeataAutoConfiguration.java │ │ └── resources/ │ │ ├── META-INF/ │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── seata-config.yml │ ├── pig-common-security/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── pig4cloud/ │ │ │ └── pig/ │ │ │ └── common/ │ │ │ └── security/ │ │ │ ├── annotation/ │ │ │ │ ├── EnablePigResourceServer.java │ │ │ │ ├── HasPermission.java │ │ │ │ └── Inner.java │ │ │ ├── component/ │ │ │ │ ├── PermissionService.java │ │ │ │ ├── PermitAllUrlProperties.java │ │ │ │ ├── PigBearerTokenExtractor.java │ │ │ │ ├── PigBootCorsProperties.java │ │ │ │ ├── PigClientCredentialsOAuth2AuthenticatedPrincipal.java │ │ │ │ ├── PigCustomOAuth2AccessTokenResponseHttpMessageConverter.java │ │ │ │ ├── PigCustomOpaqueTokenIntrospector.java │ │ │ │ ├── PigResourceServerAutoConfiguration.java │ │ │ │ ├── PigResourceServerConfiguration.java │ │ │ │ ├── PigSecurityInnerAspect.java │ │ │ │ ├── PigSecurityMessageSourceConfiguration.java │ │ │ │ └── ResourceAuthExceptionEntryPoint.java │ │ │ ├── feign/ │ │ │ │ ├── PigFeignClientConfiguration.java │ │ │ │ └── PigOAuthRequestInterceptor.java │ │ │ ├── service/ │ │ │ │ ├── PigAppUserDetailsServiceImpl.java │ │ │ │ ├── PigRedisOAuth2AuthorizationConsentService.java │ │ │ │ ├── PigRedisOAuth2AuthorizationService.java │ │ │ │ ├── PigRemoteRegisteredClientRepository.java │ │ │ │ ├── PigUser.java │ │ │ │ ├── PigUserDetailsService.java │ │ │ │ └── PigUserDetailsServiceImpl.java │ │ │ └── util/ │ │ │ ├── OAuth2EndpointUtils.java │ │ │ ├── OAuth2ErrorCodesExpand.java │ │ │ ├── OAuthClientException.java │ │ │ ├── ScopeException.java │ │ │ └── SecurityUtils.java │ │ └── resources/ │ │ ├── META-INF/ │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── i18n/ │ │ └── errors/ │ │ └── messages_zh_CN.properties │ ├── pig-common-swagger/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── pig4cloud/ │ │ │ └── pig/ │ │ │ └── common/ │ │ │ └── swagger/ │ │ │ ├── annotation/ │ │ │ │ └── EnablePigDoc.java │ │ │ ├── config/ │ │ │ │ ├── OpenAPIDefinition.java │ │ │ │ ├── OpenAPIDefinitionImportSelector.java │ │ │ │ └── OpenAPIMetadataConfiguration.java │ │ │ └── support/ │ │ │ └── SwaggerProperties.java │ │ └── resources/ │ │ └── openapi-config.yaml │ ├── pig-common-websocket/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── pig4cloud/ │ │ │ └── pig/ │ │ │ └── common/ │ │ │ └── websocket/ │ │ │ ├── config/ │ │ │ │ ├── LocalMessageDistributorConfiguration.java │ │ │ │ ├── MessageDistributorTypeConstants.java │ │ │ │ ├── RedisMessageDistributorConfiguration.java │ │ │ │ ├── WebSocketAutoConfiguration.java │ │ │ │ ├── WebSocketHandlerConfig.java │ │ │ │ ├── WebSocketMessageSender.java │ │ │ │ └── WebSocketProperties.java │ │ │ ├── custom/ │ │ │ │ ├── PigxSessionKeyGenerator.java │ │ │ │ └── UserAttributeHandshakeInterceptor.java │ │ │ ├── distribute/ │ │ │ │ ├── LocalMessageDistributor.java │ │ │ │ ├── MessageDO.java │ │ │ │ ├── MessageDistributor.java │ │ │ │ ├── MessageSender.java │ │ │ │ ├── RedisMessageDistributor.java │ │ │ │ └── RedisWebsocketMessageListener.java │ │ │ ├── handler/ │ │ │ │ ├── CustomPlanTextMessageHandler.java │ │ │ │ ├── CustomWebSocketHandler.java │ │ │ │ ├── JsonMessageHandler.java │ │ │ │ ├── PingJsonMessageHandler.java │ │ │ │ └── PlanTextMessageHandler.java │ │ │ ├── holder/ │ │ │ │ ├── JsonMessageHandlerHolder.java │ │ │ │ ├── MapSessionWebSocketHandlerDecorator.java │ │ │ │ ├── SessionKeyGenerator.java │ │ │ │ └── WebSocketSessionHolder.java │ │ │ ├── message/ │ │ │ │ ├── AbstractJsonWebSocketMessage.java │ │ │ │ ├── JsonWebSocketMessage.java │ │ │ │ ├── PingJsonWebSocketMessage.java │ │ │ │ ├── PongJsonWebSocketMessage.java │ │ │ │ └── WebSocketMessageTypeEnum.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── spring/ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ ├── pig-common-xss/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── pig4cloud/ │ │ │ └── pig/ │ │ │ └── common/ │ │ │ └── xss/ │ │ │ ├── PigXssAutoConfiguration.java │ │ │ ├── config/ │ │ │ │ ├── PigXssProperties.java │ │ │ │ └── package-info.java │ │ │ ├── core/ │ │ │ │ ├── DefaultXssCleaner.java │ │ │ │ ├── FormXssClean.java │ │ │ │ ├── FromXssException.java │ │ │ │ ├── JacksonXssClean.java │ │ │ │ ├── JacksonXssException.java │ │ │ │ ├── XssCleanDeserializer.java │ │ │ │ ├── XssCleanDeserializerBase.java │ │ │ │ ├── XssCleanIgnore.java │ │ │ │ ├── XssCleanInterceptor.java │ │ │ │ ├── XssCleaner.java │ │ │ │ ├── XssException.java │ │ │ │ ├── XssHolder.java │ │ │ │ └── XssType.java │ │ │ ├── package-info.java │ │ │ └── utils/ │ │ │ ├── XssUtil.java │ │ │ └── package-info.java │ │ └── resources/ │ │ └── META-INF/ │ │ ├── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── spring-configuration-metadata.json │ └── pom.xml ├── pig-gateway/ │ ├── Dockerfile │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── pig4cloud/ │ │ └── pig/ │ │ └── gateway/ │ │ ├── PigGatewayApplication.java │ │ ├── config/ │ │ │ ├── GatewayConfiguration.java │ │ │ ├── RateLimiterConfiguration.java │ │ │ └── SpringDocConfiguration.java │ │ ├── filter/ │ │ │ └── PigRequestGlobalFilter.java │ │ └── handler/ │ │ └── GlobalExceptionHandler.java │ └── resources/ │ ├── application.yml │ └── logback-spring.xml ├── pig-register/ │ ├── Dockerfile │ ├── pom.xml │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── nacos/ │ │ └── bootstrap/ │ │ └── PigNacosApplication.java │ └── resources/ │ ├── application.properties │ └── logback-spring.xml ├── pig-upms/ │ ├── pig-upms-api/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── pig4cloud/ │ │ │ └── pig/ │ │ │ └── admin/ │ │ │ └── api/ │ │ │ ├── dto/ │ │ │ │ ├── RegisterUserDTO.java │ │ │ │ ├── SysLogDTO.java │ │ │ │ ├── UserDTO.java │ │ │ │ └── UserInfo.java │ │ │ ├── entity/ │ │ │ │ ├── SysDept.java │ │ │ │ ├── SysDeptRelation.java │ │ │ │ ├── SysDict.java │ │ │ │ ├── SysDictItem.java │ │ │ │ ├── SysFile.java │ │ │ │ ├── SysLog.java │ │ │ │ ├── SysMenu.java │ │ │ │ ├── SysOauthClientDetails.java │ │ │ │ ├── SysPost.java │ │ │ │ ├── SysPublicParam.java │ │ │ │ ├── SysRole.java │ │ │ │ ├── SysRoleMenu.java │ │ │ │ ├── SysUser.java │ │ │ │ ├── SysUserPost.java │ │ │ │ └── SysUserRole.java │ │ │ ├── feign/ │ │ │ │ ├── RemoteClientDetailsService.java │ │ │ │ ├── RemoteDictService.java │ │ │ │ ├── RemoteLogService.java │ │ │ │ ├── RemoteParamService.java │ │ │ │ ├── RemoteTokenService.java │ │ │ │ └── RemoteUserService.java │ │ │ ├── util/ │ │ │ │ ├── DictResolver.java │ │ │ │ └── ParamResolver.java │ │ │ └── vo/ │ │ │ ├── DeptExcelVo.java │ │ │ ├── PostExcelVO.java │ │ │ ├── PreLogVO.java │ │ │ ├── RoleExcelVO.java │ │ │ ├── RoleVO.java │ │ │ ├── TokenVo.java │ │ │ ├── UserExcelVO.java │ │ │ └── UserVO.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── spring/ │ │ └── org.springframework.cloud.openfeign.FeignClient.imports │ ├── pig-upms-biz/ │ │ ├── Dockerfile │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── pig4cloud/ │ │ │ └── pig/ │ │ │ └── admin/ │ │ │ ├── PigAdminApplication.java │ │ │ ├── controller/ │ │ │ │ ├── SysClientController.java │ │ │ │ ├── SysDeptController.java │ │ │ │ ├── SysDictController.java │ │ │ │ ├── SysFileController.java │ │ │ │ ├── SysLogController.java │ │ │ │ ├── SysMenuController.java │ │ │ │ ├── SysMobileController.java │ │ │ │ ├── SysPostController.java │ │ │ │ ├── SysPublicParamController.java │ │ │ │ ├── SysRegisterController.java │ │ │ │ ├── SysRoleController.java │ │ │ │ ├── SysSystemInfoController.java │ │ │ │ ├── SysTokenController.java │ │ │ │ └── SysUserController.java │ │ │ ├── mapper/ │ │ │ │ ├── SysDeptMapper.java │ │ │ │ ├── SysDictItemMapper.java │ │ │ │ ├── SysDictMapper.java │ │ │ │ ├── SysFileMapper.java │ │ │ │ ├── SysLogMapper.java │ │ │ │ ├── SysMenuMapper.java │ │ │ │ ├── SysOauthClientDetailsMapper.java │ │ │ │ ├── SysPostMapper.java │ │ │ │ ├── SysPublicParamMapper.java │ │ │ │ ├── SysRoleMapper.java │ │ │ │ ├── SysRoleMenuMapper.java │ │ │ │ ├── SysUserMapper.java │ │ │ │ ├── SysUserPostMapper.java │ │ │ │ └── SysUserRoleMapper.java │ │ │ └── service/ │ │ │ ├── SysDeptService.java │ │ │ ├── SysDictItemService.java │ │ │ ├── SysDictService.java │ │ │ ├── SysFileService.java │ │ │ ├── SysLogService.java │ │ │ ├── SysMenuService.java │ │ │ ├── SysMobileService.java │ │ │ ├── SysOauthClientDetailsService.java │ │ │ ├── SysPostService.java │ │ │ ├── SysPublicParamService.java │ │ │ ├── SysRoleMenuService.java │ │ │ ├── SysRoleService.java │ │ │ ├── SysUserRoleService.java │ │ │ ├── SysUserService.java │ │ │ └── impl/ │ │ │ ├── SysDeptServiceImpl.java │ │ │ ├── SysDictItemServiceImpl.java │ │ │ ├── SysDictServiceImpl.java │ │ │ ├── SysFileServiceImpl.java │ │ │ ├── SysLogServiceImpl.java │ │ │ ├── SysMenuServiceImpl.java │ │ │ ├── SysMobileServiceImpl.java │ │ │ ├── SysOauthClientDetailsServiceImpl.java │ │ │ ├── SysPostServiceImpl.java │ │ │ ├── SysPublicParamServiceImpl.java │ │ │ ├── SysRoleMenuServiceImpl.java │ │ │ ├── SysRoleServiceImpl.java │ │ │ ├── SysUserRoleServiceImpl.java │ │ │ └── SysUserServiceImpl.java │ │ └── resources/ │ │ ├── application.yml │ │ ├── file/ │ │ │ ├── approle.xlsx │ │ │ ├── dept.xlsx │ │ │ ├── post.xlsx │ │ │ ├── role.xlsx │ │ │ └── user.xlsx │ │ ├── logback-spring.xml │ │ └── mapper/ │ │ ├── SysDeptMapper.xml │ │ ├── SysMenuMapper.xml │ │ ├── SysPostMapper.xml │ │ ├── SysRoleMapper.xml │ │ └── SysUserMapper.xml │ └── pom.xml ├── pig-visual/ │ ├── pig-codegen/ │ │ ├── Dockerfile │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── pig4cloud/ │ │ │ └── pig/ │ │ │ └── codegen/ │ │ │ ├── PigCodeGenApplication.java │ │ │ ├── config/ │ │ │ │ └── PigCodeGenDefaultProperties.java │ │ │ ├── controller/ │ │ │ │ ├── GenDsConfController.java │ │ │ │ ├── GenFieldTypeController.java │ │ │ │ ├── GenGroupController.java │ │ │ │ ├── GenTableController.java │ │ │ │ ├── GenTemplateController.java │ │ │ │ ├── GenTemplateGroupController.java │ │ │ │ └── GeneratorController.java │ │ │ ├── entity/ │ │ │ │ ├── ColumnEntity.java │ │ │ │ ├── GenConfig.java │ │ │ │ ├── GenDatasourceConf.java │ │ │ │ ├── GenFieldType.java │ │ │ │ ├── GenGroupEntity.java │ │ │ │ ├── GenTable.java │ │ │ │ ├── GenTableColumnEntity.java │ │ │ │ ├── GenTemplateEntity.java │ │ │ │ ├── GenTemplateGroupEntity.java │ │ │ │ └── TableEntity.java │ │ │ ├── mapper/ │ │ │ │ ├── GenDatasourceConfMapper.java │ │ │ │ ├── GenDynamicMapper.java │ │ │ │ ├── GenFieldTypeMapper.java │ │ │ │ ├── GenGroupMapper.java │ │ │ │ ├── GenTableColumnMapper.java │ │ │ │ ├── GenTableMapper.java │ │ │ │ ├── GenTemplateGroupMapper.java │ │ │ │ └── GenTemplateMapper.java │ │ │ ├── service/ │ │ │ │ ├── GenDatasourceConfService.java │ │ │ │ ├── GenFieldTypeService.java │ │ │ │ ├── GenGroupService.java │ │ │ │ ├── GenTableColumnService.java │ │ │ │ ├── GenTableService.java │ │ │ │ ├── GenTemplateGroupService.java │ │ │ │ ├── GenTemplateService.java │ │ │ │ ├── GeneratorService.java │ │ │ │ └── impl/ │ │ │ │ ├── GenDatasourceConfServiceImpl.java │ │ │ │ ├── GenFieldTypeServiceImpl.java │ │ │ │ ├── GenGroupServiceImpl.java │ │ │ │ ├── GenTableColumnServiceImpl.java │ │ │ │ ├── GenTableServiceImpl.java │ │ │ │ ├── GenTemplateGroupServiceImpl.java │ │ │ │ ├── GenTemplateServiceImpl.java │ │ │ │ └── GeneratorServiceImpl.java │ │ │ └── util/ │ │ │ ├── AutoFillEnum.java │ │ │ ├── BoolFillEnum.java │ │ │ ├── CommonColumnFiledEnum.java │ │ │ ├── DictTool.java │ │ │ ├── GenKit.java │ │ │ ├── GeneratorStyleEnum.java │ │ │ ├── NamingCaseTool.java │ │ │ ├── VelocityKit.java │ │ │ └── vo/ │ │ │ ├── GenCreateTableVO.java │ │ │ ├── GenTemplateFileVO.java │ │ │ ├── GroupVO.java │ │ │ ├── SqlDto.java │ │ │ └── TemplateGroupDTO.java │ │ └── resources/ │ │ ├── application.yml │ │ ├── logback-spring.xml │ │ └── mapper/ │ │ ├── GenFieldTypeMapper.xml │ │ ├── GenGroupMapper.xml │ │ ├── GenTableMapper.xml │ │ ├── GenTemplateGroupMapper.xml │ │ └── GenTemplateMapper.xml │ ├── pig-monitor/ │ │ ├── Dockerfile │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── pig4cloud/ │ │ │ └── pig/ │ │ │ └── monitor/ │ │ │ ├── PigMonitorApplication.java │ │ │ ├── config/ │ │ │ │ ├── CustomCsrfFilter.java │ │ │ │ └── SecuritySecureConfig.java │ │ │ └── converter/ │ │ │ └── NacosServiceInstanceConverter.java │ │ └── resources/ │ │ ├── application.yml │ │ └── logback-spring.xml │ ├── pig-quartz/ │ │ ├── Dockerfile │ │ ├── SECURITY.md │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── pig4cloud/ │ │ │ └── pig/ │ │ │ └── daemon/ │ │ │ └── quartz/ │ │ │ ├── PigQuartzApplication.java │ │ │ ├── config/ │ │ │ │ ├── AutowireCapableBeanJobFactory.java │ │ │ │ ├── PigInitQuartzJob.java │ │ │ │ ├── PigQuartzConfig.java │ │ │ │ ├── PigQuartzCustomizerConfig.java │ │ │ │ ├── PigQuartzFactory.java │ │ │ │ └── PigQuartzInvokeFactory.java │ │ │ ├── constants/ │ │ │ │ ├── JobTypeQuartzEnum.java │ │ │ │ └── PigQuartzEnum.java │ │ │ ├── controller/ │ │ │ │ ├── SysJobController.java │ │ │ │ └── SysJobLogController.java │ │ │ ├── entity/ │ │ │ │ ├── SysJob.java │ │ │ │ └── SysJobLog.java │ │ │ ├── event/ │ │ │ │ ├── SysJobEvent.java │ │ │ │ ├── SysJobListener.java │ │ │ │ ├── SysJobLogEvent.java │ │ │ │ └── SysJobLogListener.java │ │ │ ├── exception/ │ │ │ │ └── TaskException.java │ │ │ ├── mapper/ │ │ │ │ ├── SysJobLogMapper.java │ │ │ │ └── SysJobMapper.java │ │ │ ├── service/ │ │ │ │ ├── SysJobLogService.java │ │ │ │ ├── SysJobService.java │ │ │ │ └── impl/ │ │ │ │ ├── SysJobLogServiceImpl.java │ │ │ │ └── SysJobServiceImpl.java │ │ │ ├── task/ │ │ │ │ ├── RestTaskDemo.java │ │ │ │ └── SpringBeanTaskDemo.java │ │ │ └── util/ │ │ │ ├── ClassNameValidator.java │ │ │ ├── ITaskInvok.java │ │ │ ├── JarTaskInvok.java │ │ │ ├── JavaClassTaskInvok.java │ │ │ ├── RestTaskInvok.java │ │ │ ├── SpringBeanTaskInvok.java │ │ │ ├── TaskInvokFactory.java │ │ │ ├── TaskInvokUtil.java │ │ │ └── TaskUtil.java │ │ └── resources/ │ │ ├── application.yml │ │ ├── logback-spring.xml │ │ └── quartz-config.yml │ └── pom.xml └── pom.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*.{groovy,java,kt,xml}] indent_style = tab indent_size = 4 continuation_indent_size = 8 ================================================ FILE: .gitee/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false # 不允许用户创建空白 Issue contact_links: - name: 遇到问题先去看文档!谢谢! # 外部网站名称 url: https://wiki.pig4cloud.com/ # 跳转的外部网站目标地址 about: 文档可以解决你80%的疑惑 # 跳转外部网站的描述说明 ================================================ FILE: .gitee/ISSUE_TEMPLATE/issue.yml ================================================ name: 问题咨询 description: "请尽可能详细的描述问题,提供足够的上下文,一分钟的描述不需要期望别人花半小时帮你排查" body: - type: dropdown id: version attributes: label: PIG版本(提问先右上角 Star ♥️) options: - "不处理PIGX或其他魔改版本" - "3.9" - "3.8" - "3.7" validations: required: true - type: checkboxes validations: required: true attributes: label: 架构 options: - label: 微服务架构 - label: 单体架构 - type: textarea id: desired-solution attributes: label: 问题描述,提供详细截图和报错 description: 详细问题,提供相应截图和日志,一分钟的描述不需要期望别人花半小时帮你排查 validations: required: true ================================================ FILE: .github/renovate.json ================================================ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "baseBranches": [ "jdk17-dev" ], "extends": [ "config:recommended" ], "rangeStrategy": "bump", "packageRules": [ { "matchPackagePatterns": [ "^com.amazonaws:aws-java-sdk-s3$", "^tomcat$", "^io.springboot:knife4j-openapi3-ui$", "^com.alibaba:fastjson$", "^org.anyline:anyline.*$", "^org.apache.velocity:velocity-engine-core.*$" ], "enabled": false } ] } ================================================ FILE: .github/workflows/github-release.yml ================================================ name: publish github release on: workflow_dispatch: inputs: releaseversion: description: 'Release version' required: true default: '3.8.0' jobs: publish-github-release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Generate changelog id: changelog uses: metcalfc/changelog-generator@v4.5.0 with: myToken: ${{ secrets.GH_TOKEN }} - name: Create GitHub Release id: create_release uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} with: tag_name: ${{ github.event.inputs.releaseversion }} release_name: ${{ github.event.inputs.releaseversion }} body: | ### Things that changed in this release ${{ steps.changelog.outputs.changelog }} draft: false prerelease: ${{ contains(github.event.inputs.releaseversion, '-') }} ================================================ FILE: .github/workflows/image.yml ================================================ # This workflow will build a Java project with Maven # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven name: Docker 镜像 构建 on: push: branches: [ master ] jobs: build: runs-on: ubuntu-latest strategy: matrix: java-version: [ 17 ] steps: - uses: actions/checkout@v4 - name: Set up JDK ${{ matrix.java-version }} uses: actions/setup-java@v4 with: java-version: ${{ matrix.java-version }} distribution: 'zulu' - name: mvn clean install run: mvn clean install -Pcloud - name: Login to Docker Registry run: docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} registry.cn-hangzhou.aliyuncs.com - name: Build and push Docker images run: | docker compose build registry="registry.cn-hangzhou.aliyuncs.com/pigx/" for service in $(docker compose config --services); do if [ "$service" != "pig-redis" ]; then docker tag ${service}:latest ${registry}${service}:latest docker push ${registry}${service}:latest else echo "Skipping pig-redis service" fi done ================================================ FILE: .github/workflows/maven.yml ================================================ # This workflow will build a Java project with Maven # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven name: maven 编译检查 on: push: branches: [ master,dev ] jobs: build: runs-on: ubuntu-latest strategy: matrix: java-version: [ 17,21,25 ] steps: - uses: actions/checkout@v4 - name: Set up JDK ${{ matrix.java-version }} uses: actions/setup-java@v4 with: java-version: ${{ matrix.java-version }} distribution: 'zulu' - name: mvn spring-javaformat:validate id: validate run: mvn spring-javaformat:validate -Dmaven.compiler.release=${{ matrix.java-version }} continue-on-error: true - name: Auto format code if validation fails if: steps.validate.outcome == 'failure' run: mvn spring-javaformat:apply -Dmaven.compiler.release=${{ matrix.java-version }} - name: Create Pull Request for formatting changes if: steps.validate.outcome == 'failure' uses: peter-evans/create-pull-request@v5 with: token: ${{ secrets.GITHUB_TOKEN }} commit-message: 'Auto-format code with spring-javaformat' title: 'Auto-format: Fix code formatting issues' body: | This PR was automatically created because the spring-javaformat validation failed. The following changes have been applied: - Applied spring-javaformat:apply to fix formatting issues Please review and merge if the changes look correct. branch: auto-format-${{ github.run_number }} delete-branch: true - name: mvn clean install run: mvn clean install -Pboot -Dmaven.compiler.release=${{ matrix.java-version }} - name: mvn clean install run: mvn clean install -Dmaven.compiler.release=${{ matrix.java-version }} - name: failure if: failure() && github.repository == 'pig-mesh/pig' uses: chf007/action-wechat-work@master env: WECHAT_WORK_BOT_WEBHOOK: ${{secrets.WECHAT_WORK_BOT_WEBHOOK}} with: msgtype: markdown content: | # 💤🤷‍♀️ failure 🙅‍♂️💣 [pig-mesh/pig](https://github.com/pig-mesh/pig) > Github Action: https://github.com/pig-mesh/pig failure > (⋟﹏⋞) from github action message ================================================ FILE: .github/workflows/mirror.yml ================================================ name: 同步代码 on: push: branches: [ master,dev ] pull_request: branches: [ master,dev ] jobs: gitee: runs-on: ubuntu-latest container: image: "centos:8" steps: - uses: wearerequired/git-mirror-action@master #同步至 gitee env: SSH_PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} with: source-repo: "git@github.com:pig-mesh/pig.git" destination-repo: "git@gitee.com:log4j/pig.git" ================================================ FILE: .gitignore ================================================ ### gradle ### .gradle /build/ !gradle/wrapper/gradle-wrapper.jar .mvn/wrapper/maven-wrapper.jar ### STS ### .settings/ .apt_generated .classpath .factorypath .project .settings .springBeans bin/ ### IntelliJ IDEA ### !.idea/icon.png .idea *.iws *.iml *.ipr rebel.xml ### NetBeans ### nbproject/private/ build/ nbbuild/ nbdist/ .nb-gradle/ ### maven ### target/ *.war *.ear *.zip *.tar *.tar.gz *.versionsBackup ### vscode ### .vscode ### logs ### /logs/ *.log ### temp ignore ### *.cache *.diff *.patch *.tmp *.java~ *.properties~ *.xml~ ### system ignore ### .DS_Store Thumbs.db Servers .metadata .flattened-pom.xml ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ > **🚀 Spring Boot 4.0 版本来了** > > 分支 `boot4` 基于 Spring Boot 4.0 + Spring Cloud 2025.1 进行开发。

Build Status Coverage Status Downloads Downloads

## 系统说明 - 基于 Spring Cloud 、Spring Boot、 OAuth2 的 RBAC **企业级快速开发平台**, 同时支持微服务架构和单体架构 - 提供 Spring Authorization Server 的生产级实践方案,支持多种安全授权模式 - 提供对常见容器化方案支持 Kubernetes、Rancher2 、KubeSphere、EDAS、SAE 支持 #### 使用文档 PIG 提供了详尽的部署文档 👉 [wiki.pig4cloud.com](https://wiki.pig4cloud.com),涵盖开发环境配置、服务端启动、前端运行等关键步骤。 重要的事情说三遍: - 🔥 [ 配套文档 wiki.pig4cloud.com](https://wiki.pig4cloud.com) - 🔥 [ 配套文档 wiki.pig4cloud.com](https://wiki.pig4cloud.com) - 🔥 [ 配套文档 wiki.pig4cloud.com](https://wiki.pig4cloud.com) #### 其他产品 - 👉🏻 [PIGX 在线体验](http://home.pig4cloud.com:38081) - 👉🏻 [自研BPMN工作流引擎](http://home.pig4cloud.com:38082) - 👉🏻 [大模型 RAG 知识库](http://home.pig4cloud.com:38083) ## 微信群 [禁广告] 1735262426 ## 快速开始 #### Docker 快速体验 ```shell # 可用内存大于4G curl -o docker-compose.yaml https://try.pig4cloud.com # 等待5分钟 docker compose up ``` ### 核心依赖 | 依赖 | 版本 | |-----------------------------|--------| | Spring Boot | 3.5.11 | | Spring Cloud | 2025 | | Spring Cloud Alibaba | 2025 | | Spring Authorization Server | 1.5.2 | | Mybatis Plus | 3.5.15 | | Vue | 3.5 | | Element Plus | 2.8 | ### 模块说明 ```lua pig-ui -- https://gitee.com/log4j/pig-ui pig ├── pig-boot -- 单体模式启动器[9999] ├── pig-auth -- 授权服务提供[3000] └── pig-common -- 系统公共模块 ├── pig-common-bom -- 全局依赖管理控制 ├── pig-common-core -- 公共工具类核心包 ├── pig-common-datasource -- 动态数据源包 ├── pig-common-log -- 日志服务 ├── pig-common-oss -- 文件上传工具类 ├── pig-common-mybatis -- mybatis 扩展封装 ├── pig-common-seata -- 分布式事务 ├── pig-common-websocket -- websocket 封装 ├── pig-common-security -- 安全工具类 ├── pig-common-swagger -- 接口文档 ├── pig-common-feign -- feign 扩展封装 └── pig-common-xss -- xss 安全封装 ├── pig-register -- Nacos Server[8848] ├── pig-gateway -- Spring Cloud Gateway网关[9999] └── pig-upms -- 通用用户权限管理模块 └── pig-upms-api -- 通用用户权限管理系统公共api模块 └── pig-upms-biz -- 通用用户权限管理系统业务处理模块[4000] └── pig-visual └── pig-monitor -- 服务监控 [5001] ├── pig-codegen -- 图形化代码生成 [5002] └── pig-quartz -- 定时任务管理台 [5007] ``` ## 免费公开课
## 开源共建 ### 开源协议 pig 开源软件遵循 [Apache 2.0 协议](https://www.apache.org/licenses/LICENSE-2.0.html)。 允许商业使用,但务必保留类作者、Copyright 信息。 ![](https://foruda.gitee.com/images/1731647419204307063/91217172_441246.jpeg) ### 其他说明 1. 欢迎提交 [PR](https://dwz.cn/2KURd5Vf),注意对应提交对应 `dev` 分支 代码规范 [spring-javaformat](https://github.com/spring-io/spring-javaformat)
代码规范说明 1. 由于 spring-javaformat 强制所有代码按照指定格式排版,未按此要求提交的代码将不能通过合并(打包) 2. 如果使用 IntelliJ IDEA 开发,请安装自动格式化软件 spring-javaformat-intellij-idea-plugin 3. 其他开发工具,请参考 spring-javaformat 说明,或`提交代码前`在项目根目录运行下列命令(需要开发者电脑支持`mvn`命令)进行代码格式化 ``` mvn spring-javaformat:apply ```
2. 欢迎提交 [issue](https://gitee.com/log4j/pig/issues),请写清楚遇到问题的原因、开发环境、复显步骤。 ================================================ FILE: db/Dockerfile ================================================ FROM registry.cn-hangzhou.aliyuncs.com/dockerhub_mirror/mysql-server:8.0.32 MAINTAINER lengleng(wangiegie@gmail.com) ENV TZ=Asia/Shanghai RUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone COPY ./pig.sql /docker-entrypoint-initdb.d COPY ./pig_config.sql /docker-entrypoint-initdb.d EXPOSE 3306 ================================================ FILE: db/pig.sql ================================================ DROP DATABASE IF EXISTS `pig`; CREATE DATABASE `pig` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; USE `pig`; -- ---------------------------- -- Table structure for sys_dept -- ---------------------------- DROP TABLE IF EXISTS `sys_dept`; CREATE TABLE `sys_dept` ( `dept_id` bigint NOT NULL COMMENT '部门ID', `name` varchar(50) DEFAULT NULL COMMENT '部门名称', `sort_order` int NOT NULL DEFAULT '0' COMMENT '排序', `create_by` varchar(64) DEFAULT NULL COMMENT '创建人', `update_by` varchar(64) DEFAULT NULL COMMENT '修改人', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '修改时间', `del_flag` char(1) DEFAULT '0' COMMENT '删除标志', `parent_id` bigint DEFAULT NULL COMMENT '父级部门ID', PRIMARY KEY (`dept_id`) USING BTREE ) ENGINE=InnoDB COMMENT='部门管理'; -- ---------------------------- -- Records of sys_dept -- ---------------------------- BEGIN; INSERT INTO `sys_dept` VALUES (1, '总裁办', 1, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:07:49', '0', 0); INSERT INTO `sys_dept` VALUES (2, '技术部', 2, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 1); INSERT INTO `sys_dept` VALUES (3, '市场部', 3, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 1); INSERT INTO `sys_dept` VALUES (4, '销售部', 4, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 1); INSERT INTO `sys_dept` VALUES (5, '财务部', 5, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 1); INSERT INTO `sys_dept` VALUES (6, '人事行政部', 6, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:53:36', '1', 1); INSERT INTO `sys_dept` VALUES (7, '研发部', 7, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 2); INSERT INTO `sys_dept` VALUES (8, 'UI设计部', 11, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 7); INSERT INTO `sys_dept` VALUES (9, '产品部', 12, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 2); INSERT INTO `sys_dept` VALUES (10, '渠道部', 13, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 3); INSERT INTO `sys_dept` VALUES (11, '推广部', 14, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 3); INSERT INTO `sys_dept` VALUES (12, '客服部', 15, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 4); INSERT INTO `sys_dept` VALUES (13, '财务会计部', 16, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 5); INSERT INTO `sys_dept` VALUES (14, '审计风控部', 17, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 14:06:57', '0', 5); COMMIT; -- ---------------------------- -- Table structure for sys_dict -- ---------------------------- DROP TABLE IF EXISTS `sys_dict`; CREATE TABLE `sys_dict` ( `id` bigint NOT NULL COMMENT '编号', `dict_type` varchar(100) DEFAULT NULL COMMENT '字典类型', `description` varchar(100) DEFAULT NULL COMMENT '描述', `create_by` varchar(64) DEFAULT NULL COMMENT '创建人', `update_by` varchar(64) DEFAULT NULL COMMENT '修改人', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `remarks` varchar(255) DEFAULT NULL COMMENT '备注信息', `system_flag` char(1) DEFAULT '0' COMMENT '系统标志', `del_flag` char(1) DEFAULT '0' COMMENT '删除标志', PRIMARY KEY (`id`) USING BTREE, KEY `sys_dict_del_flag` (`del_flag`) USING BTREE ) ENGINE=InnoDB COMMENT='字典表'; -- ---------------------------- -- Records of sys_dict -- ---------------------------- BEGIN; INSERT INTO `sys_dict` VALUES (1, 'log_type', '日志类型', ' ', ' ', '2019-03-19 11:06:44', '2019-03-19 11:06:44', '异常、正常', '1', '0'); INSERT INTO `sys_dict` VALUES (2, 'social_type', '社交登录', ' ', ' ', '2019-03-19 11:09:44', '2019-03-19 11:09:44', '微信、QQ', '1', '0'); INSERT INTO `sys_dict` VALUES (3, 'job_type', '定时任务类型', ' ', ' ', '2019-03-19 11:22:21', '2019-03-19 11:22:21', 'quartz', '1', '0'); INSERT INTO `sys_dict` VALUES (4, 'job_status', '定时任务状态', ' ', ' ', '2019-03-19 11:24:57', '2019-03-19 11:24:57', '发布状态、运行状态', '1', '0'); INSERT INTO `sys_dict` VALUES (5, 'job_execute_status', '定时任务执行状态', ' ', ' ', '2019-03-19 11:26:15', '2019-03-19 11:26:15', '正常、异常', '1', '0'); INSERT INTO `sys_dict` VALUES (6, 'misfire_policy', '定时任务错失执行策略', ' ', ' ', '2019-03-19 11:27:19', '2019-03-19 11:27:19', '周期', '1', '0'); INSERT INTO `sys_dict` VALUES (7, 'gender', '性别', ' ', ' ', '2019-03-27 13:44:06', '2019-03-27 13:44:06', '微信用户性别', '1', '0'); INSERT INTO `sys_dict` VALUES (8, 'subscribe', '订阅状态', ' ', ' ', '2019-03-27 13:48:33', '2019-03-27 13:48:33', '公众号订阅状态', '1', '0'); INSERT INTO `sys_dict` VALUES (9, 'response_type', '回复', ' ', ' ', '2019-03-28 21:29:21', '2019-03-28 21:29:21', '微信消息是否已回复', '1', '0'); INSERT INTO `sys_dict` VALUES (10, 'param_type', '参数配置', ' ', ' ', '2019-04-29 18:20:47', '2019-04-29 18:20:47', '检索、原文、报表、安全、文档、消息、其他', '1', '0'); INSERT INTO `sys_dict` VALUES (11, 'status_type', '租户状态', ' ', ' ', '2019-05-15 16:31:08', '2019-05-15 16:31:08', '租户状态', '1', '0'); INSERT INTO `sys_dict` VALUES (12, 'dict_type', '字典类型', ' ', ' ', '2019-05-16 14:16:20', '2019-05-16 14:20:16', '系统类不能修改', '1', '0'); INSERT INTO `sys_dict` VALUES (13, 'channel_type', '支付类型', ' ', ' ', '2019-05-16 14:16:20', '2019-05-16 14:20:16', '系统类不能修改', '1', '0'); INSERT INTO `sys_dict` VALUES (14, 'grant_types', '授权类型', ' ', ' ', '2019-08-13 07:34:10', '2019-08-13 07:34:10', NULL, '1', '0'); INSERT INTO `sys_dict` VALUES (15, 'style_type', '前端风格', ' ', ' ', '2020-02-07 03:49:28', '2020-02-07 03:50:40', '0-Avue 1-element', '1', '0'); INSERT INTO `sys_dict` VALUES (16, 'captcha_flag_types', '验证码开关', ' ', ' ', '2020-11-18 06:53:25', '2020-11-18 06:53:25', '是否校验验证码', '1', '0'); INSERT INTO `sys_dict` VALUES (17, 'enc_flag_types', '前端密码加密', ' ', ' ', '2020-11-18 06:54:44', '2020-11-18 06:54:44', '前端密码是否加密传输', '1', '0'); INSERT INTO `sys_dict` VALUES (18, 'lock_flag', '用户状态', 'admin', ' ', '2023-02-01 16:55:31', NULL, NULL, '1', '0'); INSERT INTO `sys_dict` VALUES (19, 'ds_config_type', '数据连接类型', 'admin', ' ', '2023-02-06 18:36:59', NULL, NULL, '1', '0'); INSERT INTO `sys_dict` VALUES (20, 'common_status', '通用状态', 'admin', ' ', '2023-02-09 11:02:08', NULL, NULL, '1', '0'); INSERT INTO `sys_dict` VALUES (21, 'app_social_type', 'app社交登录', 'admin', ' ', '2023-02-10 11:11:06', NULL, 'app社交登录', '1', '0'); INSERT INTO `sys_dict` VALUES (22, 'yes_no_type', '是否', 'admin', ' ', '2023-02-20 23:25:04', NULL, NULL, '1', '0'); INSERT INTO `sys_dict` VALUES (23, 'repType', '微信消息类型', 'admin', ' ', '2023-02-24 15:08:25', NULL, NULL, '0', '0'); INSERT INTO `sys_dict` VALUES (24, 'leave_status', '请假状态', 'admin', ' ', '2023-03-02 22:50:15', NULL, NULL, '0', '0'); INSERT INTO `sys_dict` VALUES (25, 'schedule_type', '日程类型', 'admin', ' ', '2023-03-06 14:49:18', NULL, NULL, '0', '0'); INSERT INTO `sys_dict` VALUES (26, 'schedule_status', '日程状态', 'admin', ' ', '2023-03-06 14:52:57', NULL, NULL, '0', '0'); INSERT INTO `sys_dict` VALUES (27, 'ds_type', '代码生成器支持的数据库类型', 'admin', ' ', '2023-03-12 09:57:59', NULL, NULL, '1', '0'); COMMIT; -- ---------------------------- -- Table structure for sys_dict_item -- ---------------------------- DROP TABLE IF EXISTS `sys_dict_item`; CREATE TABLE `sys_dict_item` ( `id` bigint NOT NULL COMMENT '编号', `dict_id` bigint NOT NULL COMMENT '字典ID', `item_value` varchar(100) DEFAULT NULL COMMENT '字典项值', `label` varchar(100) DEFAULT NULL COMMENT '字典项名称', `dict_type` varchar(100) DEFAULT NULL COMMENT '字典类型', `description` varchar(100) DEFAULT NULL COMMENT '字典项描述', `sort_order` int NOT NULL DEFAULT '0' COMMENT '排序(升序)', `create_by` varchar(64) DEFAULT NULL COMMENT '创建人', `update_by` varchar(64) DEFAULT NULL COMMENT '修改人', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `remarks` varchar(255) DEFAULT NULL COMMENT '备注信息', `del_flag` char(1) DEFAULT '0' COMMENT '删除标志', PRIMARY KEY (`id`) USING BTREE, KEY `sys_dict_value` (`item_value`) USING BTREE, KEY `sys_dict_label` (`label`) USING BTREE, KEY `sys_dict_item_del_flag` (`del_flag`) USING BTREE ) ENGINE=InnoDB COMMENT='字典项'; -- ---------------------------- -- Records of sys_dict_item -- ---------------------------- BEGIN; INSERT INTO `sys_dict_item` VALUES (1, 1, '9', '异常', 'log_type', '日志异常', 1, ' ', ' ', '2019-03-19 11:08:59', '2019-03-25 12:49:13', '', '0'); INSERT INTO `sys_dict_item` VALUES (2, 1, '0', '正常', 'log_type', '日志正常', 0, ' ', ' ', '2019-03-19 11:09:17', '2019-03-25 12:49:18', '', '0'); INSERT INTO `sys_dict_item` VALUES (3, 2, 'WX', '微信', 'social_type', '微信登录', 0, ' ', ' ', '2019-03-19 11:10:02', '2019-03-25 12:49:36', '', '0'); INSERT INTO `sys_dict_item` VALUES (4, 2, 'QQ', 'QQ', 'social_type', 'QQ登录', 1, ' ', ' ', '2019-03-19 11:10:14', '2019-03-25 12:49:36', '', '0'); INSERT INTO `sys_dict_item` VALUES (5, 3, '1', 'java类', 'job_type', 'java类', 1, ' ', ' ', '2019-03-19 11:22:37', '2019-03-25 12:49:36', '', '0'); INSERT INTO `sys_dict_item` VALUES (6, 3, '2', 'spring bean', 'job_type', 'spring bean容器实例', 2, ' ', ' ', '2019-03-19 11:23:05', '2019-03-25 12:49:36', '', '0'); INSERT INTO `sys_dict_item` VALUES (7, 3, '9', '其他', 'job_type', '其他类型', 9, ' ', ' ', '2019-03-19 11:23:31', '2019-03-25 12:49:36', '', '0'); INSERT INTO `sys_dict_item` VALUES (8, 3, '3', 'Rest 调用', 'job_type', 'Rest 调用', 3, ' ', ' ', '2019-03-19 11:23:57', '2019-03-25 12:49:36', '', '0'); INSERT INTO `sys_dict_item` VALUES (9, 3, '4', 'jar', 'job_type', 'jar类型', 4, ' ', ' ', '2019-03-19 11:24:20', '2019-03-25 12:49:36', '', '0'); INSERT INTO `sys_dict_item` VALUES (10, 4, '1', '未发布', 'job_status', '未发布', 1, ' ', ' ', '2019-03-19 11:25:18', '2019-03-25 12:49:36', '', '0'); INSERT INTO `sys_dict_item` VALUES (11, 4, '2', '运行中', 'job_status', '运行中', 2, ' ', ' ', '2019-03-19 11:25:31', '2019-03-25 12:49:36', '', '0'); INSERT INTO `sys_dict_item` VALUES (12, 4, '3', '暂停', 'job_status', '暂停', 3, ' ', ' ', '2019-03-19 11:25:42', '2019-03-25 12:49:36', '', '0'); INSERT INTO `sys_dict_item` VALUES (13, 5, '0', '正常', 'job_execute_status', '正常', 0, ' ', ' ', '2019-03-19 11:26:27', '2019-03-25 12:49:36', '', '0'); INSERT INTO `sys_dict_item` VALUES (14, 5, '1', '异常', 'job_execute_status', '异常', 1, ' ', ' ', '2019-03-19 11:26:41', '2019-03-25 12:49:36', '', '0'); INSERT INTO `sys_dict_item` VALUES (15, 6, '1', '错失周期立即执行', 'misfire_policy', '错失周期立即执行', 1, ' ', ' ', '2019-03-19 11:27:45', '2019-03-25 12:49:36', '', '0'); INSERT INTO `sys_dict_item` VALUES (16, 6, '2', '错失周期执行一次', 'misfire_policy', '错失周期执行一次', 2, ' ', ' ', '2019-03-19 11:27:57', '2019-03-25 12:49:36', '', '0'); INSERT INTO `sys_dict_item` VALUES (17, 6, '3', '下周期执行', 'misfire_policy', '下周期执行', 3, ' ', ' ', '2019-03-19 11:28:08', '2019-03-25 12:49:36', '', '0'); INSERT INTO `sys_dict_item` VALUES (18, 7, '1', '男', 'gender', '微信-男', 0, ' ', ' ', '2019-03-27 13:45:13', '2019-03-27 13:45:13', '微信-男', '0'); INSERT INTO `sys_dict_item` VALUES (19, 7, '2', '女', 'gender', '女-微信', 1, ' ', ' ', '2019-03-27 13:45:34', '2019-03-27 13:45:34', '女-微信', '0'); INSERT INTO `sys_dict_item` VALUES (20, 7, '0', '未知', 'gender', 'x性别未知', 3, ' ', ' ', '2019-03-27 13:45:57', '2019-03-27 13:45:57', 'x性别未知', '0'); INSERT INTO `sys_dict_item` VALUES (21, 8, '0', '未关注', 'subscribe', '公众号-未关注', 0, ' ', ' ', '2019-03-27 13:49:07', '2019-03-27 13:49:07', '公众号-未关注', '0'); INSERT INTO `sys_dict_item` VALUES (22, 8, '1', '已关注', 'subscribe', '公众号-已关注', 1, ' ', ' ', '2019-03-27 13:49:26', '2019-03-27 13:49:26', '公众号-已关注', '0'); INSERT INTO `sys_dict_item` VALUES (23, 9, '0', '未回复', 'response_type', '微信消息-未回复', 0, ' ', ' ', '2019-03-28 21:29:47', '2019-03-28 21:29:47', '微信消息-未回复', '0'); INSERT INTO `sys_dict_item` VALUES (24, 9, '1', '已回复', 'response_type', '微信消息-已回复', 1, ' ', ' ', '2019-03-28 21:30:08', '2019-03-28 21:30:08', '微信消息-已回复', '0'); INSERT INTO `sys_dict_item` VALUES (25, 10, '1', '检索', 'param_type', '检索', 0, ' ', ' ', '2019-04-29 18:22:17', '2019-04-29 18:22:17', '检索', '0'); INSERT INTO `sys_dict_item` VALUES (26, 10, '2', '原文', 'param_type', '原文', 0, ' ', ' ', '2019-04-29 18:22:27', '2019-04-29 18:22:27', '原文', '0'); INSERT INTO `sys_dict_item` VALUES (27, 10, '3', '报表', 'param_type', '报表', 0, ' ', ' ', '2019-04-29 18:22:36', '2019-04-29 18:22:36', '报表', '0'); INSERT INTO `sys_dict_item` VALUES (28, 10, '4', '安全', 'param_type', '安全', 0, ' ', ' ', '2019-04-29 18:22:46', '2019-04-29 18:22:46', '安全', '0'); INSERT INTO `sys_dict_item` VALUES (29, 10, '5', '文档', 'param_type', '文档', 0, ' ', ' ', '2019-04-29 18:22:56', '2019-04-29 18:22:56', '文档', '0'); INSERT INTO `sys_dict_item` VALUES (30, 10, '6', '消息', 'param_type', '消息', 0, ' ', ' ', '2019-04-29 18:23:05', '2019-04-29 18:23:05', '消息', '0'); INSERT INTO `sys_dict_item` VALUES (31, 10, '9', '其他', 'param_type', '其他', 0, ' ', ' ', '2019-04-29 18:23:16', '2019-04-29 18:23:16', '其他', '0'); INSERT INTO `sys_dict_item` VALUES (32, 10, '0', '默认', 'param_type', '默认', 0, ' ', ' ', '2019-04-29 18:23:30', '2019-04-29 18:23:30', '默认', '0'); INSERT INTO `sys_dict_item` VALUES (33, 11, '0', '正常', 'status_type', '状态正常', 0, ' ', ' ', '2019-05-15 16:31:34', '2019-05-16 22:30:46', '状态正常', '0'); INSERT INTO `sys_dict_item` VALUES (34, 11, '9', '冻结', 'status_type', '状态冻结', 1, ' ', ' ', '2019-05-15 16:31:56', '2019-05-16 22:30:50', '状态冻结', '0'); INSERT INTO `sys_dict_item` VALUES (35, 12, '1', '系统类', 'dict_type', '系统类字典', 0, ' ', ' ', '2019-05-16 14:20:40', '2019-05-16 14:20:40', '不能修改删除', '0'); INSERT INTO `sys_dict_item` VALUES (36, 12, '0', '业务类', 'dict_type', '业务类字典', 0, ' ', ' ', '2019-05-16 14:20:59', '2019-05-16 14:20:59', '可以修改', '0'); INSERT INTO `sys_dict_item` VALUES (37, 2, 'GITEE', '码云', 'social_type', '码云', 2, ' ', ' ', '2019-06-28 09:59:12', '2019-06-28 09:59:12', '码云', '0'); INSERT INTO `sys_dict_item` VALUES (38, 2, 'OSC', '开源中国', 'social_type', '开源中国登录', 2, ' ', ' ', '2019-06-28 10:04:32', '2019-06-28 10:04:32', '', '0'); INSERT INTO `sys_dict_item` VALUES (39, 14, 'password', '密码模式', 'grant_types', '支持oauth密码模式', 0, ' ', ' ', '2019-08-13 07:35:28', '2019-08-13 07:35:28', NULL, '0'); INSERT INTO `sys_dict_item` VALUES (40, 14, 'authorization_code', '授权码模式', 'grant_types', 'oauth2 授权码模式', 1, ' ', ' ', '2019-08-13 07:36:07', '2019-08-13 07:36:07', NULL, '0'); INSERT INTO `sys_dict_item` VALUES (41, 14, 'client_credentials', '客户端模式', 'grant_types', 'oauth2 客户端模式', 2, ' ', ' ', '2019-08-13 07:36:30', '2019-08-13 07:36:30', NULL, '0'); INSERT INTO `sys_dict_item` VALUES (42, 14, 'refresh_token', '刷新模式', 'grant_types', 'oauth2 刷新token', 3, ' ', ' ', '2019-08-13 07:36:54', '2019-08-13 07:36:54', NULL, '0'); INSERT INTO `sys_dict_item` VALUES (43, 14, 'implicit', '简化模式', 'grant_types', 'oauth2 简化模式', 4, ' ', ' ', '2019-08-13 07:39:32', '2019-08-13 07:39:32', NULL, '0'); INSERT INTO `sys_dict_item` VALUES (44, 15, '0', 'Avue', 'style_type', 'Avue风格', 0, ' ', ' ', '2020-02-07 03:52:52', '2020-02-07 03:52:52', '', '0'); INSERT INTO `sys_dict_item` VALUES (45, 15, '1', 'element', 'style_type', 'element-ui', 1, ' ', ' ', '2020-02-07 03:53:12', '2020-02-07 03:53:12', '', '0'); INSERT INTO `sys_dict_item` VALUES (46, 16, '0', '关', 'captcha_flag_types', '不校验验证码', 0, ' ', ' ', '2020-11-18 06:53:58', '2020-11-18 06:53:58', '不校验验证码 -0', '0'); INSERT INTO `sys_dict_item` VALUES (47, 16, '1', '开', 'captcha_flag_types', '校验验证码', 1, ' ', ' ', '2020-11-18 06:54:15', '2020-11-18 06:54:15', '不校验验证码-1', '0'); INSERT INTO `sys_dict_item` VALUES (48, 17, '0', '否', 'enc_flag_types', '不加密', 0, ' ', ' ', '2020-11-18 06:55:31', '2020-11-18 06:55:31', '不加密-0', '0'); INSERT INTO `sys_dict_item` VALUES (49, 17, '1', '是', 'enc_flag_types', '加密', 1, ' ', ' ', '2020-11-18 06:55:51', '2020-11-18 06:55:51', '加密-1', '0'); INSERT INTO `sys_dict_item` VALUES (50, 13, 'MERGE_PAY', '聚合支付', 'channel_type', '聚合支付', 1, ' ', ' ', '2019-05-30 19:08:08', '2019-06-18 13:51:53', '聚合支付', '0'); INSERT INTO `sys_dict_item` VALUES (51, 2, 'CAS', 'CAS登录', 'social_type', 'CAS 单点登录系统', 3, ' ', ' ', '2022-02-18 13:56:25', '2022-02-18 13:56:28', NULL, '0'); INSERT INTO `sys_dict_item` VALUES (52, 2, 'DINGTALK', '钉钉', 'social_type', '钉钉', 3, ' ', ' ', '2022-02-18 13:56:25', '2022-02-18 13:56:28', NULL, '0'); INSERT INTO `sys_dict_item` VALUES (53, 2, 'WEIXIN_CP', '企业微信', 'social_type', '企业微信', 3, ' ', ' ', '2022-02-18 13:56:25', '2022-02-18 13:56:28', NULL, '0'); INSERT INTO `sys_dict_item` VALUES (54, 15, '2', 'APP', 'style_type', 'uview风格', 1, ' ', ' ', '2020-02-07 03:53:12', '2020-02-07 03:53:12', '', '0'); INSERT INTO `sys_dict_item` VALUES (55, 13, 'ALIPAY_WAP', '支付宝支付', 'channel_type', '支付宝支付', 1, ' ', ' ', '2019-05-30 19:08:08', '2019-06-18 13:51:53', '聚合支付', '0'); INSERT INTO `sys_dict_item` VALUES (56, 13, 'WEIXIN_MP', '微信支付', 'channel_type', '微信支付', 1, ' ', ' ', '2019-05-30 19:08:08', '2019-06-18 13:51:53', '聚合支付', '0'); INSERT INTO `sys_dict_item` VALUES (57, 14, 'mobile', 'mobile', 'grant_types', '移动端登录', 5, 'admin', ' ', '2023-01-29 17:21:42', NULL, NULL, '0'); INSERT INTO `sys_dict_item` VALUES (58, 18, '0', '有效', 'lock_flag', '有效', 0, 'admin', ' ', '2023-02-01 16:56:00', NULL, NULL, '0'); INSERT INTO `sys_dict_item` VALUES (59, 18, '9', '禁用', 'lock_flag', '禁用', 1, 'admin', ' ', '2023-02-01 16:56:09', NULL, NULL, '0'); INSERT INTO `sys_dict_item` VALUES (60, 15, '4', 'vue3', 'style_type', 'element-plus', 4, 'admin', ' ', '2023-02-06 13:52:43', NULL, NULL, '0'); INSERT INTO `sys_dict_item` VALUES (61, 19, '0', '主机', 'ds_config_type', '主机', 0, 'admin', ' ', '2023-02-06 18:37:23', NULL, NULL, '0'); INSERT INTO `sys_dict_item` VALUES (62, 19, '1', 'JDBC', 'ds_config_type', 'jdbc', 2, 'admin', ' ', '2023-02-06 18:37:34', NULL, NULL, '0'); INSERT INTO `sys_dict_item` VALUES (63, 20, 'false', '否', 'common_status', '否', 1, 'admin', ' ', '2023-02-09 11:02:39', NULL, NULL, '0'); INSERT INTO `sys_dict_item` VALUES (64, 20, 'true', '是', 'common_status', '是', 2, 'admin', ' ', '2023-02-09 11:02:52', NULL, NULL, '0'); INSERT INTO `sys_dict_item` VALUES (65, 21, 'MINI', '小程序', 'app_social_type', '小程序登录', 0, 'admin', ' ', '2023-02-10 11:11:41', NULL, NULL, '0'); INSERT INTO `sys_dict_item` VALUES (66, 22, '0', '否', 'yes_no_type', '0', 0, 'admin', ' ', '2023-02-20 23:35:23', NULL, '0', '0'); INSERT INTO `sys_dict_item` VALUES (67, 22, '1', '是', 'yes_no_type', '1', 0, 'admin', ' ', '2023-02-20 23:35:37', NULL, '1', '0'); INSERT INTO `sys_dict_item` VALUES (69, 23, 'text', '文本', 'repType', '文本', 0, 'admin', ' ', '2023-02-24 15:08:45', NULL, NULL, '0'); INSERT INTO `sys_dict_item` VALUES (70, 23, 'image', '图片', 'repType', '图片', 0, 'admin', ' ', '2023-02-24 15:08:56', NULL, NULL, '0'); INSERT INTO `sys_dict_item` VALUES (71, 23, 'voice', '语音', 'repType', '语音', 0, 'admin', ' ', '2023-02-24 15:09:08', NULL, NULL, '0'); INSERT INTO `sys_dict_item` VALUES (72, 23, 'video', '视频', 'repType', '视频', 0, 'admin', ' ', '2023-02-24 15:09:18', NULL, NULL, '0'); INSERT INTO `sys_dict_item` VALUES (73, 23, 'shortvideo', '小视频', 'repType', '小视频', 0, 'admin', ' ', '2023-02-24 15:09:29', NULL, NULL, '0'); INSERT INTO `sys_dict_item` VALUES (74, 23, 'location', '地理位置', 'repType', '地理位置', 0, 'admin', ' ', '2023-02-24 15:09:41', NULL, NULL, '0'); INSERT INTO `sys_dict_item` VALUES (75, 23, 'link', '链接消息', 'repType', '链接消息', 0, 'admin', ' ', '2023-02-24 15:09:49', NULL, NULL, '0'); INSERT INTO `sys_dict_item` VALUES (76, 23, 'event', '事件推送', 'repType', '事件推送', 0, 'admin', ' ', '2023-02-24 15:09:57', NULL, NULL, '0'); INSERT INTO `sys_dict_item` VALUES (77, 24, '0', '未提交', 'leave_status', '未提交', 0, 'admin', ' ', '2023-03-02 22:50:45', NULL, '未提交', '0'); INSERT INTO `sys_dict_item` VALUES (78, 24, '1', '审批中', 'leave_status', '审批中', 0, 'admin', ' ', '2023-03-02 22:50:57', NULL, '审批中', '0'); INSERT INTO `sys_dict_item` VALUES (79, 24, '2', '完成', 'leave_status', '完成', 0, 'admin', ' ', '2023-03-02 22:51:06', NULL, '完成', '0'); INSERT INTO `sys_dict_item` VALUES (80, 24, '9', '驳回', 'leave_status', '驳回', 0, 'admin', ' ', '2023-03-02 22:51:20', NULL, NULL, '0'); INSERT INTO `sys_dict_item` VALUES (81, 25, 'record', '日程记录', 'schedule_type', '日程记录', 0, 'admin', ' ', '2023-03-06 14:50:01', NULL, NULL, '0'); INSERT INTO `sys_dict_item` VALUES (82, 25, 'plan', '计划', 'schedule_type', '计划类型', 0, 'admin', ' ', '2023-03-06 14:50:29', NULL, NULL, '0'); INSERT INTO `sys_dict_item` VALUES (83, 26, '0', '计划中', 'schedule_status', '日程状态', 0, 'admin', ' ', '2023-03-06 14:53:18', NULL, NULL, '0'); INSERT INTO `sys_dict_item` VALUES (84, 26, '1', '已开始', 'schedule_status', '已开始', 0, 'admin', ' ', '2023-03-06 14:53:33', NULL, NULL, '0'); INSERT INTO `sys_dict_item` VALUES (85, 26, '3', '已结束', 'schedule_status', '已结束', 0, 'admin', ' ', '2023-03-06 14:53:41', NULL, NULL, '0'); INSERT INTO `sys_dict_item` VALUES (86, 27, 'mysql', 'mysql', 'ds_type', 'mysql', 0, 'admin', ' ', '2023-03-12 09:58:11', NULL, NULL, '0'); COMMIT; -- ---------------------------- -- Table structure for sys_file -- ---------------------------- DROP TABLE IF EXISTS `sys_file`; CREATE TABLE `sys_file` ( `id` bigint NOT NULL COMMENT '编号', `file_name` varchar(100) DEFAULT NULL COMMENT '文件名', `bucket_name` varchar(200) DEFAULT NULL COMMENT '文件存储桶名称', `original` varchar(100) DEFAULT NULL COMMENT '原始文件名', `type` varchar(50) DEFAULT NULL COMMENT '文件类型', `file_size` bigint DEFAULT NULL COMMENT '文件大小', `create_by` varchar(64) DEFAULT NULL COMMENT '创建人', `update_by` varchar(64) DEFAULT NULL COMMENT '修改人', `create_time` datetime DEFAULT NULL COMMENT '上传时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `del_flag` char(1) DEFAULT '0' COMMENT '删除标志', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB COMMENT='文件管理表'; -- ---------------------------- -- Records of sys_file -- ---------------------------- BEGIN; COMMIT; -- ---------------------------- -- Table structure for sys_log -- ---------------------------- DROP TABLE IF EXISTS `sys_log`; CREATE TABLE `sys_log` ( `id` bigint NOT NULL COMMENT '编号', `log_type` char(1) DEFAULT '0' COMMENT '日志类型', `title` varchar(255) DEFAULT NULL COMMENT '日志标题', `service_id` varchar(32) DEFAULT NULL COMMENT '服务ID', `create_by` varchar(64) DEFAULT NULL COMMENT '创建人', `update_by` varchar(64) DEFAULT NULL COMMENT '修改人', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `remote_addr` varchar(255) DEFAULT NULL COMMENT '远程地址', `user_agent` varchar(1000) DEFAULT NULL COMMENT '用户代理', `request_uri` varchar(255) DEFAULT NULL COMMENT '请求URI', `method` varchar(10) DEFAULT NULL COMMENT '请求方法', `params` text COMMENT '请求参数', `time` bigint DEFAULT NULL COMMENT '执行时间', `del_flag` char(1) DEFAULT '0' COMMENT '删除标志', `exception` text COMMENT '异常信息', PRIMARY KEY (`id`) USING BTREE, KEY `sys_log_request_uri` (`request_uri`) USING BTREE, KEY `sys_log_type` (`log_type`) USING BTREE, KEY `sys_log_create_date` (`create_time`) USING BTREE ) ENGINE=InnoDB COMMENT='日志表'; -- ---------------------------- -- Table structure for sys_menu -- ---------------------------- DROP TABLE IF EXISTS `sys_menu`; CREATE TABLE `sys_menu` ( `menu_id` bigint NOT NULL COMMENT '菜单ID', `name` varchar(32) DEFAULT NULL COMMENT '菜单名称', `en_name` varchar(128) DEFAULT NULL COMMENT '英文名称', `permission` varchar(32) DEFAULT NULL COMMENT '权限标识', `path` varchar(128) DEFAULT NULL COMMENT '路由路径', `parent_id` bigint DEFAULT NULL COMMENT '父菜单ID', `icon` varchar(64) DEFAULT NULL COMMENT '菜单图标', `visible` char(1) DEFAULT '1' COMMENT '是否可见,0隐藏,1显示', `sort_order` int DEFAULT '1' COMMENT '排序值,越小越靠前', `keep_alive` char(1) DEFAULT '0' COMMENT '是否缓存,0否,1是', `embedded` char(1) DEFAULT NULL COMMENT '是否内嵌,0否,1是', `menu_type` char(1) DEFAULT '0' COMMENT '菜单类型,0目录,1菜单,2按钮', `create_by` varchar(64) DEFAULT NULL COMMENT '创建人', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_by` varchar(64) DEFAULT NULL COMMENT '修改人', `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `del_flag` char(1) DEFAULT '0' COMMENT '删除标志,0未删除,1已删除', PRIMARY KEY (`menu_id`) USING BTREE ) ENGINE=InnoDB COMMENT='菜单权限表'; -- ---------------------------- -- Records of sys_menu -- ---------------------------- BEGIN; INSERT INTO `sys_menu` VALUES (1000, '权限管理', 'authorization', NULL, '/admin', -1, 'iconfont icon-icon-', '1', 0, '0', '0', '0', '', '2018-09-28 08:29:53', 'admin', '2023-03-12 22:32:52', '0'); INSERT INTO `sys_menu` VALUES (1100, '用户管理', 'user', NULL, '/admin/user/index', 1000, 'ele-User', '1', 1, '0', '0', '0', '', '2017-11-02 22:24:37', 'admin', '2023-07-05 10:28:22', '0'); INSERT INTO `sys_menu` VALUES (1101, '用户新增', NULL, 'sys_user_add', NULL, 1100, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 09:52:09', ' ', '2021-05-25 03:12:55', '0'); INSERT INTO `sys_menu` VALUES (1102, '用户修改', NULL, 'sys_user_edit', NULL, 1100, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 09:52:48', ' ', '2021-05-25 03:12:55', '0'); INSERT INTO `sys_menu` VALUES (1103, '用户删除', NULL, 'sys_user_del', NULL, 1100, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 09:54:01', ' ', '2021-05-25 03:12:55', '0'); INSERT INTO `sys_menu` VALUES (1104, '导入导出', NULL, 'sys_user_export', NULL, 1100, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 09:54:01', ' ', '2021-05-25 03:12:55', '0'); INSERT INTO `sys_menu` VALUES (1200, '菜单管理', 'menu', NULL, '/admin/menu/index', 1000, 'iconfont icon-caidan', '1', 2, '0', '0', '0', '', '2017-11-08 09:57:27', 'admin', '2023-07-05 10:28:17', '0'); INSERT INTO `sys_menu` VALUES (1201, '菜单新增', NULL, 'sys_menu_add', NULL, 1200, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 10:15:53', ' ', '2021-05-25 03:12:55', '0'); INSERT INTO `sys_menu` VALUES (1202, '菜单修改', NULL, 'sys_menu_edit', NULL, 1200, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 10:16:23', ' ', '2021-05-25 03:12:55', '0'); INSERT INTO `sys_menu` VALUES (1203, '菜单删除', NULL, 'sys_menu_del', NULL, 1200, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 10:16:43', ' ', '2021-05-25 03:12:55', '0'); INSERT INTO `sys_menu` VALUES (1300, '角色管理', 'role', NULL, '/admin/role/index', 1000, 'iconfont icon-gerenzhongxin', '1', 3, '0', NULL, '0', '', '2017-11-08 10:13:37', 'admin', '2023-07-05 10:28:13', '0'); INSERT INTO `sys_menu` VALUES (1301, '角色新增', NULL, 'sys_role_add', NULL, 1300, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 10:14:18', ' ', '2021-05-25 03:12:55', '0'); INSERT INTO `sys_menu` VALUES (1302, '角色修改', NULL, 'sys_role_edit', NULL, 1300, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 10:14:41', ' ', '2021-05-25 03:12:55', '0'); INSERT INTO `sys_menu` VALUES (1303, '角色删除', NULL, 'sys_role_del', NULL, 1300, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 10:14:59', ' ', '2021-05-25 03:12:55', '0'); INSERT INTO `sys_menu` VALUES (1304, '分配权限', NULL, 'sys_role_perm', NULL, 1300, NULL, '1', 1, '0', NULL, '1', ' ', '2018-04-20 07:22:55', ' ', '2021-05-25 03:12:55', '0'); INSERT INTO `sys_menu` VALUES (1305, '角色导入导出', NULL, 'sys_role_export', NULL, 1300, NULL, '1', 4, '0', NULL, '1', ' ', '2022-03-26 15:54:34', ' ', NULL, '0'); INSERT INTO `sys_menu` VALUES (1400, '部门管理', 'dept', NULL, '/admin/dept/index', 1000, 'iconfont icon-zidingyibuju', '1', 4, '0', NULL, '0', '', '2018-01-20 13:17:19', 'admin', '2023-07-05 10:28:07', '0'); INSERT INTO `sys_menu` VALUES (1401, '部门新增', NULL, 'sys_dept_add', NULL, 1400, NULL, '1', 1, '0', NULL, '1', ' ', '2018-01-20 14:56:16', ' ', '2021-05-25 03:12:55', '0'); INSERT INTO `sys_menu` VALUES (1402, '部门修改', NULL, 'sys_dept_edit', NULL, 1400, NULL, '1', 1, '0', NULL, '1', ' ', '2018-01-20 14:56:59', ' ', '2021-05-25 03:12:55', '0'); INSERT INTO `sys_menu` VALUES (1403, '部门删除', NULL, 'sys_dept_del', NULL, 1400, NULL, '1', 1, '0', NULL, '1', ' ', '2018-01-20 14:57:28', ' ', '2021-05-25 03:12:55', '0'); INSERT INTO `sys_menu` VALUES (1600, '岗位管理', 'post', NULL, '/admin/post/index', 1000, 'iconfont icon--chaifenhang', '1', 5, '1', '0', '0', '', '2022-03-26 13:04:14', 'admin', '2023-07-05 10:28:03', '0'); INSERT INTO `sys_menu` VALUES (1601, '岗位信息查看', NULL, 'sys_post_view', NULL, 1600, NULL, '1', 0, '0', NULL, '1', ' ', '2022-03-26 13:05:34', ' ', NULL, '0'); INSERT INTO `sys_menu` VALUES (1602, '岗位信息新增', NULL, 'sys_post_add', NULL, 1600, NULL, '1', 1, '0', NULL, '1', ' ', '2022-03-26 13:06:00', ' ', NULL, '0'); INSERT INTO `sys_menu` VALUES (1603, '岗位信息修改', NULL, 'sys_post_edit', NULL, 1600, NULL, '1', 2, '0', NULL, '1', ' ', '2022-03-26 13:06:31', ' ', '2022-03-26 13:06:38', '0'); INSERT INTO `sys_menu` VALUES (1604, '岗位信息删除', NULL, 'sys_post_del', NULL, 1600, NULL, '1', 3, '0', NULL, '1', ' ', '2022-03-26 13:06:31', ' ', NULL, '0'); INSERT INTO `sys_menu` VALUES (1605, '岗位导入导出', NULL, 'sys_post_export', NULL, 1600, NULL, '1', 4, '0', NULL, '1', ' ', '2022-03-26 13:06:31', ' ', '2022-03-26 06:32:02', '0'); INSERT INTO `sys_menu` VALUES (2000, '系统管理', 'system', NULL, '/system', -1, 'iconfont icon-quanjushezhi_o', '1', 1, '0', NULL, '0', '', '2017-11-07 20:56:00', 'admin', '2023-07-05 10:27:58', '0'); INSERT INTO `sys_menu` VALUES (2001, '日志管理', 'log', NULL, '/admin/logs', 2000, 'ele-Cloudy', '1', 0, '0', '0', '0', 'admin', '2023-03-02 12:26:42', 'admin', '2023-07-05 10:27:53', '0'); INSERT INTO `sys_menu` VALUES (2100, '操作日志', 'operation', NULL, '/admin/log/index', 2001, 'iconfont icon-jinridaiban', '1', 2, '0', '0', '0', '', '2017-11-20 14:06:22', 'admin', '2023-07-05 10:27:49', '0'); INSERT INTO `sys_menu` VALUES (2101, '日志删除', NULL, 'sys_log_del', NULL, 2100, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-20 20:37:37', ' ', '2021-05-25 03:12:55', '0'); INSERT INTO `sys_menu` VALUES (2102, '导入导出', NULL, 'sys_log_export', NULL, 2100, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 09:54:01', ' ', '2021-05-25 03:12:55', '0'); INSERT INTO `sys_menu` VALUES (2200, '字典管理', 'dict', NULL, '/admin/dict/index', 2000, 'iconfont icon-zhongduancanshuchaxun', '1', 6, '0', NULL, '0', '', '2017-11-29 11:30:52', 'admin', '2023-07-05 10:27:37', '0'); INSERT INTO `sys_menu` VALUES (2201, '字典删除', NULL, 'sys_dict_del', NULL, 2200, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-29 11:30:11', ' ', '2021-05-25 03:12:55', '0'); INSERT INTO `sys_menu` VALUES (2202, '字典新增', NULL, 'sys_dict_add', NULL, 2200, NULL, '1', 1, '0', NULL, '1', ' ', '2018-05-11 22:34:55', ' ', '2021-05-25 03:12:55', '0'); INSERT INTO `sys_menu` VALUES (2203, '字典修改', NULL, 'sys_dict_edit', NULL, 2200, NULL, '1', 1, '0', NULL, '1', ' ', '2018-05-11 22:36:03', ' ', '2021-05-25 03:12:55', '0'); INSERT INTO `sys_menu` VALUES (2210, '参数管理', 'parameter', NULL, '/admin/param/index', 2000, 'iconfont icon-wenducanshu-05', '1', 7, '1', NULL, '0', '', '2019-04-29 22:16:50', 'admin', '2023-02-16 15:24:51', '0'); INSERT INTO `sys_menu` VALUES (2211, '参数新增', NULL, 'sys_syspublicparam_add', NULL, 2210, NULL, '1', 1, '0', NULL, '1', ' ', '2019-04-29 22:17:36', ' ', '2020-03-24 08:57:11', '0'); INSERT INTO `sys_menu` VALUES (2212, '参数删除', NULL, 'sys_syspublicparam_del', NULL, 2210, NULL, '1', 1, '0', NULL, '1', ' ', '2019-04-29 22:17:55', ' ', '2020-03-24 08:57:12', '0'); INSERT INTO `sys_menu` VALUES (2213, '参数编辑', NULL, 'sys_syspublicparam_edit', NULL, 2210, NULL, '1', 1, '0', NULL, '1', ' ', '2019-04-29 22:18:14', ' ', '2020-03-24 08:57:13', '0'); INSERT INTO `sys_menu` VALUES (2300, '代码生成', 'code', NULL, '/gen/table/index', 9000, 'iconfont icon-zhongduancanshu', '1', 1, '0', '0', '0', '', '2018-01-20 13:17:19', 'admin', '2023-02-20 13:54:35', '0'); INSERT INTO `sys_menu` VALUES (2400, '终端管理', 'client', NULL, '/admin/client/index', 2000, 'iconfont icon-gongju', '1', 9, '1', NULL, '0', '', '2018-01-20 13:17:19', 'admin', '2023-02-16 15:25:28', '0'); INSERT INTO `sys_menu` VALUES (2401, '客户端新增', NULL, 'sys_client_add', NULL, 2400, '1', '1', 1, '0', NULL, '1', ' ', '2018-05-15 21:35:18', ' ', '2021-05-25 03:12:55', '0'); INSERT INTO `sys_menu` VALUES (2402, '客户端修改', NULL, 'sys_client_edit', NULL, 2400, NULL, '1', 1, '0', NULL, '1', ' ', '2018-05-15 21:37:06', ' ', '2021-05-25 03:12:55', '0'); INSERT INTO `sys_menu` VALUES (2403, '客户端删除', NULL, 'sys_client_del', NULL, 2400, NULL, '1', 1, '0', NULL, '1', ' ', '2018-05-15 21:39:16', ' ', '2021-05-25 03:12:55', '0'); INSERT INTO `sys_menu` VALUES (2600, '令牌管理', 'token', NULL, '/admin/token/index', 2000, 'ele-Key', '1', 11, '0', NULL, '0', '', '2018-09-04 05:58:41', 'admin', '2023-02-16 15:28:28', '0'); INSERT INTO `sys_menu` VALUES (2601, '令牌删除', NULL, 'sys_token_del', NULL, 2600, NULL, '1', 1, '0', NULL, '1', ' ', '2018-09-04 05:59:50', ' ', '2020-03-24 08:57:24', '0'); INSERT INTO `sys_menu` VALUES (2800, 'Quartz管理', 'quartz', NULL, '/daemon/job-manage/index', 2000, 'ele-AlarmClock', '1', 8, '0', NULL, '0', '', '2018-01-20 13:17:19', 'admin', '2023-02-16 15:25:06', '0'); INSERT INTO `sys_menu` VALUES (2810, '任务新增', NULL, 'job_sys_job_add', NULL, 2800, '1', '1', 0, '0', NULL, '1', ' ', '2018-05-15 21:35:18', ' ', '2020-03-24 08:57:26', '0'); INSERT INTO `sys_menu` VALUES (2820, '任务修改', NULL, 'job_sys_job_edit', NULL, 2800, '1', '1', 0, '0', NULL, '1', ' ', '2018-05-15 21:35:18', ' ', '2020-03-24 08:57:27', '0'); INSERT INTO `sys_menu` VALUES (2830, '任务删除', NULL, 'job_sys_job_del', NULL, 2800, '1', '1', 0, '0', NULL, '1', ' ', '2018-05-15 21:35:18', ' ', '2020-03-24 08:57:28', '0'); INSERT INTO `sys_menu` VALUES (2840, '任务暂停', NULL, 'job_sys_job_shutdown_job', NULL, 2800, '1', '1', 0, '0', NULL, '1', ' ', '2018-05-15 21:35:18', ' ', '2020-03-24 08:57:28', '0'); INSERT INTO `sys_menu` VALUES (2850, '任务开始', NULL, 'job_sys_job_start_job', NULL, 2800, '1', '1', 0, '0', NULL, '1', ' ', '2018-05-15 21:35:18', ' ', '2020-03-24 08:57:29', '0'); INSERT INTO `sys_menu` VALUES (2860, '任务刷新', NULL, 'job_sys_job_refresh_job', NULL, 2800, '1', '1', 0, '0', NULL, '1', ' ', '2018-05-15 21:35:18', ' ', '2020-03-24 08:57:30', '0'); INSERT INTO `sys_menu` VALUES (2870, '执行任务', NULL, 'job_sys_job_run_job', NULL, 2800, '1', '1', 0, '0', NULL, '1', ' ', '2019-08-08 15:35:18', ' ', '2020-03-24 08:57:31', '0'); INSERT INTO `sys_menu` VALUES (2871, '导出', NULL, 'job_sys_job_export', NULL, 2800, NULL, '1', 0, '0', '0', '1', 'admin', '2023-03-06 15:26:13', ' ', NULL, '0'); INSERT INTO `sys_menu` VALUES (2906, '文件管理', 'file', NULL, '/admin/file/index', 2000, 'ele-Files', '1', 6, '0', NULL, '0', '', '2019-06-25 12:44:46', 'admin', '2023-02-16 15:24:42', '0'); INSERT INTO `sys_menu` VALUES (2907, '删除文件', NULL, 'sys_file_del', NULL, 2906, NULL, '1', 1, '0', NULL, '1', ' ', '2019-06-25 13:41:41', ' ', '2020-03-24 08:58:42', '0'); INSERT INTO `sys_menu` VALUES (4000, '系统监控', 'monitor', NULL, '/daemon', -1, 'iconfont icon-shuju', '1', 3, '0', '0', '0', 'admin', '2023-02-06 20:20:47', 'admin', '2023-02-23 20:01:07', '0'); INSERT INTO `sys_menu` VALUES (4001, '文档扩展', 'doc', NULL, 'http://pig-gateway:9999/swagger-ui.html', 4000, 'iconfont icon-biaodan', '1', 2, '0', '1', '0', '', '2018-06-26 10:50:32', 'admin', '2023-02-23 20:01:29', '0'); INSERT INTO `sys_menu` VALUES (4002, '缓存监控', 'cache', NULL, '/ext/cache', 4000, 'iconfont icon-shuju', '1', 1, '0', '0', '0', 'admin', '2023-05-29 15:12:59', 'admin', '2023-06-06 11:58:41', '0'); INSERT INTO `sys_menu` VALUES (9000, '开发平台', 'develop', NULL, '/gen', -1, 'iconfont icon-shuxingtu', '1', 9, '0', '0', '0', '', '2019-08-12 09:35:16', 'admin', '2023-07-05 10:25:27', '0'); INSERT INTO `sys_menu` VALUES (9005, '数据源管理', 'datasource', NULL, '/gen/datasource/index', 9000, 'ele-Coin', '1', 0, '0', NULL, '0', '', '2019-08-12 09:42:11', 'admin', '2023-07-05 10:26:56', '0'); INSERT INTO `sys_menu` VALUES (9006, '表单设计', 'Form Design', NULL, '/gen/design/index', 9000, 'iconfont icon-AIshiyanshi', '0', 2, '0', '0', '0', '', '2019-08-16 10:08:56', 'admin', '2023-02-23 14:06:50', '0'); INSERT INTO `sys_menu` VALUES (9007, '生成页面', 'generation', NULL, '/gen/gener/index', 9000, 'iconfont icon-tongzhi4', '0', 0, '0', '0', '0', 'admin', '2023-02-20 09:58:23', 'admin', '2023-07-05 10:27:06', '0'); INSERT INTO `sys_menu` VALUES (9050, '元数据管理', 'metadata', NULL, '/gen/metadata', 9000, 'iconfont icon--chaifenhang', '1', 9, '0', '0', '0', '', '2018-07-27 01:13:21', 'admin', '2023-07-05 10:27:13', '0'); INSERT INTO `sys_menu` VALUES (9051, '模板管理', 'template', NULL, '/gen/template/index', 9050, 'iconfont icon--chaifenhang', '1', 5, '0', '0', '0', 'admin', '2023-02-21 11:22:54', 'admin', '2023-07-05 10:27:18', '0'); INSERT INTO `sys_menu` VALUES (9052, '查询', NULL, 'codegen_template_view', NULL, 9051, NULL, '0', 0, '0', '0', '1', 'admin', '2023-02-21 12:33:03', 'admin', '2023-02-21 13:50:54', '0'); INSERT INTO `sys_menu` VALUES (9053, '增加', NULL, 'codegen_template_add', NULL, 9051, NULL, '1', 0, '0', '0', '1', 'admin', '2023-02-21 13:34:10', 'admin', '2023-02-21 13:39:49', '0'); INSERT INTO `sys_menu` VALUES (9054, '新增', NULL, 'codegen_template_add', NULL, 9051, NULL, '0', 1, '0', '0', '1', 'admin', '2023-02-21 13:51:32', ' ', NULL, '0'); INSERT INTO `sys_menu` VALUES (9055, '导出', NULL, 'codegen_template_export', NULL, 9051, NULL, '0', 2, '0', '0', '1', 'admin', '2023-02-21 13:51:58', ' ', NULL, '0'); INSERT INTO `sys_menu` VALUES (9056, '删除', NULL, 'codegen_template_del', NULL, 9051, NULL, '0', 3, '0', '0', '1', 'admin', '2023-02-21 13:52:16', ' ', NULL, '0'); INSERT INTO `sys_menu` VALUES (9057, '编辑', NULL, 'codegen_template_edit', NULL, 9051, NULL, '0', 4, '0', '0', '1', 'admin', '2023-02-21 13:52:58', ' ', NULL, '0'); INSERT INTO `sys_menu` VALUES (9059, '模板分组', 'group', NULL, '/gen/group/index', 9050, 'iconfont icon-shuxingtu', '1', 6, '0', '0', '0', 'admin', '2023-02-21 15:06:50', 'admin', '2023-07-05 10:27:22', '0'); INSERT INTO `sys_menu` VALUES (9060, '查询', NULL, 'codegen_group_view', NULL, 9059, NULL, '0', 0, '0', '0', '1', 'admin', '2023-02-21 15:08:07', ' ', NULL, '0'); INSERT INTO `sys_menu` VALUES (9061, '新增', NULL, 'codegen_group_add', NULL, 9059, NULL, '0', 0, '0', '0', '1', 'admin', '2023-02-21 15:08:28', ' ', NULL, '0'); INSERT INTO `sys_menu` VALUES (9062, '修改', NULL, 'codegen_group_edit', NULL, 9059, NULL, '0', 0, '0', '0', '1', 'admin', '2023-02-21 15:08:43', ' ', NULL, '0'); INSERT INTO `sys_menu` VALUES (9063, '删除', NULL, 'codegen_group_del', NULL, 9059, NULL, '0', 0, '0', '0', '1', 'admin', '2023-02-21 15:09:02', ' ', NULL, '0'); INSERT INTO `sys_menu` VALUES (9064, '导出', NULL, 'codegen_group_export', NULL, 9059, NULL, '0', 0, '0', '0', '1', 'admin', '2023-02-21 15:09:22', ' ', NULL, '0'); INSERT INTO `sys_menu` VALUES (9065, '字段管理', 'field', NULL, '/gen/field-type/index', 9050, 'iconfont icon-fuwenben', '1', 0, '0', '0', '0', 'admin', '2023-02-23 20:05:09', 'admin', '2023-07-05 10:27:31', '0'); COMMIT; -- ---------------------------- -- Table structure for sys_oauth_client_details -- ---------------------------- DROP TABLE IF EXISTS `sys_oauth_client_details`; CREATE TABLE `sys_oauth_client_details` ( `id` bigint NOT NULL COMMENT 'ID', `client_id` varchar(32) NOT NULL COMMENT '客户端ID', `resource_ids` varchar(256) DEFAULT NULL COMMENT '资源ID集合', `client_secret` varchar(256) DEFAULT NULL COMMENT '客户端秘钥', `scope` varchar(256) DEFAULT NULL COMMENT '授权范围', `authorized_grant_types` varchar(256) DEFAULT NULL COMMENT '授权类型', `web_server_redirect_uri` varchar(256) DEFAULT NULL COMMENT '回调地址', `authorities` varchar(256) DEFAULT NULL COMMENT '权限集合', `access_token_validity` int DEFAULT NULL COMMENT '访问令牌有效期(秒)', `refresh_token_validity` int DEFAULT NULL COMMENT '刷新令牌有效期(秒)', `additional_information` varchar(4096) DEFAULT NULL COMMENT '附加信息', `autoapprove` varchar(256) DEFAULT NULL COMMENT '自动授权', `del_flag` char(1) DEFAULT '0' COMMENT '删除标记,0未删除,1已删除', `create_by` varchar(64) DEFAULT NULL COMMENT '创建人', `update_by` varchar(64) DEFAULT NULL COMMENT '修改人', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB COMMENT='终端信息表'; -- ---------------------------- -- Records of sys_oauth_client_details -- ---------------------------- BEGIN; INSERT INTO `sys_oauth_client_details` VALUES (1, 'app', NULL, 'app', 'server', 'password,refresh_token,authorization_code,client_credentials,mobile', 'http://localhost:4040/sso1/login,http://localhost:4041/sso1/login,http://localhost:8080/renren-admin/sys/oauth2-sso,http://localhost:8090/sys/oauth2-sso', NULL, 43200, 2592001, '{\"enc_flag\":\"1\",\"captcha_flag\":\"1\",\"online_quantity\":\"1\"}', 'true', '0', '', 'admin', NULL, '2023-02-09 13:54:54'); INSERT INTO `sys_oauth_client_details` VALUES (2, 'daemon', NULL, 'daemon', 'server', 'password,refresh_token', NULL, NULL, 43200, 2592001, '{\"enc_flag\":\"1\",\"captcha_flag\":\"1\"}', 'true', '0', ' ', ' ', NULL, NULL); INSERT INTO `sys_oauth_client_details` VALUES (3, 'gen', NULL, 'gen', 'server', 'password,refresh_token', NULL, NULL, 43200, 2592001, '{\"enc_flag\":\"1\",\"captcha_flag\":\"1\"}', 'true', '0', ' ', ' ', NULL, NULL); INSERT INTO `sys_oauth_client_details` VALUES (4, 'mp', NULL, 'mp', 'server', 'password,refresh_token', NULL, NULL, 43200, 2592001, '{\"enc_flag\":\"1\",\"captcha_flag\":\"1\"}', 'true', '0', ' ', ' ', NULL, NULL); INSERT INTO `sys_oauth_client_details` VALUES (5, 'pig', NULL, 'pig', 'server', 'password,refresh_token,authorization_code,client_credentials,mobile', 'http://localhost:4040/sso1/login,http://localhost:4041/sso1/login,http://localhost:8080/renren-admin/sys/oauth2-sso,http://localhost:8090/sys/oauth2-sso', NULL, 43200, 2592001, '{\"enc_flag\":\"1\",\"captcha_flag\":\"1\",\"online_quantity\":\"1\"}', 'false', '0', '', 'admin', NULL, '2023-03-08 11:32:41'); INSERT INTO `sys_oauth_client_details` VALUES (6, 'test', NULL, 'test', 'server', 'password,refresh_token', NULL, NULL, 43200, 2592001, '{ \"enc_flag\":\"1\",\"captcha_flag\":\"0\"}', 'true', '0', ' ', ' ', NULL, NULL); INSERT INTO `sys_oauth_client_details` VALUES (7, 'social', NULL, 'social', 'server', 'password,refresh_token,mobile', NULL, NULL, 43200, 2592001, '{ \"enc_flag\":\"0\",\"captcha_flag\":\"0\"}', 'true', '0', ' ', ' ', NULL, NULL); COMMIT; -- ---------------------------- -- Table structure for sys_post -- ---------------------------- DROP TABLE IF EXISTS `sys_post`; CREATE TABLE `sys_post` ( `post_id` bigint NOT NULL COMMENT '岗位ID', `post_code` varchar(64) NOT NULL COMMENT '岗位编码', `post_name` varchar(50) NOT NULL COMMENT '岗位名称', `post_sort` int NOT NULL COMMENT '岗位排序', `remark` varchar(500) DEFAULT NULL COMMENT '岗位描述', `del_flag` char(1) NOT NULL DEFAULT '0' COMMENT '是否删除 -1:已删除 0:正常', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `create_by` varchar(64) DEFAULT NULL COMMENT '创建人', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `update_by` varchar(64) DEFAULT NULL COMMENT '更新人', PRIMARY KEY (`post_id`) USING BTREE ) ENGINE=InnoDB COMMENT='岗位信息表'; -- ---------------------------- -- Records of sys_post -- ---------------------------- BEGIN; INSERT INTO `sys_post` VALUES (1, 'CTO', 'CTO', 0, 'CTOOO', '0', '2022-03-26 13:48:17', '', '2023-03-08 16:03:35', 'admin'); COMMIT; -- ---------------------------- -- Table structure for sys_public_param -- ---------------------------- DROP TABLE IF EXISTS `sys_public_param`; CREATE TABLE `sys_public_param` ( `public_id` bigint NOT NULL COMMENT '编号', `public_name` varchar(128) DEFAULT NULL COMMENT '名称', `public_key` varchar(128) DEFAULT NULL COMMENT '键', `public_value` varchar(128) DEFAULT NULL COMMENT '值', `status` char(1) DEFAULT '0' COMMENT '状态,0禁用,1启用', `validate_code` varchar(64) DEFAULT NULL COMMENT '校验码', `create_by` varchar(64) DEFAULT NULL COMMENT '创建人', `update_by` varchar(64) DEFAULT NULL COMMENT '修改人', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `public_type` char(1) DEFAULT '0' COMMENT '类型,0未知,1系统,2业务', `system_flag` char(1) DEFAULT '0' COMMENT '系统标识,0非系统,1系统', `del_flag` char(1) DEFAULT '0' COMMENT '删除标记,0未删除,1已删除', PRIMARY KEY (`public_id`) USING BTREE ) ENGINE=InnoDB COMMENT='公共参数配置表'; -- ---------------------------- -- Records of sys_public_param -- ---------------------------- BEGIN; INSERT INTO `sys_public_param` VALUES (1, '租户默认来源', 'TENANT_DEFAULT_ID', '1', '0', '', ' ', ' ', '2020-05-12 04:03:46', '2020-06-20 08:56:30', '2', '0', '1'); INSERT INTO `sys_public_param` VALUES (2, '租户默认部门名称', 'TENANT_DEFAULT_DEPTNAME', '租户默认部门', '0', '', ' ', ' ', '2020-05-12 03:36:32', NULL, '2', '1', '0'); INSERT INTO `sys_public_param` VALUES (3, '租户默认账户', 'TENANT_DEFAULT_USERNAME', 'admin', '0', '', ' ', ' ', '2020-05-12 04:05:04', NULL, '2', '1', '0'); INSERT INTO `sys_public_param` VALUES (4, '租户默认密码', 'TENANT_DEFAULT_PASSWORD', '123456', '0', '', ' ', ' ', '2020-05-12 04:05:24', NULL, '2', '1', '0'); INSERT INTO `sys_public_param` VALUES (5, '租户默认角色编码', 'TENANT_DEFAULT_ROLECODE', 'ROLE_ADMIN', '0', '', ' ', ' ', '2020-05-12 04:05:57', NULL, '2', '1', '0'); INSERT INTO `sys_public_param` VALUES (6, '租户默认角色名称', 'TENANT_DEFAULT_ROLENAME', '租户默认角色', '0', '', ' ', ' ', '2020-05-12 04:06:19', NULL, '2', '1', '0'); INSERT INTO `sys_public_param` VALUES (7, '表前缀', 'GEN_TABLE_PREFIX', 'tb_', '0', '', ' ', ' ', '2020-05-12 04:23:04', NULL, '9', '1', '0'); INSERT INTO `sys_public_param` VALUES (8, '接口文档不显示的字段', 'GEN_HIDDEN_COLUMNS', 'tenant_id', '0', '', ' ', ' ', '2020-05-12 04:25:19', NULL, '9', '1', '0'); INSERT INTO `sys_public_param` VALUES (9, '注册用户默认角色', 'USER_DEFAULT_ROLE', 'GENERAL_USER', '0', NULL, ' ', ' ', '2022-03-31 16:52:24', NULL, '2', '1', '0'); COMMIT; -- ---------------------------- -- Table structure for sys_role -- ---------------------------- DROP TABLE IF EXISTS `sys_role`; CREATE TABLE `sys_role` ( `role_id` bigint NOT NULL COMMENT '角色ID', `role_name` varchar(64) DEFAULT NULL COMMENT '角色名称', `role_code` varchar(64) DEFAULT NULL COMMENT '角色编码', `role_desc` varchar(255) DEFAULT NULL COMMENT '角色描述', `create_by` varchar(64) DEFAULT NULL COMMENT '创建人', `update_by` varchar(64) DEFAULT NULL COMMENT '修改人', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `del_flag` char(1) DEFAULT '0' COMMENT '删除标记,0未删除,1已删除', PRIMARY KEY (`role_id`) USING BTREE, KEY `role_idx1_role_code` (`role_code`) USING BTREE ) ENGINE=InnoDB COMMENT='系统角色表'; -- ---------------------------- -- Records of sys_role -- ---------------------------- BEGIN; INSERT INTO `sys_role` VALUES (1, '管理员', 'ROLE_ADMIN', '管理员', '', 'admin', '2017-10-29 15:45:51', '2023-07-07 14:55:07', '0'); INSERT INTO `sys_role` VALUES (2, '普通用户', 'GENERAL_USER', '普通用户', '', 'admin', '2022-03-31 17:03:15', '2023-04-03 02:28:51', '0'); COMMIT; -- ---------------------------- -- 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 COMMENT='角色菜单表'; -- ---------------------------- -- Records of sys_role_menu -- ---------------------------- BEGIN; INSERT INTO `sys_role_menu` VALUES (1, 1000); INSERT INTO `sys_role_menu` VALUES (1, 1100); INSERT INTO `sys_role_menu` VALUES (1, 1101); INSERT INTO `sys_role_menu` VALUES (1, 1102); INSERT INTO `sys_role_menu` VALUES (1, 1103); INSERT INTO `sys_role_menu` VALUES (1, 1104); INSERT INTO `sys_role_menu` VALUES (1, 1200); INSERT INTO `sys_role_menu` VALUES (1, 1201); INSERT INTO `sys_role_menu` VALUES (1, 1202); INSERT INTO `sys_role_menu` VALUES (1, 1203); INSERT INTO `sys_role_menu` VALUES (1, 1300); INSERT INTO `sys_role_menu` VALUES (1, 1301); INSERT INTO `sys_role_menu` VALUES (1, 1302); INSERT INTO `sys_role_menu` VALUES (1, 1303); INSERT INTO `sys_role_menu` VALUES (1, 1304); INSERT INTO `sys_role_menu` VALUES (1, 1305); INSERT INTO `sys_role_menu` VALUES (1, 1400); INSERT INTO `sys_role_menu` VALUES (1, 1401); INSERT INTO `sys_role_menu` VALUES (1, 1402); INSERT INTO `sys_role_menu` VALUES (1, 1403); INSERT INTO `sys_role_menu` VALUES (1, 1600); INSERT INTO `sys_role_menu` VALUES (1, 1601); INSERT INTO `sys_role_menu` VALUES (1, 1602); INSERT INTO `sys_role_menu` VALUES (1, 1603); INSERT INTO `sys_role_menu` VALUES (1, 1604); INSERT INTO `sys_role_menu` VALUES (1, 1605); INSERT INTO `sys_role_menu` VALUES (1, 2000); INSERT INTO `sys_role_menu` VALUES (1, 2001); INSERT INTO `sys_role_menu` VALUES (1, 2100); INSERT INTO `sys_role_menu` VALUES (1, 2101); INSERT INTO `sys_role_menu` VALUES (1, 2102); INSERT INTO `sys_role_menu` VALUES (1, 2200); INSERT INTO `sys_role_menu` VALUES (1, 2201); INSERT INTO `sys_role_menu` VALUES (1, 2202); INSERT INTO `sys_role_menu` VALUES (1, 2203); INSERT INTO `sys_role_menu` VALUES (1, 2210); INSERT INTO `sys_role_menu` VALUES (1, 2211); INSERT INTO `sys_role_menu` VALUES (1, 2212); INSERT INTO `sys_role_menu` VALUES (1, 2213); INSERT INTO `sys_role_menu` VALUES (1, 2300); INSERT INTO `sys_role_menu` VALUES (1, 2400); INSERT INTO `sys_role_menu` VALUES (1, 2401); INSERT INTO `sys_role_menu` VALUES (1, 2402); INSERT INTO `sys_role_menu` VALUES (1, 2403); INSERT INTO `sys_role_menu` VALUES (1, 2600); INSERT INTO `sys_role_menu` VALUES (1, 2601); INSERT INTO `sys_role_menu` VALUES (1, 2800); INSERT INTO `sys_role_menu` VALUES (1, 2810); INSERT INTO `sys_role_menu` VALUES (1, 2820); INSERT INTO `sys_role_menu` VALUES (1, 2830); INSERT INTO `sys_role_menu` VALUES (1, 2840); INSERT INTO `sys_role_menu` VALUES (1, 2850); INSERT INTO `sys_role_menu` VALUES (1, 2860); INSERT INTO `sys_role_menu` VALUES (1, 2870); INSERT INTO `sys_role_menu` VALUES (1, 2871); INSERT INTO `sys_role_menu` VALUES (1, 2906); INSERT INTO `sys_role_menu` VALUES (1, 2907); INSERT INTO `sys_role_menu` VALUES (1, 4000); INSERT INTO `sys_role_menu` VALUES (1, 4001); INSERT INTO `sys_role_menu` VALUES (1, 4002); INSERT INTO `sys_role_menu` VALUES (1, 9000); INSERT INTO `sys_role_menu` VALUES (1, 9005); INSERT INTO `sys_role_menu` VALUES (1, 9006); INSERT INTO `sys_role_menu` VALUES (1, 9007); INSERT INTO `sys_role_menu` VALUES (1, 9050); INSERT INTO `sys_role_menu` VALUES (1, 9051); INSERT INTO `sys_role_menu` VALUES (1, 9052); INSERT INTO `sys_role_menu` VALUES (1, 9053); INSERT INTO `sys_role_menu` VALUES (1, 9054); INSERT INTO `sys_role_menu` VALUES (1, 9055); INSERT INTO `sys_role_menu` VALUES (1, 9056); INSERT INTO `sys_role_menu` VALUES (1, 9057); INSERT INTO `sys_role_menu` VALUES (1, 9059); INSERT INTO `sys_role_menu` VALUES (1, 9060); INSERT INTO `sys_role_menu` VALUES (1, 9061); INSERT INTO `sys_role_menu` VALUES (1, 9062); INSERT INTO `sys_role_menu` VALUES (1, 9063); INSERT INTO `sys_role_menu` VALUES (1, 9064); INSERT INTO `sys_role_menu` VALUES (1, 9065); INSERT INTO `sys_role_menu` VALUES (2, 4000); INSERT INTO `sys_role_menu` VALUES (2, 4001); INSERT INTO `sys_role_menu` VALUES (2, 4002); COMMIT; -- ---------------------------- -- Table structure for sys_user -- ---------------------------- DROP TABLE IF EXISTS `sys_user`; CREATE TABLE `sys_user` ( `user_id` bigint NOT NULL COMMENT '用户ID', `username` varchar(64) DEFAULT NULL COMMENT '用户名', `password` varchar(255) DEFAULT NULL COMMENT '密码', `salt` varchar(255) DEFAULT NULL COMMENT '盐值', `phone` varchar(20) DEFAULT NULL COMMENT '电话号码', `avatar` varchar(255) DEFAULT NULL COMMENT '头像', `nickname` varchar(64) DEFAULT NULL COMMENT '昵称', `name` varchar(64) DEFAULT NULL COMMENT '姓名', `email` varchar(128) DEFAULT NULL COMMENT '邮箱地址', `dept_id` bigint DEFAULT NULL COMMENT '所属部门ID', `create_by` varchar(64) DEFAULT NULL COMMENT '创建人', `update_by` varchar(64) DEFAULT NULL COMMENT '修改人', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', `lock_flag` char(1) DEFAULT '0' COMMENT '锁定标记,0未锁定,9已锁定', `del_flag` char(1) DEFAULT '0' COMMENT '删除标记,0未删除,1已删除', `wx_openid` varchar(32) DEFAULT NULL COMMENT '微信登录openId', `mini_openid` varchar(32) DEFAULT NULL COMMENT '小程序openId', `qq_openid` varchar(32) DEFAULT NULL COMMENT 'QQ openId', `gitee_login` varchar(100) DEFAULT NULL COMMENT '码云标识', `osc_id` varchar(100) DEFAULT NULL COMMENT '开源中国标识', PRIMARY KEY (`user_id`) USING BTREE, KEY `user_wx_openid` (`wx_openid`) USING BTREE, KEY `user_qq_openid` (`qq_openid`) USING BTREE, KEY `user_idx1_username` (`username`) USING BTREE ) ENGINE=InnoDB COMMENT='用户表'; -- ---------------------------- -- Records of sys_user -- ---------------------------- BEGIN; INSERT INTO `sys_user` VALUES (1, 'admin', '$2a$10$c/Ae0pRjJtMZg3BnvVpO.eIK6WYWVbKTzqgdy3afR7w.vd.xi3Mgy', '', '17034642999', '/admin/sys-file/s3demo/7ff4ca6b7bf446f3a5a13ac016dc21af.png', '管理员', '管理员', 'pig4cloud@qq.com', 4, ' ', 'admin', '2018-04-20 07:15:18', '2023-07-07 14:55:40', '0', '0', NULL, 'oBxPy5E-v82xWGsfzZVzkD3wEX64', NULL, 'log4j', NULL); COMMIT; -- ---------------------------- -- 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 ROW_FORMAT=DYNAMIC COMMENT='用户与岗位关联表'; -- ---------------------------- -- Records of sys_user_post -- ---------------------------- BEGIN; INSERT INTO `sys_user_post` VALUES (1, 1); COMMIT; -- ---------------------------- -- 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 COMMENT='用户角色表'; -- ---------------------------- -- Records of sys_user_role -- ---------------------------- BEGIN; INSERT INTO `sys_user_role` VALUES (1, 1); INSERT INTO `sys_user_role` VALUES (1676492190299299842, 2); COMMIT; -- ---------------------------- -- Table structure for sys_job -- ---------------------------- DROP TABLE IF EXISTS `sys_job`; CREATE TABLE `sys_job` ( `job_id` bigint NOT NULL COMMENT '任务id', `job_name` varchar(64) NOT NULL COMMENT '任务名称', `job_group` varchar(64) NOT NULL COMMENT '任务组名', `job_order` char(1) DEFAULT '1' COMMENT '组内执行顺利,值越大执行优先级越高,最大值9,最小值1', `job_type` char(1) NOT NULL DEFAULT '1' COMMENT '1、java类;2、spring bean名称;3、rest调用;4、jar调用;9其他', `execute_path` varchar(500) DEFAULT NULL COMMENT 'job_type=3时,rest调用地址,仅支持rest get协议,需要增加String返回值,0成功,1失败;job_type=4时,jar路径;其它值为空', `class_name` varchar(500) DEFAULT NULL COMMENT 'job_type=1时,类完整路径;job_type=2时,spring bean名称;其它值为空', `method_name` varchar(500) DEFAULT NULL COMMENT '任务方法', `method_params_value` varchar(2000) DEFAULT NULL COMMENT '参数值', `cron_expression` varchar(255) DEFAULT NULL COMMENT 'cron执行表达式', `misfire_policy` varchar(20) DEFAULT '3' COMMENT '错失执行策略(1错失周期立即执行 2错失周期执行一次 3下周期执行)', `job_tenant_type` char(1) DEFAULT '1' COMMENT '1、多租户任务;2、非多租户任务', `job_status` char(1) DEFAULT '0' COMMENT '状态(1、未发布;2、运行中;3、暂停;4、删除;)', `job_execute_status` char(1) DEFAULT '0' COMMENT '状态(0正常 1异常)', `create_by` varchar(64) DEFAULT NULL COMMENT '创建者', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_by` varchar(64) DEFAULT NULL COMMENT '更新者', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `start_time` timestamp NULL DEFAULT NULL COMMENT '初次执行时间', `previous_time` timestamp NULL DEFAULT NULL COMMENT '上次执行时间', `next_time` timestamp NULL DEFAULT NULL COMMENT '下次执行时间', `remark` varchar(500) DEFAULT '' COMMENT '备注信息', PRIMARY KEY (`job_id`) USING BTREE, UNIQUE KEY `job_name_group_idx` (`job_name`,`job_group`) USING BTREE ) ENGINE=InnoDB COMMENT='定时任务调度表'; -- ---------------------------- DROP TABLE IF EXISTS `sys_job_log`; CREATE TABLE `sys_job_log` ( `job_log_id` bigint NOT NULL COMMENT '任务日志ID', `job_id` bigint NOT NULL COMMENT '任务id', `job_name` varchar(64) DEFAULT NULL COMMENT '任务名称', `job_group` varchar(64) DEFAULT NULL COMMENT '任务组名', `job_order` char(1) DEFAULT NULL COMMENT '组内执行顺利,值越大执行优先级越高,最大值9,最小值1', `job_type` char(1) NOT NULL DEFAULT '1' COMMENT '1、java类;2、spring bean名称;3、rest调用;4、jar调用;9其他', `execute_path` varchar(500) DEFAULT NULL COMMENT 'job_type=3时,rest调用地址,仅支持post协议;job_type=4时,jar路径;其它值为空', `class_name` varchar(500) DEFAULT NULL COMMENT 'job_type=1时,类完整路径;job_type=2时,spring bean名称;其它值为空', `method_name` varchar(500) DEFAULT NULL COMMENT '任务方法', `method_params_value` varchar(2000) DEFAULT NULL COMMENT '参数值', `cron_expression` varchar(255) DEFAULT NULL COMMENT 'cron执行表达式', `job_message` varchar(500) DEFAULT NULL COMMENT '日志信息', `job_log_status` char(1) DEFAULT '0' COMMENT '执行状态(0正常 1失败)', `execute_time` varchar(30) DEFAULT NULL COMMENT '执行时间', `exception_info` varchar(2000) DEFAULT '' COMMENT '异常信息', `create_time` datetime DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`job_log_id`) USING BTREE ) ENGINE=InnoDB COMMENT='定时任务执行日志表'; # # Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar # # PLEASE consider using mysql with innodb tables to avoid locking issues # # In your Quartz properties file, you'll need to set # org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate # DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; DROP TABLE IF EXISTS QRTZ_LOCKS; DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS; DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; DROP TABLE IF EXISTS QRTZ_TRIGGERS; DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; DROP TABLE IF EXISTS QRTZ_CALENDARS; CREATE TABLE QRTZ_JOB_DETAILS ( SCHED_NAME VARCHAR(120) NOT NULL, JOB_NAME VARCHAR(200) NOT NULL, JOB_GROUP VARCHAR(200) NOT NULL, DESCRIPTION VARCHAR(250) NULL, JOB_CLASS_NAME VARCHAR(250) NOT NULL, IS_DURABLE VARCHAR(1) NOT NULL, IS_NONCONCURRENT VARCHAR(1) NOT NULL, IS_UPDATE_DATA VARCHAR(1) NOT NULL, REQUESTS_RECOVERY VARCHAR(1) NOT NULL, JOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) ); CREATE TABLE QRTZ_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, JOB_NAME VARCHAR(200) NOT NULL, JOB_GROUP VARCHAR(200) NOT NULL, DESCRIPTION VARCHAR(250) NULL, NEXT_FIRE_TIME BIGINT(13) NULL, PREV_FIRE_TIME BIGINT(13) NULL, PRIORITY INTEGER NULL, TRIGGER_STATE VARCHAR(16) NOT NULL, TRIGGER_TYPE VARCHAR(8) NOT NULL, START_TIME BIGINT(13) NOT NULL, END_TIME BIGINT(13) NULL, CALENDAR_NAME VARCHAR(200) NULL, MISFIRE_INSTR SMALLINT(2) NULL, JOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP) ); CREATE TABLE QRTZ_SIMPLE_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, REPEAT_COUNT BIGINT(7) NOT NULL, REPEAT_INTERVAL BIGINT(12) NOT NULL, TIMES_TRIGGERED BIGINT(10) NOT NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_CRON_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, CRON_EXPRESSION VARCHAR(200) NOT NULL, TIME_ZONE_ID VARCHAR(80), PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_SIMPROP_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, STR_PROP_1 VARCHAR(512) NULL, STR_PROP_2 VARCHAR(512) NULL, STR_PROP_3 VARCHAR(512) NULL, INT_PROP_1 INT NULL, INT_PROP_2 INT NULL, LONG_PROP_1 BIGINT NULL, LONG_PROP_2 BIGINT NULL, DEC_PROP_1 NUMERIC(13,4) NULL, DEC_PROP_2 NUMERIC(13,4) NULL, BOOL_PROP_1 VARCHAR(1) NULL, BOOL_PROP_2 VARCHAR(1) NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_BLOB_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, BLOB_DATA BLOB NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_CALENDARS ( SCHED_NAME VARCHAR(120) NOT NULL, CALENDAR_NAME VARCHAR(200) NOT NULL, CALENDAR BLOB NOT NULL, PRIMARY KEY (SCHED_NAME,CALENDAR_NAME) ); CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP) ); CREATE TABLE QRTZ_FIRED_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, ENTRY_ID VARCHAR(95) NOT NULL, TRIGGER_NAME VARCHAR(200) NOT NULL, TRIGGER_GROUP VARCHAR(200) NOT NULL, INSTANCE_NAME VARCHAR(200) NOT NULL, FIRED_TIME BIGINT(13) NOT NULL, SCHED_TIME BIGINT(13) NOT NULL, PRIORITY INTEGER NOT NULL, STATE VARCHAR(16) NOT NULL, JOB_NAME VARCHAR(200) NULL, JOB_GROUP VARCHAR(200) NULL, IS_NONCONCURRENT VARCHAR(1) NULL, REQUESTS_RECOVERY VARCHAR(1) NULL, PRIMARY KEY (SCHED_NAME,ENTRY_ID) ); CREATE TABLE QRTZ_SCHEDULER_STATE ( SCHED_NAME VARCHAR(120) NOT NULL, INSTANCE_NAME VARCHAR(200) NOT NULL, LAST_CHECKIN_TIME BIGINT(13) NOT NULL, CHECKIN_INTERVAL BIGINT(13) NOT NULL, PRIMARY KEY (SCHED_NAME,INSTANCE_NAME) ); CREATE TABLE QRTZ_LOCKS ( SCHED_NAME VARCHAR(120) NOT NULL, LOCK_NAME VARCHAR(40) NOT NULL, PRIMARY KEY (SCHED_NAME,LOCK_NAME) ); -- ---------------------------- -- Table structure for gen_datasource_conf -- ---------------------------- DROP TABLE IF EXISTS `gen_datasource_conf`; CREATE TABLE `gen_datasource_conf` ( `id` bigint NOT NULL COMMENT '主键', `name` varchar(64) DEFAULT NULL COMMENT '别名', `url` varchar(255) DEFAULT NULL COMMENT 'jdbcurl', `username` varchar(64) DEFAULT NULL COMMENT '用户名', `password` varchar(64) DEFAULT NULL COMMENT '密码', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新', `del_flag` char(1) DEFAULT '0' COMMENT '删除标记', `ds_type` varchar(64) DEFAULT NULL COMMENT '数据库类型', `conf_type` char(1) DEFAULT NULL COMMENT '配置类型', `ds_name` varchar(64) DEFAULT NULL COMMENT '数据库名称', `instance` varchar(64) DEFAULT NULL COMMENT '实例', `port` int DEFAULT NULL COMMENT '端口', `host` varchar(128) DEFAULT NULL COMMENT '主机', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB COMMENT='数据源表'; -- ---------------------------- -- Records of gen_datasource_conf -- ---------------------------- BEGIN; COMMIT; -- ---------------------------- -- Table structure for gen_field_type -- ---------------------------- DROP TABLE IF EXISTS `gen_field_type`; CREATE TABLE `gen_field_type` ( `id` bigint NOT NULL COMMENT '主键', `column_type` varchar(200) DEFAULT NULL COMMENT '字段类型', `attr_type` varchar(200) DEFAULT NULL COMMENT '属性类型', `package_name` varchar(200) DEFAULT NULL COMMENT '属性包名', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `create_by` varchar(64) DEFAULT NULL COMMENT '创建人', `update_time` datetime DEFAULT NULL COMMENT '修改时间', `update_by` varchar(64) DEFAULT NULL COMMENT '修改人', `del_flag` char(1) DEFAULT '0' COMMENT '删除标记', PRIMARY KEY (`id`), UNIQUE KEY `column_type` (`column_type`) ) ENGINE=InnoDB AUTO_INCREMENT=1634915190321451010 COMMENT='字段类型管理'; -- ---------------------------- -- Records of gen_field_type -- ---------------------------- BEGIN; INSERT INTO `gen_field_type` VALUES (1, 'datetime', 'LocalDateTime', 'java.time.LocalDateTime', '2023-02-06 08:45:10', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (2, 'date', 'LocalDate', 'java.time.LocalDate', '2023-02-06 08:45:10', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (3, 'tinyint', 'Integer', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (4, 'smallint', 'Integer', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (5, 'mediumint', 'Integer', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (6, 'int', 'Integer', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (7, 'integer', 'Integer', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (8, 'bigint', 'Long', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (9, 'float', 'Float', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (10, 'double', 'Double', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (11, 'decimal', 'BigDecimal', 'java.math.BigDecimal', '2023-02-06 08:45:11', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (12, 'bit', 'Boolean', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (13, 'char', 'String', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (14, 'varchar', 'String', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (15, 'tinytext', 'String', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (16, 'text', 'String', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (17, 'mediumtext', 'String', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (18, 'longtext', 'String', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (19, 'timestamp', 'LocalDateTime', 'java.time.LocalDateTime', '2023-02-06 08:45:11', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (20, 'NUMBER', 'Integer', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (21, 'BINARY_INTEGER', 'Integer', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (22, 'BINARY_FLOAT', 'Float', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (23, 'BINARY_DOUBLE', 'Double', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (24, 'VARCHAR2', 'String', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (25, 'NVARCHAR', 'String', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (26, 'NVARCHAR2', 'String', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (27, 'CLOB', 'String', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (28, 'int8', 'Long', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (29, 'int4', 'Integer', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (30, 'int2', 'Integer', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (31, 'numeric', 'BigDecimal', 'java.math.BigDecimal', '2023-02-06 08:45:12', NULL, NULL, NULL, '0'); INSERT INTO `gen_field_type` VALUES (32, 'json', 'String', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0'); COMMIT; -- ---------------------------- -- Table structure for gen_group -- ---------------------------- DROP TABLE IF EXISTS `gen_group`; CREATE TABLE `gen_group` ( `id` bigint NOT NULL, `group_name` varchar(255) DEFAULT NULL COMMENT '分组名称', `group_desc` varchar(255) DEFAULT NULL COMMENT '分组描述', `create_by` varchar(64) DEFAULT NULL COMMENT '创建人', `update_by` varchar(64) DEFAULT NULL COMMENT '修改人', `create_time` datetime DEFAULT NULL COMMENT '创建人', `update_time` datetime DEFAULT NULL COMMENT '修改人', `del_flag` char(1) DEFAULT '0' COMMENT '删除标记', PRIMARY KEY (`id`) ) ENGINE=InnoDB COMMENT='模板分组'; -- ---------------------------- -- Table structure for gen_table -- ---------------------------- DROP TABLE IF EXISTS `gen_table`; CREATE TABLE `gen_table` ( `id` bigint NOT NULL, `table_name` varchar(200) DEFAULT NULL COMMENT '表名', `class_name` varchar(200) DEFAULT NULL COMMENT '类名', `db_type` varchar(200) DEFAULT NULL COMMENT '数据库类型', `table_comment` varchar(200) DEFAULT NULL COMMENT '说明', `author` varchar(200) DEFAULT NULL COMMENT '作者', `email` varchar(200) DEFAULT NULL COMMENT '邮箱', `package_name` varchar(200) DEFAULT NULL COMMENT '项目包名', `version` varchar(200) DEFAULT NULL COMMENT '项目版本号', `i18n` char(1) DEFAULT '0' COMMENT '是否生成带有i18n 0 不带有 1带有', `style` bigint DEFAULT NULL COMMENT '代码风格', `child_table_name` varchar(200) DEFAULT NULL COMMENT '子表名称', `main_field` varchar(200) DEFAULT NULL COMMENT '主表关联键', `child_field` varchar(200) DEFAULT NULL COMMENT '子表关联键', `generator_type` char(1) DEFAULT '0' COMMENT '生成方式 0:zip压缩包 1:自定义目录', `backend_path` varchar(500) DEFAULT NULL COMMENT '后端生成路径', `frontend_path` varchar(500) DEFAULT NULL COMMENT '前端生成路径', `module_name` varchar(200) DEFAULT NULL COMMENT '模块名', `function_name` varchar(200) DEFAULT NULL COMMENT '功能名', `form_layout` tinyint DEFAULT NULL COMMENT '表单布局 1:一列 2:两列', `ds_name` varchar(200) DEFAULT NULL COMMENT '数据源ID', `baseclass_id` bigint DEFAULT NULL COMMENT '基类ID', `create_time` datetime DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`id`), UNIQUE KEY `table_name` (`table_name`,`ds_name`) USING BTREE ) ENGINE=InnoDB COMMENT='代码生成表'; -- ---------------------------- -- Records of gen_table -- ---------------------------- BEGIN; COMMIT; -- ---------------------------- -- Table structure for gen_table_column -- ---------------------------- DROP TABLE IF EXISTS `gen_table_column`; CREATE TABLE `gen_table_column` ( `id` bigint NOT NULL, `ds_name` varchar(200) DEFAULT NULL COMMENT '数据源名称', `table_name` varchar(200) DEFAULT NULL COMMENT '表名称', `field_name` varchar(200) DEFAULT NULL COMMENT '字段名称', `field_type` varchar(200) DEFAULT NULL COMMENT '字段类型', `field_comment` varchar(200) DEFAULT NULL COMMENT '字段说明', `attr_name` varchar(200) DEFAULT NULL COMMENT '属性名', `attr_type` varchar(200) DEFAULT NULL COMMENT '属性类型', `package_name` varchar(200) DEFAULT NULL COMMENT '属性包名', `sort` int DEFAULT NULL COMMENT '排序', `auto_fill` varchar(20) DEFAULT NULL COMMENT '自动填充 DEFAULT、INSERT、UPDATE、INSERT_UPDATE', `primary_pk` char(1) DEFAULT '0' COMMENT '主键 0:否 1:是', `base_field` char(1) DEFAULT '0' COMMENT '基类字段 0:否 1:是', `form_item` char(1) DEFAULT '0' COMMENT '表单项 0:否 1:是', `form_required` char(1) DEFAULT '0' COMMENT '表单必填 0:否 1:是', `form_type` varchar(200) DEFAULT NULL COMMENT '表单类型', `form_validator` varchar(200) DEFAULT NULL COMMENT '表单效验', `grid_item` char(1) DEFAULT '0' COMMENT '列表项 0:否 1:是', `grid_sort` char(1) DEFAULT '0' COMMENT '列表排序 0:否 1:是', `query_item` char(1) DEFAULT '0' COMMENT '查询项 0:否 1:是', `query_type` varchar(200) DEFAULT NULL COMMENT '查询方式', `query_form_type` varchar(200) DEFAULT NULL COMMENT '查询表单类型', `field_dict` varchar(200) DEFAULT NULL COMMENT '字典类型', PRIMARY KEY (`id`) ) ENGINE=InnoDB COMMENT='代码生成表字段'; -- ---------------------------- -- Records of gen_table_column -- ---------------------------- BEGIN; COMMIT; -- ---------------------------- -- Table structure for gen_template -- ---------------------------- DROP TABLE IF EXISTS `gen_template`; CREATE TABLE `gen_template` ( `id` bigint NOT NULL COMMENT '主键', `template_name` varchar(255) NOT NULL COMMENT '模板名称', `generator_path` varchar(255) NOT NULL COMMENT '模板路径', `template_desc` varchar(255) NOT NULL COMMENT '模板描述', `template_code` text NOT NULL COMMENT '模板代码', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新', `del_flag` char(1) NOT NULL DEFAULT '0' COMMENT '删除标记', `create_by` varchar(64) DEFAULT NULL COMMENT '创建人', `update_by` varchar(64) DEFAULT NULL COMMENT '修改人', PRIMARY KEY (`id`) ) ENGINE=InnoDB COMMENT='模板'; -- ---------------------------- -- Table structure for gen_template_group -- ---------------------------- DROP TABLE IF EXISTS `gen_template_group`; CREATE TABLE `gen_template_group` ( `group_id` bigint NOT NULL COMMENT '分组id', `template_id` bigint NOT NULL COMMENT '模板id', PRIMARY KEY (`group_id`,`template_id`) ) ENGINE=InnoDB COMMENT='模板分组关联表'; SET FOREIGN_KEY_CHECKS = 1; ================================================ FILE: db/pig_config.sql ================================================ DROP DATABASE IF EXISTS `pig_config`; CREATE DATABASE `pig_config` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; USE pig_config; SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for config_info -- ---------------------------- DROP TABLE IF EXISTS `config_info`; CREATE TABLE `config_info` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id', `data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id', `group_id` varchar(128) COLLATE utf8_bin DEFAULT NULL COMMENT 'group_id', `content` longtext COLLATE utf8_bin NOT NULL COMMENT 'content', `md5` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'md5', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', `src_user` text COLLATE utf8_bin COMMENT 'source user', `src_ip` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT 'source ip', `app_name` varchar(128) COLLATE utf8_bin DEFAULT NULL COMMENT 'app_name', `tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT '租户字段', `c_desc` varchar(256) COLLATE utf8_bin DEFAULT NULL COMMENT 'configuration description', `c_use` varchar(64) COLLATE utf8_bin DEFAULT NULL COMMENT 'configuration usage', `effect` varchar(64) COLLATE utf8_bin DEFAULT NULL COMMENT '配置生效的描述', `type` varchar(64) COLLATE utf8_bin DEFAULT NULL COMMENT '配置的类型', `c_schema` text COLLATE utf8_bin COMMENT '配置的模式', `encrypted_data_key` varchar(1024) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT '密钥', PRIMARY KEY (`id`), UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`) ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info'; -- ---------------------------- -- Records of his_config_info -- ---------------------------- BEGIN; INSERT INTO `config_info` (`id`, `data_id`, `group_id`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `app_name`, `tenant_id`, `c_desc`, `c_use`, `effect`, `type`, `c_schema`, `encrypted_data_key`) VALUES (1, 'application-dev.yml', 'DEFAULT_GROUP', '# 配置文件加密根密码\njasypt:\n encryptor:\n password: pig\n algorithm: PBEWithMD5AndDES\n iv-generator-classname: org.jasypt.iv.NoIvGenerator\n \n# Spring 相关\nspring:\n cache:\n type: redis\n data:\n redis:\n host: ${REDIS_HOST:127.0.0.1}\n password: ${REDIS_PASSWORD:}\n port: ${REDIS_PORT:6379}\n database: ${REDIS_DATABASE:0}\n cloud:\n sentinel:\n eager: true\n transport:\n dashboard: pig-sentinel:5003\n openfeign:\n sentinel:\n enabled: true\n okhttp:\n enabled: true\n httpclient:\n enabled: false\n compression:\n request:\n enabled: true\n response:\n enabled: true\n\n# 暴露监控端点\nmanagement:\n endpoints:\n web:\n exposure:\n include: \"*\" \n endpoint:\n health:\n show-details: ALWAYS\n\n# mybaits-plus配置\nmybatis-plus:\n mapper-locations: classpath:/mapper/*Mapper.xml\n global-config:\n banner: false\n db-config:\n id-type: auto\n table-underline: true\n logic-delete-value: 1\n logic-not-delete-value: 0\n type-handlers-package: com.pig4cloud.pig.common.mybatis.handler\n configuration:\n map-underscore-to-camel-case: true\n shrink-whitespaces-in-sql: true\n\n# 短信插件配置:https://www.yuque.com/vxixfq/pig/zw8udk\nsms:\n is-print: false # 是否打印日志\n config-type: yaml # 配置类型,yaml', '670b60f71ed234ee2c2d363721a1e2c9', '2025-05-16 12:48:39', '2025-10-29 09:01:23', 'nacos', '10.25.25.1', '', 'public', '', NULL, NULL, 'yaml', NULL, ''); INSERT INTO `config_info` (`id`, `data_id`, `group_id`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `app_name`, `tenant_id`, `c_desc`, `c_use`, `effect`, `type`, `c_schema`, `encrypted_data_key`) VALUES (2, 'pig-auth-dev.yml', 'DEFAULT_GROUP', '# 数据源\nspring:\n freemarker:\n allow-request-override: false\n allow-session-override: false\n cache: true\n charset: UTF-8\n check-template-location: true\n content-type: text/html\n enabled: true\n request-context-attribute: request\n expose-request-attributes: false\n expose-session-attributes: false\n expose-spring-macro-helpers: true\n prefer-file-system-access: true\n suffix: .ftl\n template-loader-path: classpath:/templates/\n\n\nsecurity:\n encode-key: \'thanks,pig4cloud\'\n ignore-clients:\n - test\n - client\n - open\n - app', 'b4a660ece61e8180b4940a0770eddfee', '2025-01-30 16:50:04', '2025-01-30 16:50:04', 'nacos', '127.0.0.1', '', 'public', NULL, NULL, NULL, 'yaml', NULL, ''); INSERT INTO `config_info` (`id`, `data_id`, `group_id`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `app_name`, `tenant_id`, `c_desc`, `c_use`, `effect`, `type`, `c_schema`, `encrypted_data_key`) VALUES (3, 'pig-codegen-dev.yml', 'DEFAULT_GROUP', '# 数据源配置\nspring:\n datasource:\n type: com.zaxxer.hikari.HikariDataSource\n driver-class-name: com.mysql.cj.jdbc.Driver\n username: ${MYSQL_USERNAME:root}\n password: ${MYSQL_PASSWORD:root}\n url: jdbc:mysql://${MYSQL_HOST:127.0.0.1}:${MYSQL_PORT:3306}/${MYSQL_DB:pig}?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true\n resources:\n static-locations: classpath:/static/,classpath:/views/\n', 'a1e1ae7127517eae96a2df8b15d94fe3', '2025-10-29 11:39:40', '2025-10-29 11:39:40', 'nacos', '10.25.25.2', '', 'public', '', NULL, NULL, 'yaml', NULL, ''); INSERT INTO `config_info` (`id`, `data_id`, `group_id`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `app_name`, `tenant_id`, `c_desc`, `c_use`, `effect`, `type`, `c_schema`, `encrypted_data_key`) VALUES (4, 'pig-gateway-dev.yml', 'DEFAULT_GROUP', 'spring:\n cloud:\n gateway:\n server:\n webflux:\n routes:\n # 认证中心\n - id: pig-auth\n uri: lb://pig-auth\n predicates:\n - Path=/auth/**\n #UPMS 模块\n - id: pig-upms-biz\n uri: lb://pig-upms-biz\n predicates:\n - Path=/admin/**\n filters:\n # 限流配置\n - name: RequestRateLimiter\n args:\n key-resolver: \'#{@remoteAddrKeyResolver}\'\n redis-rate-limiter.replenishRate: 100\n redis-rate-limiter.burstCapacity: 200\n # 代码生成模块\n - id: pig-codegen\n uri: lb://pig-codegen\n predicates:\n - Path=/gen/**\n # 代码生成模块\n - id: pig-quartz\n uri: lb://pig-quartz\n predicates:\n - Path=/job/**\n # 固定路由转发配置 无修改\n - id: openapi\n uri: lb://pig-gateway\n predicates:\n - Path=/v3/api-docs/**\n filters:\n - RewritePath=/v3/api-docs/(?.*), /$\\{path}/$\\{path}/v3/api-docs', '53ace4035d810f07e3767d94e1e68379', '2025-01-30 16:50:04', '2025-05-30 08:36:27', 'nacos_namespace_migrate', '0:0:0:0:0:0:0:1', '', 'public', '', NULL, NULL, 'yaml', NULL, ''); INSERT INTO `config_info` (`id`, `data_id`, `group_id`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `app_name`, `tenant_id`, `c_desc`, `c_use`, `effect`, `type`, `c_schema`, `encrypted_data_key`) VALUES (5, 'pig-monitor-dev.yml', 'DEFAULT_GROUP', 'spring:\n autoconfigure:\n exclude: com.pig4cloud.pig.common.core.config.JacksonConfiguration\n # 安全配置\n security:\n user:\n name: ENC(8Hk2ILNJM8UTOuW/Xi75qg==) # pig\n password: ENC(o6cuPFfUevmTbkmBnE67Ow====) # pig\n', '650bdfa15f60f3faa84dfe6e6878b8cf', '2025-01-30 16:50:04', '2025-01-30 16:50:04', 'nacos', '127.0.0.1', '', 'public', NULL, NULL, NULL, 'yaml', NULL, ''); INSERT INTO `config_info` (`id`, `data_id`, `group_id`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `app_name`, `tenant_id`, `c_desc`, `c_use`, `effect`, `type`, `c_schema`, `encrypted_data_key`) VALUES (6, 'pig-upms-biz-dev.yml', 'DEFAULT_GROUP', '# 数据源\nspring:\n datasource:\n type: com.zaxxer.hikari.HikariDataSource\n driver-class-name: com.mysql.cj.jdbc.Driver\n username: ${MYSQL_USERNAME:root}\n password: ${MYSQL_PASSWORD:root}\n url: jdbc:mysql://${MYSQL_HOST:127.0.0.1}:${MYSQL_PORT:3306}/${MYSQL_DB:pig}?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true\n\n# 文件上传相关 支持阿里云、华为云、腾讯、minio\nfile:\n bucketName: s3demo \n local:\n enable: true\n base-path: /Users/lengleng/Downloads/img', '80cf3a9b7b490e32b03550c429dea33e', '2025-10-29 11:39:40', '2025-10-29 11:39:40', 'nacos', '10.25.25.2', '', 'public', '', NULL, NULL, 'yaml', NULL, ''); INSERT INTO `config_info` (`id`, `data_id`, `group_id`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `app_name`, `tenant_id`, `c_desc`, `c_use`, `effect`, `type`, `c_schema`, `encrypted_data_key`) VALUES (7, 'pig-quartz-dev.yml', 'DEFAULT_GROUP', 'spring:\n datasource:\n type: com.zaxxer.hikari.HikariDataSource\n driver-class-name: com.mysql.cj.jdbc.Driver\n username: ${MYSQL_USERNAME:root}\n password: ${MYSQL_PASSWORD:root}\n url: jdbc:mysql://${MYSQL_HOST:127.0.0.1}:${MYSQL_PORT:3306}/${MYSQL_DB:pig}?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true\n', 'e0c9ce980fd14fd28f955852061970ca', '2025-10-29 11:39:40', '2025-10-29 11:39:40', 'nacos', '10.25.25.2', '', 'public', '', NULL, NULL, 'yaml', NULL, ''); COMMIT; -- ---------------------------- -- Table structure for config_info_beta -- ---------------------------- DROP TABLE IF EXISTS `config_info_beta`; CREATE TABLE `config_info_beta` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id', `data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id', `group_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'group_id', `app_name` varchar(128) COLLATE utf8_bin DEFAULT NULL COMMENT 'app_name', `content` longtext COLLATE utf8_bin NOT NULL COMMENT 'content', `beta_ips` varchar(1024) COLLATE utf8_bin DEFAULT NULL COMMENT 'betaIps', `md5` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'md5', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', `src_user` text COLLATE utf8_bin COMMENT 'source user', `src_ip` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT 'source ip', `tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT '租户字段', `encrypted_data_key` varchar(1024) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT '密钥', PRIMARY KEY (`id`), UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta'; -- ---------------------------- -- Records of config_info_beta -- ---------------------------- BEGIN; COMMIT; -- ---------------------------- -- Table structure for config_info_gray -- ---------------------------- DROP TABLE IF EXISTS `config_info_gray`; CREATE TABLE `config_info_gray` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', `data_id` varchar(255) NOT NULL COMMENT 'data_id', `group_id` varchar(128) NOT NULL COMMENT 'group_id', `content` longtext NOT NULL COMMENT 'content', `md5` varchar(32) DEFAULT NULL COMMENT 'md5', `src_user` text COMMENT 'src_user', `src_ip` varchar(100) DEFAULT NULL COMMENT 'src_ip', `gmt_create` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'gmt_create', `gmt_modified` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'gmt_modified', `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name', `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id', `gray_name` varchar(128) NOT NULL COMMENT 'gray_name', `gray_rule` text NOT NULL COMMENT 'gray_rule', `encrypted_data_key` varchar(256) NOT NULL DEFAULT '' COMMENT 'encrypted_data_key', PRIMARY KEY (`id`), UNIQUE KEY `uk_configinfogray_datagrouptenantgray` (`data_id`,`group_id`,`tenant_id`,`gray_name`), KEY `idx_dataid_gmt_modified` (`data_id`,`gmt_modified`), KEY `idx_gmt_modified` (`gmt_modified`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='config_info_gray'; -- ---------------------------- -- Records of config_info_gray -- ---------------------------- BEGIN; COMMIT; -- ---------------------------- -- Table structure for config_info_tag -- ---------------------------- DROP TABLE IF EXISTS `config_info_tag`; CREATE TABLE `config_info_tag` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id', `data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id', `group_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'group_id', `tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT 'tenant_id', `tag_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'tag_id', `app_name` varchar(128) COLLATE utf8_bin DEFAULT NULL COMMENT 'app_name', `content` longtext COLLATE utf8_bin NOT NULL COMMENT 'content', `md5` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'md5', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', `src_user` text COLLATE utf8_bin COMMENT 'source user', `src_ip` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT 'source ip', PRIMARY KEY (`id`), UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag'; -- ---------------------------- -- Records of config_info_tag -- ---------------------------- BEGIN; COMMIT; -- ---------------------------- -- Table structure for config_tags_relation -- ---------------------------- DROP TABLE IF EXISTS `config_tags_relation`; CREATE TABLE `config_tags_relation` ( `id` bigint NOT NULL COMMENT 'id', `tag_name` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'tag_name', `tag_type` varchar(64) COLLATE utf8_bin DEFAULT NULL COMMENT 'tag_type', `data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id', `group_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'group_id', `tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT 'tenant_id', `nid` bigint NOT NULL AUTO_INCREMENT COMMENT 'nid, 自增长标识', PRIMARY KEY (`nid`), UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`), KEY `idx_tenant_id` (`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation'; -- ---------------------------- -- Records of config_tags_relation -- ---------------------------- BEGIN; COMMIT; -- ---------------------------- -- Table structure for group_capacity -- ---------------------------- DROP TABLE IF EXISTS `group_capacity`; CREATE TABLE `group_capacity` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', `group_id` varchar(128) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群', `quota` int unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值', `usage` int unsigned NOT NULL DEFAULT '0' COMMENT '使用量', `max_size` int unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值', `max_aggr_count` int unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值', `max_aggr_size` int unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值', `max_history_count` int unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_group_id` (`group_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表'; -- ---------------------------- -- Records of group_capacity -- ---------------------------- BEGIN; COMMIT; -- ---------------------------- -- Table structure for his_config_info -- ---------------------------- DROP TABLE IF EXISTS `his_config_info`; CREATE TABLE `his_config_info` ( `id` bigint unsigned NOT NULL COMMENT 'id', `nid` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'nid, 自增标识', `data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id', `group_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'group_id', `app_name` varchar(128) COLLATE utf8_bin DEFAULT NULL COMMENT 'app_name', `content` longtext COLLATE utf8_bin NOT NULL COMMENT 'content', `md5` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'md5', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', `src_user` text COLLATE utf8_bin COMMENT 'source user', `src_ip` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT 'source ip', `op_type` char(10) COLLATE utf8_bin DEFAULT NULL COMMENT 'operation type', `tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT '租户字段', `encrypted_data_key` varchar(1024) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT '密钥', `publish_type` varchar(50) COLLATE utf8_bin DEFAULT 'formal' COMMENT 'publish type gray or formal', `gray_name` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT 'gray name', `ext_info` longtext COLLATE utf8_bin COMMENT 'ext info', PRIMARY KEY (`nid`), KEY `idx_gmt_create` (`gmt_create`), KEY `idx_gmt_modified` (`gmt_modified`), KEY `idx_did` (`data_id`) ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造'; -- ---------------------------- -- Table structure for permissions -- ---------------------------- DROP TABLE IF EXISTS `permissions`; CREATE TABLE `permissions` ( `role` varchar(50) NOT NULL COMMENT 'role', `resource` varchar(128) NOT NULL COMMENT 'resource', `action` varchar(8) NOT NULL COMMENT 'action', UNIQUE KEY `uk_role_permission` (`role`,`resource`,`action`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of permissions -- ---------------------------- BEGIN; COMMIT; -- ---------------------------- -- Table structure for roles -- ---------------------------- DROP TABLE IF EXISTS `roles`; CREATE TABLE `roles` ( `username` varchar(50) NOT NULL COMMENT 'username', `role` varchar(50) NOT NULL COMMENT 'role', UNIQUE KEY `idx_user_role` (`username`,`role`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of roles -- ---------------------------- BEGIN; INSERT INTO `roles` (`username`, `role`) VALUES ('nacos', 'ROLE_ADMIN'); COMMIT; -- ---------------------------- -- Table structure for tenant_capacity -- ---------------------------- DROP TABLE IF EXISTS `tenant_capacity`; CREATE TABLE `tenant_capacity` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', `tenant_id` varchar(128) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT 'Tenant ID', `quota` int unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值', `usage` int unsigned NOT NULL DEFAULT '0' COMMENT '使用量', `max_size` int unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值', `max_aggr_count` int unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数', `max_aggr_size` int unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值', `max_history_count` int unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_tenant_id` (`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表'; -- ---------------------------- -- Records of tenant_capacity -- ---------------------------- BEGIN; COMMIT; -- ---------------------------- -- Table structure for tenant_info -- ---------------------------- DROP TABLE IF EXISTS `tenant_info`; CREATE TABLE `tenant_info` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id', `kp` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'kp', `tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT 'tenant_id', `tenant_name` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT 'tenant_name', `tenant_desc` varchar(256) COLLATE utf8_bin DEFAULT NULL COMMENT 'tenant_desc', `create_source` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'create_source', `gmt_create` bigint NOT NULL COMMENT '创建时间', `gmt_modified` bigint NOT NULL COMMENT '修改时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`), KEY `idx_tenant_id` (`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info'; -- ---------------------------- -- Records of tenant_info -- ---------------------------- BEGIN; COMMIT; -- ---------------------------- -- Table structure for users -- ---------------------------- DROP TABLE IF EXISTS `users`; CREATE TABLE `users` ( `username` varchar(50) NOT NULL COMMENT 'username', `password` varchar(500) NOT NULL COMMENT 'password', `enabled` tinyint(1) NOT NULL COMMENT 'enabled', PRIMARY KEY (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of users -- ---------------------------- BEGIN; INSERT INTO `users` (`username`, `password`, `enabled`) VALUES ('nacos', '$2a$10$W6PKgRTzXUp6R/NY853Kn.nRaIcX3whIMTZ/WWkNqo2MTOeSBjKJq', 1); COMMIT; SET FOREIGN_KEY_CHECKS = 1; ================================================ FILE: docker-compose.yml ================================================ services: pig-mysql: build: context: ./db environment: MYSQL_ROOT_HOST: "%" MYSQL_ROOT_PASSWORD: root restart: always container_name: pig-mysql image: pig-mysql ports: - 33306:3306 networks: - spring_cloud_default pig-redis: image: registry.cn-hangzhou.aliyuncs.com/dockerhub_mirror/redis ports: - 36379:6379 restart: always container_name: pig-redis hostname: pig-redis networks: - spring_cloud_default pig-register: build: context: ./pig-register restart: always ports: - 8848:8848 - 9848:9848 - 8080:8080 environment: MYSQL_HOST: pig-mysql REDIS_HOST: pig-redis container_name: pig-register hostname: pig-register image: pig-register networks: - spring_cloud_default pig-gateway: build: context: ./pig-gateway restart: always ports: - 9999:9999 container_name: pig-gateway hostname: pig-gateway image: pig-gateway environment: REDIS_HOST: pig-redis NACOS_HOST: pig-register networks: - spring_cloud_default pig-auth: build: context: ./pig-auth restart: always container_name: pig-auth hostname: pig-auth image: pig-auth environment: REDIS_HOST: pig-redis NACOS_HOST: pig-register networks: - spring_cloud_default pig-upms: build: context: ./pig-upms/pig-upms-biz restart: always container_name: pig-upms hostname: pig-upms image: pig-upms environment: MYSQL_HOST: pig-mysql REDIS_HOST: pig-redis NACOS_HOST: pig-register networks: - spring_cloud_default pig-monitor: build: context: ./pig-visual/pig-monitor restart: always ports: - 5001:5001 container_name: pig-monitor hostname: pig-monitor image: pig-monitor environment: NACOS_HOST: pig-register networks: - spring_cloud_default pig-codegen: build: context: ./pig-visual/pig-codegen restart: always container_name: pig-codegen hostname: pig-codegen image: pig-codegen environment: MYSQL_HOST: pig-mysql REDIS_HOST: pig-redis NACOS_HOST: pig-register networks: - spring_cloud_default pig-quartz: build: context: ./pig-visual/pig-quartz restart: always image: pig-quartz container_name: pig-quartz environment: MYSQL_HOST: pig-mysql REDIS_HOST: pig-redis NACOS_HOST: pig-register networks: - spring_cloud_default networks: spring_cloud_default: name: spring_cloud_default driver: bridge ================================================ FILE: pig-auth/Dockerfile ================================================ FROM registry.cn-hangzhou.aliyuncs.com/dockerhub_mirror/java:21-anolis WORKDIR /pig-auth ARG JAR_FILE=target/pig-auth.jar COPY ${JAR_FILE} app.jar EXPOSE 3000 ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms128m -Xmx256m -Djava.security.egd=file:/dev/./urandom" CMD sleep 60; java $JAVA_OPTS -jar app.jar ================================================ FILE: pig-auth/pom.xml ================================================ 4.0.0 com.pig4cloud pig ${revision} pig-auth jar pig 认证授权中心,基于 spring security oAuth2 com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config com.pig4cloud pig-common-feign com.pig4cloud pig-upms-api com.pig4cloud pig-common-security org.springframework.boot spring-boot-starter-security org.springframework.boot spring-boot-starter-freemarker org.springframework.boot spring-boot-starter-undertow com.pig4cloud pig-common-log com.pig4cloud.plugin captcha-core cn.hutool hutool-crypto boot cloud true org.springframework.boot spring-boot-maven-plugin io.fabric8 docker-maven-plugin ================================================ FILE: pig-auth/src/main/java/com/pig4cloud/pig/auth/PigAuthApplication.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.auth; import com.pig4cloud.pig.common.feign.annotation.EnablePigFeignClients; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; /** * 认证授权中心应用启动类 * * @author lengleng * @date 2025/05/30 */ @EnablePigFeignClients @EnableDiscoveryClient @SpringBootApplication public class PigAuthApplication { public static void main(String[] args) { SpringApplication.run(PigAuthApplication.class, args); } } ================================================ FILE: pig-auth/src/main/java/com/pig4cloud/pig/auth/config/AuthorizationServerConfiguration.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.auth.config; import com.pig4cloud.pig.auth.support.CustomeOAuth2AccessTokenGenerator; import com.pig4cloud.pig.auth.support.core.CustomeOAuth2TokenCustomizer; import com.pig4cloud.pig.auth.support.core.FormIdentityLoginConfigurer; import com.pig4cloud.pig.auth.support.core.PigDaoAuthenticationProvider; import com.pig4cloud.pig.auth.support.filter.PasswordDecoderFilter; import com.pig4cloud.pig.auth.support.filter.ValidateCodeFilter; import com.pig4cloud.pig.auth.support.handler.PigAuthenticationFailureEventHandler; import com.pig4cloud.pig.auth.support.handler.PigAuthenticationSuccessEventHandler; import com.pig4cloud.pig.auth.support.password.OAuth2ResourceOwnerPasswordAuthenticationConverter; import com.pig4cloud.pig.auth.support.password.OAuth2ResourceOwnerPasswordAuthenticationProvider; import com.pig4cloud.pig.auth.support.sms.OAuth2ResourceOwnerSmsAuthenticationConverter; import com.pig4cloud.pig.auth.support.sms.OAuth2ResourceOwnerSmsAuthenticationProvider; import com.pig4cloud.pig.common.core.constant.SecurityConstants; import com.pig4cloud.pig.common.security.component.PigBootCorsProperties; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator; import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ClientCredentialsAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2RefreshTokenAuthenticationConverter; import org.springframework.security.web.DefaultSecurityFilterChain; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.authentication.DelegatingAuthenticationConverter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import java.util.Arrays; /** * 认证服务器配置类 * * @author lengleng * @date 2025/05/30 */ @Configuration @RequiredArgsConstructor public class AuthorizationServerConfiguration { private final OAuth2AuthorizationService authorizationService; private final PasswordDecoderFilter passwordDecoderFilter; private final ValidateCodeFilter validateCodeFilter; private final PigBootCorsProperties pigBootCorsProperties; /** * Authorization Server 配置,仅对 /oauth2/** 的请求有效 * @param http http * @return {@link SecurityFilterChain } * @throws Exception 异常 */ @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SecurityFilterChain authorizationServer(HttpSecurity http) throws Exception { // 配置授权服务器的安全策略,只有/oauth2/**的请求才会走如下的配置 http.securityMatcher("/oauth2/**"); OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer(); // 增加验证码过滤器 http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class); // 增加密码解密过滤器 http.addFilterBefore(passwordDecoderFilter, UsernamePasswordAuthenticationFilter.class); http.with(authorizationServerConfigurer.tokenEndpoint((tokenEndpoint) -> {// 个性化认证授权端点 tokenEndpoint.accessTokenRequestConverter(accessTokenRequestConverter()) // 注入自定义的授权认证Converter .accessTokenResponseHandler(new PigAuthenticationSuccessEventHandler()) // 登录成功处理器 .errorResponseHandler(new PigAuthenticationFailureEventHandler());// 登录失败处理器 }).clientAuthentication(oAuth2ClientAuthenticationConfigurer -> // 个性化客户端认证 oAuth2ClientAuthenticationConfigurer.errorResponseHandler(new PigAuthenticationFailureEventHandler()))// 处理客户端认证异常 .authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint// 授权码端点个性化confirm页面 .consentPage(SecurityConstants.CUSTOM_CONSENT_PAGE_URI)), Customizer.withDefaults()) .authorizeHttpRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated()); // 设置 Token 存储的策略 http.with(authorizationServerConfigurer.authorizationService(authorizationService)// redis存储token的实现 .authorizationServerSettings( AuthorizationServerSettings.builder().issuer(SecurityConstants.PROJECT_LICENSE).build()), Customizer.withDefaults()); // 设置授权码模式登录页面 http.with(new FormIdentityLoginConfigurer(), Customizer.withDefaults()); // 配置 CORS 跨域资源共享 if (Boolean.TRUE.equals(pigBootCorsProperties.getEnabled())) { http.cors(cors -> cors.configurationSource(corsConfigurationSource())); } DefaultSecurityFilterChain securityFilterChain = http.build(); // 注入自定义授权模式实现 addCustomOAuth2GrantAuthenticationProvider(http); return securityFilterChain; } /** * 令牌生成规则实现
* client:username:uuid * @return OAuth2TokenGenerator */ @Bean public OAuth2TokenGenerator oAuth2TokenGenerator() { CustomeOAuth2AccessTokenGenerator accessTokenGenerator = new CustomeOAuth2AccessTokenGenerator(); // 注入Token 增加关联用户信息 accessTokenGenerator.setAccessTokenCustomizer(new CustomeOAuth2TokenCustomizer()); return new DelegatingOAuth2TokenGenerator(accessTokenGenerator, new OAuth2RefreshTokenGenerator()); } /** * request -> xToken 注入请求转换器 * @return DelegatingAuthenticationConverter */ @Bean public AuthenticationConverter accessTokenRequestConverter() { return new DelegatingAuthenticationConverter(Arrays.asList( new OAuth2ResourceOwnerPasswordAuthenticationConverter(), new OAuth2ResourceOwnerSmsAuthenticationConverter(), new OAuth2RefreshTokenAuthenticationConverter(), new OAuth2ClientCredentialsAuthenticationConverter(), new OAuth2AuthorizationCodeAuthenticationConverter(), new OAuth2AuthorizationCodeRequestAuthenticationConverter())); } /** * 注入授权模式实现提供方 *

* 1. 密码模式
* 2. 短信登录
*/ private void addCustomOAuth2GrantAuthenticationProvider(HttpSecurity http) { AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class); OAuth2AuthorizationService authorizationService = http.getSharedObject(OAuth2AuthorizationService.class); OAuth2ResourceOwnerPasswordAuthenticationProvider resourceOwnerPasswordAuthenticationProvider = new OAuth2ResourceOwnerPasswordAuthenticationProvider( authenticationManager, authorizationService, oAuth2TokenGenerator()); OAuth2ResourceOwnerSmsAuthenticationProvider resourceOwnerSmsAuthenticationProvider = new OAuth2ResourceOwnerSmsAuthenticationProvider( authenticationManager, authorizationService, oAuth2TokenGenerator()); // 处理 UsernamePasswordAuthenticationToken http.authenticationProvider(new PigDaoAuthenticationProvider()); // 处理 OAuth2ResourceOwnerPasswordAuthenticationToken http.authenticationProvider(resourceOwnerPasswordAuthenticationProvider); // 处理 OAuth2ResourceOwnerSmsAuthenticationToken http.authenticationProvider(resourceOwnerSmsAuthenticationProvider); } /** * 配置 CORS 跨域资源共享 * @return UrlBasedCorsConfigurationSource CORS配置源 */ private UrlBasedCorsConfigurationSource corsConfigurationSource() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration corsConfiguration = new CorsConfiguration(); // 从配置文件读取允许的源模式 pigBootCorsProperties.getAllowedOriginPatterns().forEach(corsConfiguration::addAllowedOriginPattern); // 从配置文件读取允许的请求头 pigBootCorsProperties.getAllowedHeaders().forEach(corsConfiguration::addAllowedHeader); // 从配置文件读取允许的HTTP方法 pigBootCorsProperties.getAllowedMethods().forEach(corsConfiguration::addAllowedMethod); // 从配置文件读取是否允许携带凭证 corsConfiguration.setAllowCredentials(pigBootCorsProperties.getAllowCredentials()); // 注册CORS配置到指定路径 source.registerCorsConfiguration(pigBootCorsProperties.getPathPattern(), corsConfiguration); return source; } } ================================================ FILE: pig-auth/src/main/java/com/pig4cloud/pig/auth/endpoint/ImageCodeEndpoint.java ================================================ package com.pig4cloud.pig.auth.endpoint; import cn.hutool.core.lang.Validator; import com.pig4cloud.captcha.ArithmeticCaptcha; import com.pig4cloud.pig.common.core.constant.CacheConstants; import com.pig4cloud.pig.common.core.constant.SecurityConstants; import com.pig4cloud.pig.common.core.util.RedisUtils; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.concurrent.TimeUnit; /** * 验证码相关的接口 * * @author lengleng * @date 2022/6/27 */ @RestController @RequestMapping("/code") @RequiredArgsConstructor @Tag(description = "code", name = "验证码控制器管理模块") public class ImageCodeEndpoint { private static final Integer DEFAULT_IMAGE_WIDTH = 100; private static final Integer DEFAULT_IMAGE_HEIGHT = 40; /** * 创建图形验证码并输出到响应流 * @param randomStr 随机字符串,用于缓存验证码 * @param response HTTP响应对象,用于输出验证码图片 */ @SneakyThrows @GetMapping("/image") @Operation(summary = "创建图形验证码并输出到响应流", description = "创建图形验证码并输出到响应流") public void image(String randomStr, HttpServletResponse response) { ArithmeticCaptcha captcha = new ArithmeticCaptcha(DEFAULT_IMAGE_WIDTH, DEFAULT_IMAGE_HEIGHT); if (Validator.isMobile(randomStr)) { return; } String result = captcha.text(); RedisUtils.set(CacheConstants.DEFAULT_CODE_KEY + randomStr, result, SecurityConstants.CODE_TIME, TimeUnit.SECONDS); // 转换流信息写出 captcha.out(response.getOutputStream()); } } ================================================ FILE: pig-auth/src/main/java/com/pig4cloud/pig/auth/endpoint/PigTokenEndpoint.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.auth.endpoint; import java.security.Principal; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.cache.CacheManager; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.security.authentication.event.LogoutSuccessEvent; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; import org.springframework.util.StringUtils; 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.RequestHeader; 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.servlet.ModelAndView; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.pig4cloud.pig.admin.api.entity.SysOauthClientDetails; import com.pig4cloud.pig.admin.api.feign.RemoteClientDetailsService; import com.pig4cloud.pig.admin.api.vo.TokenVo; import com.pig4cloud.pig.auth.support.handler.PigAuthenticationFailureEventHandler; import com.pig4cloud.pig.common.core.constant.CacheConstants; import com.pig4cloud.pig.common.core.constant.CommonConstants; import com.pig4cloud.pig.common.core.constant.SecurityConstants; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.core.util.RedisUtils; import com.pig4cloud.pig.common.core.util.RetOps; import com.pig4cloud.pig.common.core.util.SpringContextHolder; import com.pig4cloud.pig.common.security.annotation.Inner; import com.pig4cloud.pig.common.security.util.OAuth2EndpointUtils; import com.pig4cloud.pig.common.security.util.OAuth2ErrorCodesExpand; import com.pig4cloud.pig.common.security.util.OAuthClientException; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.TemporalAccessorUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; /** * OAuth2 令牌端点控制器,提供令牌相关操作 * * @author lengleng * @date 2025/05/30 */ @RestController @RequestMapping @RequiredArgsConstructor @Tag(description = "oauth", name = "OAuth2 令牌端点控制器管理模块") public class PigTokenEndpoint { private final HttpMessageConverter accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); private final AuthenticationFailureHandler authenticationFailureHandler = new PigAuthenticationFailureEventHandler(); private final OAuth2AuthorizationService authorizationService; private final RemoteClientDetailsService clientDetailsService; private final CacheManager cacheManager; /** * 授权码模式:认证页面 * @param modelAndView 视图模型对象 * @param error 表单登录失败处理回调的错误信息 * @return 包含登录页面视图和错误信息的ModelAndView对象 */ @GetMapping("/token/login") @Operation(summary = "授权码模式:认证页面", description = "授权码模式:认证页面") public ModelAndView require(ModelAndView modelAndView, @RequestParam(required = false) String error) { modelAndView.setViewName("ftl/login"); modelAndView.addObject("error", error); return modelAndView; } /** * 授权码模式:确认页面 * @param principal 用户主体信息 * @param modelAndView 模型和视图对象 * @param clientId 客户端ID * @param scope 请求的权限范围 * @param state 状态参数 * @return 包含确认页面信息的ModelAndView对象 */ @GetMapping("/oauth2/confirm_access") @Operation(summary = "授权码模式:确认页面", description = "授权码模式:确认页面") public ModelAndView confirm(Principal principal, ModelAndView modelAndView, @RequestParam(OAuth2ParameterNames.CLIENT_ID) String clientId, @RequestParam(OAuth2ParameterNames.SCOPE) String scope, @RequestParam(OAuth2ParameterNames.STATE) String state) { SysOauthClientDetails clientDetails = RetOps.of(clientDetailsService.getClientDetailsById(clientId)) .getData() .orElseThrow(() -> new OAuthClientException("clientId 不合法")); Set authorizedScopes = StringUtils.commaDelimitedListToSet(clientDetails.getScope()); modelAndView.addObject("clientId", clientId); modelAndView.addObject("state", state); modelAndView.addObject("scopeList", authorizedScopes); modelAndView.addObject("principalName", principal.getName()); modelAndView.setViewName("ftl/confirm"); return modelAndView; } /** * 注销并删除令牌 * @param authHeader 认证头信息,包含Bearer token * @return 返回操作结果,包含布尔值表示是否成功 */ @DeleteMapping("/token/logout") @Operation(summary = "注销并删除令牌", description = "注销并删除令牌") public R logout(@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authHeader) { if (StrUtil.isBlank(authHeader)) { return R.ok(); } String tokenValue = authHeader.replace(OAuth2AccessToken.TokenType.BEARER.getValue(), StrUtil.EMPTY).trim(); return removeToken(tokenValue); } /** * 检查令牌有效性 * @param token 待验证的令牌 * @param response HTTP响应对象 * @param request HTTP请求对象 * @throws InvalidBearerTokenException 令牌无效或缺失时抛出异常 */ @SneakyThrows @GetMapping("/token/check_token") @Operation(summary = "检查令牌有效性", description = "检查令牌有效性") public void checkToken(String token, HttpServletResponse response, HttpServletRequest request) { ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); if (StrUtil.isBlank(token)) { httpResponse.setStatusCode(HttpStatus.UNAUTHORIZED); this.authenticationFailureHandler.onAuthenticationFailure(request, response, new InvalidBearerTokenException(OAuth2ErrorCodesExpand.TOKEN_MISSING)); return; } OAuth2Authorization authorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN); // 如果令牌不存在 返回401 if (authorization == null || authorization.getAccessToken() == null) { this.authenticationFailureHandler.onAuthenticationFailure(request, response, new InvalidBearerTokenException(OAuth2ErrorCodesExpand.INVALID_BEARER_TOKEN)); return; } Map claims = authorization.getAccessToken().getClaims(); OAuth2AccessTokenResponse sendAccessTokenResponse = OAuth2EndpointUtils.sendAccessTokenResponse(authorization, claims); this.accessTokenHttpResponseConverter.write(sendAccessTokenResponse, MediaType.APPLICATION_JSON, httpResponse); } /** * 删除令牌 * @param token 令牌 * @return 删除结果 */ @Inner @DeleteMapping("/token/remove/{token}") @Operation(summary = "删除令牌", description = "删除令牌") public R removeToken(@PathVariable("token") String token) { OAuth2Authorization authorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN); if (authorization == null) { return R.ok(); } OAuth2Authorization.Token accessToken = authorization.getAccessToken(); if (accessToken == null || StrUtil.isBlank(accessToken.getToken().getTokenValue())) { return R.ok(); } // 清空用户信息(立即删除) cacheManager.getCache(CacheConstants.USER_DETAILS).evictIfPresent(authorization.getPrincipalName()); // 清空access token authorizationService.remove(authorization); // 处理自定义退出事件,保存相关日志 SpringContextHolder.publishEvent(new LogoutSuccessEvent(new PreAuthenticatedAuthenticationToken( authorization.getPrincipalName(), authorization.getRegisteredClientId()))); return R.ok(); } /** * 分页查询令牌列表 * @param params 请求参数,包含分页参数current和size * @return 分页结果,包含令牌信息列表 */ @Inner @PostMapping("/token/page") @Operation(summary = "分页查询令牌列表", description = "分页查询令牌列表") public R tokenList(@RequestBody Map params) { // 根据分页参数获取对应数据 String username = MapUtil.getStr(params, SecurityConstants.USERNAME); String pattern = String.format("%s::*", CacheConstants.PROJECT_OAUTH_ACCESS); int current = MapUtil.getInt(params, CommonConstants.CURRENT); int size = MapUtil.getInt(params, CommonConstants.SIZE); Page result = new Page(current, size); // 获取总数 List allKeys = RedisUtils.scan(pattern); result.setTotal(allKeys.size()); List pageKeys = RedisUtils.findKeysForPage(pattern, current - 1, size); List pagedAuthorizations = RedisUtils.multiGet(pageKeys); // 转换为TokenVo List tokenVoList = pagedAuthorizations.stream() .filter(Objects::nonNull) .map(this::convertToTokenVo) .filter(tokenVo -> { if (StrUtil.isBlank(username)) { return true; } return StrUtil.startWithAnyIgnoreCase(tokenVo.getUsername(), username); }) .toList(); if (StrUtil.isNotBlank(username)) { result.setTotal(tokenVoList.size()); } result.setRecords(tokenVoList); return R.ok(result); } /** * 将OAuth2Authorization转换为TokenVo * @param authorization OAuth2授权对象 * @return TokenVo对象 */ private TokenVo convertToTokenVo(OAuth2Authorization authorization) { TokenVo tokenVo = new TokenVo(); tokenVo.setClientId(authorization.getRegisteredClientId()); tokenVo.setId(authorization.getId()); tokenVo.setUsername(authorization.getPrincipalName()); OAuth2Authorization.Token accessToken = authorization.getAccessToken(); tokenVo.setAccessToken(accessToken.getToken().getTokenValue()); String expiresAt = TemporalAccessorUtil.format(accessToken.getToken().getExpiresAt(), DatePattern.NORM_DATETIME_PATTERN); tokenVo.setExpiresAt(expiresAt); String issuedAt = TemporalAccessorUtil.format(accessToken.getToken().getIssuedAt(), DatePattern.NORM_DATETIME_PATTERN); tokenVo.setIssuedAt(issuedAt); return tokenVo; } } ================================================ FILE: pig-auth/src/main/java/com/pig4cloud/pig/auth/support/CustomeOAuth2AccessTokenGenerator.java ================================================ package com.pig4cloud.pig.auth.support; import org.springframework.lang.Nullable; import org.springframework.security.crypto.keygen.Base64StringKeyGenerator; import org.springframework.security.crypto.keygen.StringKeyGenerator; import org.springframework.security.oauth2.core.ClaimAccessor; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; import org.springframework.security.oauth2.server.authorization.token.*; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import java.io.Serial; import java.time.Instant; import java.util.*; /** * 自定义OAuth2访问令牌生成器 * * @author lengleng * @date 2025/05/30 */ public class CustomeOAuth2AccessTokenGenerator implements OAuth2TokenGenerator { private OAuth2TokenCustomizer accessTokenCustomizer; private final StringKeyGenerator accessTokenGenerator = new Base64StringKeyGenerator( Base64.getUrlEncoder().withoutPadding(), 96); /** * 生成OAuth2访问令牌 * @param context OAuth2令牌上下文 * @return 生成的访问令牌,如果令牌类型不是ACCESS_TOKEN或格式不是REFERENCE则返回null * @see OAuth2TokenContext * @see OAuth2AccessToken */ @Nullable @Override public OAuth2AccessToken generate(OAuth2TokenContext context) { if (!OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType()) || !OAuth2TokenFormat.REFERENCE .equals(context.getRegisteredClient().getTokenSettings().getAccessTokenFormat())) { return null; } String issuer = null; if (context.getAuthorizationServerContext() != null) { issuer = context.getAuthorizationServerContext().getIssuer(); } RegisteredClient registeredClient = context.getRegisteredClient(); Instant issuedAt = Instant.now(); Instant expiresAt = issuedAt.plus(registeredClient.getTokenSettings().getAccessTokenTimeToLive()); // @formatter:off OAuth2TokenClaimsSet.Builder claimsBuilder = OAuth2TokenClaimsSet.builder(); if (StringUtils.hasText(issuer)) { claimsBuilder.issuer(issuer); } claimsBuilder .subject(context.getPrincipal().getName()) .audience(Collections.singletonList(registeredClient.getClientId())) .issuedAt(issuedAt) .expiresAt(expiresAt) .notBefore(issuedAt) .id(UUID.randomUUID().toString()); if (!CollectionUtils.isEmpty(context.getAuthorizedScopes())) { claimsBuilder.claim(OAuth2ParameterNames.SCOPE, context.getAuthorizedScopes()); } // @formatter:on if (this.accessTokenCustomizer != null) { // @formatter:off OAuth2TokenClaimsContext.Builder accessTokenContextBuilder = OAuth2TokenClaimsContext.with(claimsBuilder) .registeredClient(context.getRegisteredClient()) .principal(context.getPrincipal()) .authorizationServerContext(context.getAuthorizationServerContext()) .authorizedScopes(context.getAuthorizedScopes()) .tokenType(context.getTokenType()) .authorizationGrantType(context.getAuthorizationGrantType()); if (context.getAuthorization() != null) { accessTokenContextBuilder.authorization(context.getAuthorization()); } if (context.getAuthorizationGrant() != null) { accessTokenContextBuilder.authorizationGrant(context.getAuthorizationGrant()); } // @formatter:on OAuth2TokenClaimsContext accessTokenContext = accessTokenContextBuilder.build(); this.accessTokenCustomizer.customize(accessTokenContext); } OAuth2TokenClaimsSet accessTokenClaimsSet = claimsBuilder.build(); return new CustomeOAuth2AccessTokenGenerator.OAuth2AccessTokenClaims(OAuth2AccessToken.TokenType.BEARER, this.accessTokenGenerator.generateKey(), accessTokenClaimsSet.getIssuedAt(), accessTokenClaimsSet.getExpiresAt(), context.getAuthorizedScopes(), accessTokenClaimsSet.getClaims()); } /** * 设置用于定制{@link OAuth2AccessToken}的{@link OAuth2TokenClaimsContext#getClaims()}的{@link OAuth2TokenCustomizer} * @param accessTokenCustomizer * 用于定制{@code OAuth2AccessToken}声明的{@link OAuth2TokenCustomizer} * @throws IllegalArgumentException 当accessTokenCustomizer为null时抛出 */ public void setAccessTokenCustomizer(OAuth2TokenCustomizer accessTokenCustomizer) { Assert.notNull(accessTokenCustomizer, "accessTokenCustomizer cannot be null"); this.accessTokenCustomizer = accessTokenCustomizer; } /** * OAuth2访问令牌声明类,继承自OAuth2AccessToken并实现ClaimAccessor接口 * * @author lengleng * @date 2025/05/30 */ private static final class OAuth2AccessTokenClaims extends OAuth2AccessToken implements ClaimAccessor { @Serial private static final long serialVersionUID = 1L; private final Map claims; /** * 构造OAuth2访问令牌声明 * @param tokenType 令牌类型 * @param tokenValue 令牌值 * @param issuedAt 颁发时间 * @param expiresAt 过期时间 * @param scopes 权限范围集合 * @param claims 声明信息映射 */ private OAuth2AccessTokenClaims(TokenType tokenType, String tokenValue, Instant issuedAt, Instant expiresAt, Set scopes, Map claims) { super(tokenType, tokenValue, issuedAt, expiresAt, scopes); this.claims = claims; } /** * 获取claims集合 * @return claims键值对集合 */ @Override public Map getClaims() { return this.claims; } } } ================================================ FILE: pig-auth/src/main/java/com/pig4cloud/pig/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationConverter.java ================================================ package com.pig4cloud.pig.auth.support.base; import com.pig4cloud.pig.common.security.util.OAuth2EndpointUtils; import jakarta.servlet.http.HttpServletRequest; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.core.OAuth2ErrorCodes; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import java.util.Arrays; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; /** * OAuth2资源所有者基础认证转换器抽象类 * * @param 继承自OAuth2ResourceOwnerBaseAuthenticationToken的泛型类型 * @author lengleng * @date 2025/05/30 */ public abstract class OAuth2ResourceOwnerBaseAuthenticationConverter implements AuthenticationConverter { /** * 是否支持此convert * @param grantType 授权类型 * @return */ public abstract boolean support(String grantType); /** * 校验参数 * @param request 请求 */ public void checkParams(HttpServletRequest request) { } /** * 构建具体类型的token * @param clientPrincipal 客户端认证信息 * @param requestedScopes 请求的作用域集合 * @param additionalParameters 附加参数映射 * @return 构建完成的token对象 */ public abstract T buildToken(Authentication clientPrincipal, Set requestedScopes, Map additionalParameters); /** * 将HttpServletRequest转换为Authentication对象 * @param request HTTP请求对象 * @return 认证信息对象 * @throws OAuth2AuthenticationException 当请求参数不合法或客户端未认证时抛出异常 */ @Override public Authentication convert(HttpServletRequest request) { // grant_type (REQUIRED) String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE); if (!support(grantType)) { return null; } MultiValueMap parameters = OAuth2EndpointUtils.getParameters(request); // scope (OPTIONAL) String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE); if (StringUtils.hasText(scope) && parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) { OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE, OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); } Set requestedScopes = null; if (StringUtils.hasText(scope)) { requestedScopes = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " "))); } // 校验个性化参数 checkParams(request); // 获取当前已经认证的客户端信息 Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); if (clientPrincipal == null) { OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ErrorCodes.INVALID_CLIENT, OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); } // 扩展信息 Map additionalParameters = parameters.entrySet() .stream() .filter(e -> !e.getKey().equals(OAuth2ParameterNames.GRANT_TYPE) && !e.getKey().equals(OAuth2ParameterNames.SCOPE)) .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0))); // 创建token return buildToken(clientPrincipal, requestedScopes, additionalParameters); } } ================================================ FILE: pig-auth/src/main/java/com/pig4cloud/pig/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationProvider.java ================================================ package com.pig4cloud.pig.auth.support.base; import cn.hutool.extra.spring.SpringUtil; import com.pig4cloud.pig.common.security.util.OAuth2ErrorCodesExpand; import com.pig4cloud.pig.common.security.util.ScopeException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.context.support.MessageSourceAccessor; import org.springframework.security.authentication.*; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.oauth2.core.*; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import java.security.Principal; import java.time.Instant; import java.util.*; import java.util.function.Supplier; /** * OAuth2资源所有者基础认证提供者抽象类,用于处理资源所有者密码凭证授权流程 * * @param OAuth2资源所有者基础认证令牌类型 * @author lengleng * @date 2025/05/30 */ public abstract class OAuth2ResourceOwnerBaseAuthenticationProvider implements AuthenticationProvider { private static final Logger LOGGER = LogManager.getLogger(OAuth2ResourceOwnerBaseAuthenticationProvider.class); private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1"; private final OAuth2AuthorizationService authorizationService; private final OAuth2TokenGenerator tokenGenerator; private final AuthenticationManager authenticationManager; private final MessageSourceAccessor messages; @Deprecated private Supplier refreshTokenGenerator; /** * 构造一个基于资源所有者密码模式的OAuth2认证提供者 * @param authenticationManager 认证管理器 * @param authorizationService 授权服务 * @param tokenGenerator token生成器 * @throws IllegalArgumentException 当authorizationService或tokenGenerator为null时抛出 * @since 0.2.3 */ public OAuth2ResourceOwnerBaseAuthenticationProvider(AuthenticationManager authenticationManager, OAuth2AuthorizationService authorizationService, OAuth2TokenGenerator tokenGenerator) { Assert.notNull(authorizationService, "authorizationService cannot be null"); Assert.notNull(tokenGenerator, "tokenGenerator cannot be null"); this.authenticationManager = authenticationManager; this.authorizationService = authorizationService; this.tokenGenerator = tokenGenerator; // 国际化配置 this.messages = new MessageSourceAccessor(SpringUtil.getBean("securityMessageSource"), Locale.CHINA); } /** * 设置刷新令牌生成器 * @param refreshTokenGenerator 刷新令牌生成器,不能为null * @deprecated 该方法已废弃 */ @Deprecated public void setRefreshTokenGenerator(Supplier refreshTokenGenerator) { Assert.notNull(refreshTokenGenerator, "refreshTokenGenerator cannot be null"); this.refreshTokenGenerator = refreshTokenGenerator; } /** * 构建用户名密码认证令牌 * @param reqParameters 请求参数映射 * @return 用户名密码认证令牌 */ public abstract UsernamePasswordAuthenticationToken buildToken(Map reqParameters); /** * 当前provider是否支持此令牌类型 * @param authentication * @return */ @Override public abstract boolean supports(Class authentication); /** * 当前的请求客户端是否支持此模式 * @param registeredClient */ public abstract void checkClient(RegisteredClient registeredClient); /** * 执行认证操作,遵循与{@link AuthenticationManager#authenticate(Authentication)}相同的契约 * @param authentication 认证请求对象 * @return 包含凭证的完整认证对象,如果当前认证提供者无法处理传入的认证对象可能返回null * @throws AuthenticationException 认证失败时抛出 * @throws OAuth2AuthenticationException 当scope无效或token生成失败时抛出 */ @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { T resouceOwnerBaseAuthentication = (T) authentication; OAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient( resouceOwnerBaseAuthentication); RegisteredClient registeredClient = clientPrincipal.getRegisteredClient(); checkClient(registeredClient); Set authorizedScopes; // Default to configured scopes if (!CollectionUtils.isEmpty(resouceOwnerBaseAuthentication.getScopes())) { for (String requestedScope : resouceOwnerBaseAuthentication.getScopes()) { if (!registeredClient.getScopes().contains(requestedScope)) { throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_SCOPE); } } authorizedScopes = new LinkedHashSet<>(resouceOwnerBaseAuthentication.getScopes()); } else { authorizedScopes = new LinkedHashSet<>(); } Map reqParameters = resouceOwnerBaseAuthentication.getAdditionalParameters(); try { UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = buildToken(reqParameters); LOGGER.debug("got usernamePasswordAuthenticationToken=" + usernamePasswordAuthenticationToken); Authentication usernamePasswordAuthentication = authenticationManager .authenticate(usernamePasswordAuthenticationToken); // @formatter:off DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder() .registeredClient(registeredClient) .principal(usernamePasswordAuthentication) .authorizationServerContext(AuthorizationServerContextHolder.getContext()) .authorizedScopes(authorizedScopes) .authorizationGrantType(resouceOwnerBaseAuthentication.getAuthorizationGrantType()) .authorizationGrant(resouceOwnerBaseAuthentication); // @formatter:on OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization .withRegisteredClient(registeredClient) .principalName(usernamePasswordAuthentication.getName()) .authorizationGrantType(resouceOwnerBaseAuthentication.getAuthorizationGrantType()) // 0.4.0 新增的方法 .authorizedScopes(authorizedScopes); // ----- Access token ----- OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build(); OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext); if (generatedAccessToken == null) { OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, "The token generator failed to generate the access token.", ERROR_URI); throw new OAuth2AuthenticationException(error); } OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(), generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes()); if (generatedAccessToken instanceof ClaimAccessor) { authorizationBuilder.id(accessToken.getTokenValue()) .token(accessToken, (metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims())) // 0.4.0 新增的方法 .authorizedScopes(authorizedScopes) .attribute(Principal.class.getName(), usernamePasswordAuthentication); } else { authorizationBuilder.id(accessToken.getTokenValue()).accessToken(accessToken); } // ----- Refresh token ----- OAuth2RefreshToken refreshToken = null; if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) && // Do not issue refresh token to public client !clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) { if (this.refreshTokenGenerator != null) { Instant issuedAt = Instant.now(); Instant expiresAt = issuedAt.plus(registeredClient.getTokenSettings().getRefreshTokenTimeToLive()); refreshToken = new OAuth2RefreshToken(this.refreshTokenGenerator.get(), issuedAt, expiresAt); } else { tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build(); OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext); if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) { OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, "The token generator failed to generate the refresh token.", ERROR_URI); throw new OAuth2AuthenticationException(error); } refreshToken = (OAuth2RefreshToken) generatedRefreshToken; } authorizationBuilder.refreshToken(refreshToken); } OAuth2Authorization authorization = authorizationBuilder.build(); this.authorizationService.save(authorization); LOGGER.debug("returning OAuth2AccessTokenAuthenticationToken"); return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, Objects.requireNonNull(authorization.getAccessToken().getClaims())); } catch (Exception ex) { LOGGER.error("problem in authenticate", ex); throw oAuth2AuthenticationException(authentication, (AuthenticationException) ex); } } /** * 登录异常转换为oauth2异常 * @param authentication 身份验证 * @param authenticationException 身份验证异常 * @return {@link OAuth2AuthenticationException} */ private OAuth2AuthenticationException oAuth2AuthenticationException(Authentication authentication, AuthenticationException authenticationException) { if (authenticationException instanceof UsernameNotFoundException) { return new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodesExpand.USERNAME_NOT_FOUND, this.messages.getMessage("JdbcDaoImpl.notFound", new Object[] { authentication.getName() }, "Username {0} not found"), "")); } if (authenticationException instanceof BadCredentialsException) { return new OAuth2AuthenticationException( new OAuth2Error(OAuth2ErrorCodesExpand.BAD_CREDENTIALS, this.messages.getMessage( "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), "")); } if (authenticationException instanceof LockedException) { return new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodesExpand.USER_LOCKED, this.messages .getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"), "")); } if (authenticationException instanceof DisabledException) { return new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodesExpand.USER_DISABLE, this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"), "")); } if (authenticationException instanceof AccountExpiredException) { return new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodesExpand.USER_EXPIRED, this.messages .getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"), "")); } if (authenticationException instanceof CredentialsExpiredException) { return new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodesExpand.CREDENTIALS_EXPIRED, this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.credentialsExpired", "User credentials have expired"), "")); } if (authenticationException instanceof ScopeException) { return new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_SCOPE, this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "invalid_scope"), "")); } return new OAuth2AuthenticationException(OAuth2ErrorCodesExpand.UN_KNOW_LOGIN_ERROR); } /** * 获取已认证的客户端主体,否则抛出无效客户端异常 * @param authentication 认证信息 * @return 已认证的客户端主体 * @throws OAuth2AuthenticationException 客户端未认证时抛出异常 */ private OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient( Authentication authentication) { OAuth2ClientAuthenticationToken clientPrincipal = null; if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) { clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal(); } if (clientPrincipal != null && clientPrincipal.isAuthenticated()) { return clientPrincipal; } throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT); } } ================================================ FILE: pig-auth/src/main/java/com/pig4cloud/pig/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationToken.java ================================================ package com.pig4cloud.pig.auth.support.base; import lombok.Getter; import org.springframework.lang.Nullable; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.util.Assert; import java.io.Serial; import java.util.*; /** * OAuth2资源所有者基础认证令牌抽象类 * * @author lengleng * @date 2025/05/30 */ public abstract class OAuth2ResourceOwnerBaseAuthenticationToken extends AbstractAuthenticationToken { @Serial private static final long serialVersionUID = 1L; @Getter private final AuthorizationGrantType authorizationGrantType; @Getter private final Authentication clientPrincipal; @Getter private final Set scopes; @Getter private final Map additionalParameters; public OAuth2ResourceOwnerBaseAuthenticationToken(AuthorizationGrantType authorizationGrantType, Authentication clientPrincipal, @Nullable Set scopes, @Nullable Map additionalParameters) { super(Collections.emptyList()); Assert.notNull(authorizationGrantType, "authorizationGrantType cannot be null"); Assert.notNull(clientPrincipal, "clientPrincipal cannot be null"); this.authorizationGrantType = authorizationGrantType; this.clientPrincipal = clientPrincipal; this.scopes = Collections.unmodifiableSet(scopes != null ? new HashSet<>(scopes) : Collections.emptySet()); this.additionalParameters = Collections.unmodifiableMap( additionalParameters != null ? new HashMap<>(additionalParameters) : Collections.emptyMap()); } /** * 扩展模式一般不需要密码 */ @Override public Object getCredentials() { return ""; } /** * 获取用户名 */ @Override public Object getPrincipal() { return this.clientPrincipal; } } ================================================ FILE: pig-auth/src/main/java/com/pig4cloud/pig/auth/support/base/package-info.java ================================================ /** * 自定义认证模式接入的抽象实现 */ package com.pig4cloud.pig.auth.support.base; ================================================ FILE: pig-auth/src/main/java/com/pig4cloud/pig/auth/support/core/CustomeOAuth2TokenCustomizer.java ================================================ package com.pig4cloud.pig.auth.support.core; import com.pig4cloud.pig.common.core.constant.SecurityConstants; import com.pig4cloud.pig.common.security.service.PigUser; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsSet; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; /** * OAuth2 Token 自定义增强实现类 * * @author lengleng * @date 2025/05/30 */ public class CustomeOAuth2TokenCustomizer implements OAuth2TokenCustomizer { /** * 自定义OAuth 2.0 Token属性 * @param context 包含OAuth 2.0 Token属性的上下文 */ @Override public void customize(OAuth2TokenClaimsContext context) { OAuth2TokenClaimsSet.Builder claims = context.getClaims(); claims.claim(SecurityConstants.DETAILS_LICENSE, SecurityConstants.PROJECT_LICENSE); String clientId = context.getAuthorizationGrant().getName(); claims.claim(SecurityConstants.CLIENT_ID, clientId); // 客户端模式不返回具体用户信息 if (SecurityConstants.CLIENT_CREDENTIALS.equals(context.getAuthorizationGrantType().getValue())) { return; } PigUser pigUser = (PigUser) context.getPrincipal().getPrincipal(); claims.claim(SecurityConstants.DETAILS_USER, pigUser); claims.claim(SecurityConstants.DETAILS_USER_ID, pigUser.getId()); claims.claim(SecurityConstants.USERNAME, pigUser.getUsername()); } } ================================================ FILE: pig-auth/src/main/java/com/pig4cloud/pig/auth/support/core/FormIdentityLoginConfigurer.java ================================================ package com.pig4cloud.pig.auth.support.core; import com.pig4cloud.pig.auth.support.handler.FormAuthenticationFailureHandler; import com.pig4cloud.pig.auth.support.handler.SsoLogoutSuccessHandler; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; /** * 基于授权码模式的统一认证登录配置类,适用于Spring Security和SAS * * @author lengleng * @date 2025/05/30 */ public final class FormIdentityLoginConfigurer extends AbstractHttpConfigurer { @Override public void init(HttpSecurity http) throws Exception { http.formLogin(formLogin -> { formLogin.loginPage("/token/login"); formLogin.loginProcessingUrl("/oauth2/form"); formLogin.failureHandler(new FormAuthenticationFailureHandler()); }) .logout(logout -> logout.logoutUrl("/oauth2/logout") .logoutSuccessHandler(new SsoLogoutSuccessHandler()) .deleteCookies("JSESSIONID") .invalidateHttpSession(true)) // SSO登出成功处理 .csrf(AbstractHttpConfigurer::disable); } } ================================================ FILE: pig-auth/src/main/java/com/pig4cloud/pig/auth/support/core/PigDaoAuthenticationProvider.java ================================================ package com.pig4cloud.pig.auth.support.core; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.spring.SpringUtil; import com.pig4cloud.pig.common.core.util.WebUtils; import com.pig4cloud.pig.common.security.service.PigUserDetailsService; import jakarta.servlet.http.HttpServletRequest; import lombok.SneakyThrows; import org.springframework.core.Ordered; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsPasswordService; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.web.authentication.www.BasicAuthenticationConverter; import org.springframework.util.Assert; import java.util.Comparator; import java.util.Map; import java.util.Optional; import java.util.function.Supplier; import static com.pig4cloud.pig.common.core.constant.SecurityConstants.PASSWORD; /** * 基于DAO的认证提供者实现,用于处理用户名密码认证 * * @author lengleng * @date 2025/05/30 */ public class PigDaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider { /** * 用户未找到时用于PasswordEncoder#matches(CharSequence, String)的明文密码,避免SEC-2056问题 */ private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword"; private final static BasicAuthenticationConverter basicConvert = new BasicAuthenticationConverter(); /** * 密码编码器 */ private PasswordEncoder passwordEncoder; /** * 用户未找到时的加密密码,用于避免SEC-2056问题,某些密码编码器在密码格式无效时会短路处理 */ private volatile String userNotFoundEncodedPassword; private UserDetailsService userDetailsService; private UserDetailsPasswordService userDetailsPasswordService; public PigDaoAuthenticationProvider() { setMessageSource(SpringUtil.getBean("securityMessageSource")); setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder()); } /** * 执行额外的身份验证检查 * @param userDetails 用户详细信息 * @param authentication 身份验证令牌 * @throws AuthenticationException 身份验证失败时抛出异常 */ @Override protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { // 只有密码模式需要校验密码 String grantType = WebUtils.getRequest().get().getParameter(OAuth2ParameterNames.GRANT_TYPE); if (!StrUtil.equals(PASSWORD, grantType)) { return; } if (authentication.getCredentials() == null) { this.logger.debug("Failed to authenticate since no credentials provided"); throw new BadCredentialsException(this.messages .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } String presentedPassword = authentication.getCredentials().toString(); if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) { this.logger.debug("Failed to authenticate since password does not match stored value"); throw new BadCredentialsException(this.messages .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } } /** * 根据用户名检索用户详情 * @param username 用户名 * @param authentication 认证令牌 * @return 用户详情信息 * @throws InternalAuthenticationServiceException * 当无法获取请求、未注册UserDetailsService或加载用户失败时抛出 * @throws UsernameNotFoundException 当用户名不存在时抛出 */ @SneakyThrows @Override protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) { prepareTimingAttackProtection(); HttpServletRequest request = WebUtils.getRequest() .orElseThrow( (Supplier) () -> new InternalAuthenticationServiceException("web request is empty")); String grantType = WebUtils.getRequest().get().getParameter(OAuth2ParameterNames.GRANT_TYPE); String clientId = WebUtils.getRequest().get().getParameter(OAuth2ParameterNames.CLIENT_ID); if (StrUtil.isBlank(clientId)) { clientId = Optional.ofNullable(basicConvert.convert(request)) .map(UsernamePasswordAuthenticationToken::getName) .orElse(null); } Map userDetailsServiceMap = SpringUtil .getBeansOfType(PigUserDetailsService.class); String finalClientId = clientId; Optional optional = userDetailsServiceMap.values() .stream() .filter(service -> service.support(finalClientId, grantType)) .max(Comparator.comparingInt(Ordered::getOrder)); if (optional.isEmpty()) { throw new InternalAuthenticationServiceException("UserDetailsService error , not register"); } try { UserDetails loadedUser = optional.get().loadUserByUsername(username); if (loadedUser == null) { throw new InternalAuthenticationServiceException( "UserDetailsService returned null, which is an interface contract violation"); } return loadedUser; } catch (UsernameNotFoundException ex) { mitigateAgainstTimingAttack(authentication); throw ex; } catch (InternalAuthenticationServiceException ex) { throw ex; } catch (Exception ex) { throw new InternalAuthenticationServiceException(ex.getMessage(), ex); } } /** * 创建认证成功后的Authentication对象 * @param principal 认证主体 * @param authentication 认证信息 * @param user 用户详情 * @return 认证成功后的Authentication对象 */ @Override protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) { boolean upgradeEncoding = this.userDetailsPasswordService != null && this.passwordEncoder.upgradeEncoding(user.getPassword()); if (upgradeEncoding) { String presentedPassword = authentication.getCredentials().toString(); String newPassword = this.passwordEncoder.encode(presentedPassword); user = this.userDetailsPasswordService.updatePassword(user, newPassword); } return super.createSuccessAuthentication(principal, authentication, user); } /** * 准备定时攻击保护,如果未找到用户编码密码为空则进行编码 */ private void prepareTimingAttackProtection() { if (this.userNotFoundEncodedPassword == null) { this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD); } } /** * 防止时序攻击的缓解措施 * @param authentication 用户名密码认证令牌 */ private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) { if (authentication.getCredentials() != null) { String presentedPassword = authentication.getCredentials().toString(); this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword); } } /** * 设置用于编码和验证密码的PasswordEncoder实例 * @param passwordEncoder 密码编码器实例,不能为null */ public void setPasswordEncoder(PasswordEncoder passwordEncoder) { Assert.notNull(passwordEncoder, "passwordEncoder cannot be null"); this.passwordEncoder = passwordEncoder; this.userNotFoundEncodedPassword = null; } protected PasswordEncoder getPasswordEncoder() { return this.passwordEncoder; } /** * 设置用户详情服务 * @param userDetailsService 用户详情服务 */ public void setUserDetailsService(UserDetailsService userDetailsService) { this.userDetailsService = userDetailsService; } protected UserDetailsService getUserDetailsService() { return this.userDetailsService; } /** * 设置用户详情密码服务 * @param userDetailsPasswordService 用户详情密码服务 */ public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) { this.userDetailsPasswordService = userDetailsPasswordService; } } ================================================ FILE: pig-auth/src/main/java/com/pig4cloud/pig/auth/support/filter/AuthSecurityConfigProperties.java ================================================ package com.pig4cloud.pig.auth.support.filter; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.stereotype.Component; import java.util.List; /** * 安全认证配置属性类 * *

* 用于配置网关安全相关属性 *

* * @author lengleng * @date 2025/05/30 * @since 2020/10/4 */ @Data @Component @RefreshScope @ConfigurationProperties("security") public class AuthSecurityConfigProperties { /** * 是否是微服务架构 */ private boolean isMicro; /** * 网关解密登录前端密码 秘钥 */ private String encodeKey; /** * 网关不需要校验验证码的客户端 */ private List ignoreClients; } ================================================ FILE: pig-auth/src/main/java/com/pig4cloud/pig/auth/support/filter/PasswordDecoderFilter.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.auth.support.filter; import java.io.IOException; import java.util.Map; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import com.pig4cloud.pig.common.core.constant.SecurityConstants; import com.pig4cloud.pig.common.core.servlet.RepeatBodyRequestWrapper; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.Mode; import cn.hutool.crypto.Padding; import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.symmetric.AES; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; /** * 密码解密过滤器:用于处理登录请求中的密码解密 * * @author lengleng * @date 2025/05/30 */ @Component @RequiredArgsConstructor public class PasswordDecoderFilter extends OncePerRequestFilter { private final AuthSecurityConfigProperties authSecurityConfigProperties; private static final String PASSWORD = "password"; private static final String KEY_ALGORITHM = "AES"; static { // 关闭hutool 强制关闭Bouncy Castle库的依赖 SecureUtil.disableBouncyCastle(); } /** * 过滤器内部处理逻辑,用于处理登录请求中的密码解密 * @param request HTTP请求对象 * @param response HTTP响应对象 * @param chain 过滤器链 * @throws ServletException 如果发生servlet相关异常 * @throws IOException 如果发生I/O异常 */ @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { // 不是登录请求,直接向下执行 if (!StrUtil.containsAnyIgnoreCase(request.getRequestURI(), SecurityConstants.OAUTH_TOKEN_URL)) { chain.doFilter(request, response); return; } // 将请求流转换为可多次读取的请求流 RepeatBodyRequestWrapper requestWrapper = new RepeatBodyRequestWrapper(request); Map parameterMap = requestWrapper.getParameterMap(); // 构建前端对应解密AES 因子 AES aes = new AES(Mode.CFB, Padding.NoPadding, new SecretKeySpec(authSecurityConfigProperties.getEncodeKey().getBytes(), KEY_ALGORITHM), new IvParameterSpec(authSecurityConfigProperties.getEncodeKey().getBytes())); parameterMap.forEach((k, v) -> { String[] values = parameterMap.get(k); if (!PASSWORD.equals(k) || ArrayUtil.isEmpty(values)) { return; } // 解密密码 String decryptPassword = aes.decryptStr(values[0]); parameterMap.put(k, new String[] { decryptPassword }); }); chain.doFilter(requestWrapper, response); } } ================================================ FILE: pig-auth/src/main/java/com/pig4cloud/pig/auth/support/filter/ValidateCodeFilter.java ================================================ package com.pig4cloud.pig.auth.support.filter; import java.io.IOException; import java.util.Optional; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import com.pig4cloud.pig.common.core.constant.CacheConstants; import com.pig4cloud.pig.common.core.constant.SecurityConstants; import com.pig4cloud.pig.common.core.exception.ValidateCodeException; import com.pig4cloud.pig.common.core.util.RedisUtils; import com.pig4cloud.pig.common.core.util.WebUtils; /** * 登录前处理器 * * @author lengleng * @date 2024/4/3 */ import cn.hutool.core.util.StrUtil; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; /** * 验证码过滤器:用于处理登录请求中的验证码校验 * * @author lengleng * @date 2025/05/30 */ @Component @RequiredArgsConstructor public class ValidateCodeFilter extends OncePerRequestFilter { private final AuthSecurityConfigProperties authSecurityConfigProperties; /** * 过滤器内部处理逻辑,用于验证码校验 * @param request HTTP请求 * @param response HTTP响应 * @param filterChain 过滤器链 * @throws ServletException Servlet异常 * @throws IOException IO异常 */ @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String requestUrl = request.getServletPath(); // 不是登录URL 请求直接跳过 if (!SecurityConstants.OAUTH_TOKEN_URL.equals(requestUrl)) { filterChain.doFilter(request, response); return; } // 如果登录URL 但是刷新token的请求,直接向下执行 String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE); if (StrUtil.equals(SecurityConstants.REFRESH_TOKEN, grantType)) { filterChain.doFilter(request, response); return; } // 如果是密码模式 && 客户端不需要校验验证码 boolean isIgnoreClient = authSecurityConfigProperties.getIgnoreClients().contains(WebUtils.getClientId()); if (StrUtil.equalsAnyIgnoreCase(grantType, SecurityConstants.PASSWORD, SecurityConstants.CLIENT_CREDENTIALS, SecurityConstants.AUTHORIZATION_CODE) && isIgnoreClient) { filterChain.doFilter(request, response); return; } // 校验验证码 1. 客户端开启验证码 2. 短信模式 try { checkCode(); filterChain.doFilter(request, response); } catch (ValidateCodeException validateCodeException) { throw new OAuth2AuthenticationException(validateCodeException.getMessage()); } } /** * 校验验证码 */ private void checkCode() throws ValidateCodeException { Optional request = WebUtils.getRequest(); String code = request.get().getParameter("code"); if (StrUtil.isBlank(code)) { throw new ValidateCodeException("验证码不能为空"); } String randomStr = request.get().getParameter("randomStr"); // https://gitee.com/log4j/pig/issues/IWA0D String mobile = request.get().getParameter("mobile"); if (StrUtil.isNotBlank(mobile)) { randomStr = mobile; } String key = CacheConstants.DEFAULT_CODE_KEY + randomStr; if (!RedisUtils.hasKey(key)) { throw new ValidateCodeException("验证码不合法"); } String saveCode = RedisUtils.get(key); if (StrUtil.isBlank(saveCode)) { RedisUtils.delete(key); throw new ValidateCodeException("验证码不合法"); } if (!StrUtil.equals(saveCode, code)) { RedisUtils.delete(key); throw new ValidateCodeException("验证码不合法"); } } } ================================================ FILE: pig-auth/src/main/java/com/pig4cloud/pig/auth/support/handler/FormAuthenticationFailureHandler.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.auth.support.handler; import cn.hutool.core.util.CharsetUtil; import cn.hutool.http.HttpUtil; import com.pig4cloud.pig.common.core.util.WebUtils; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import java.io.IOException; /** * 表单登录失败处理逻辑 * * @author lengleng * @date 2025/05/30 */ @Slf4j public class FormAuthenticationFailureHandler implements AuthenticationFailureHandler { /** * 当认证失败时调用 * @param request 认证尝试发生的请求 * @param response 响应对象 * @param exception 拒绝认证时抛出的异常 */ @Override @SneakyThrows public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) { log.debug("表单登录失败:{}", exception.getLocalizedMessage()); // 获取当前请求的context-path String contextPath = request.getContextPath(); // 构建重定向URL,加入context-path String url = HttpUtil.encodeParams( String.format("%s/token/login?error=%s", contextPath, exception.getMessage()), CharsetUtil.CHARSET_UTF_8); try { WebUtils.getResponse().sendRedirect(url); } catch (IOException e) { log.error("重定向失败", e); } } } ================================================ FILE: pig-auth/src/main/java/com/pig4cloud/pig/auth/support/handler/PigAuthenticationFailureEventHandler.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.auth.support.handler; import cn.hutool.core.util.StrUtil; import com.pig4cloud.pig.admin.api.entity.SysLog; import com.pig4cloud.pig.common.core.constant.CommonConstants; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.core.util.SpringContextHolder; import com.pig4cloud.pig.common.log.event.SysLogEvent; import com.pig4cloud.pig.common.log.util.LogTypeEnum; import com.pig4cloud.pig.common.log.util.SysLogUtils; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import java.io.IOException; /** * 认证失败处理器:处理用户认证失败事件并记录日志 * * @author lengleng * @date 2025/05/30 */ @Slf4j public class PigAuthenticationFailureEventHandler implements AuthenticationFailureHandler { private final MappingJackson2HttpMessageConverter errorHttpResponseConverter = new MappingJackson2HttpMessageConverter(); /** * 当认证失败时调用 * @param request 认证请求 * @param response 认证响应 * @param exception 认证失败的异常 */ @Override @SneakyThrows public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) { String username = request.getParameter(OAuth2ParameterNames.USERNAME); log.info("用户:{} 登录失败,异常:{}", username, exception.getLocalizedMessage()); SysLog logVo = SysLogUtils.getSysLog(); logVo.setTitle("登录失败"); logVo.setLogType(LogTypeEnum.ERROR.getType()); logVo.setException(exception.getLocalizedMessage()); // 发送异步日志事件 String startTimeStr = request.getHeader(CommonConstants.REQUEST_START_TIME); if (StrUtil.isNotBlank(startTimeStr)) { Long startTime = Long.parseLong(startTimeStr); Long endTime = System.currentTimeMillis(); logVo.setTime(endTime - startTime); } logVo.setCreateBy(username); SpringContextHolder.publishEvent(new SysLogEvent(logVo)); // 写出错误信息 sendErrorResponse(request, response, exception); } /** * 发送错误响应 * @param request HTTP请求 * @param response HTTP响应 * @param exception 认证异常 * @throws IOException 写入响应时发生IO异常 */ private void sendErrorResponse(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException { ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); httpResponse.setStatusCode(HttpStatus.UNAUTHORIZED); String errorMessage; if (exception instanceof OAuth2AuthenticationException) { OAuth2AuthenticationException authorizationException = (OAuth2AuthenticationException) exception; errorMessage = StrUtil.isBlank(authorizationException.getError().getDescription()) ? authorizationException.getError().getErrorCode() : authorizationException.getError().getDescription(); } else { errorMessage = exception.getLocalizedMessage(); } this.errorHttpResponseConverter.write(R.failed(errorMessage), MediaType.APPLICATION_JSON, httpResponse); } } ================================================ FILE: pig-auth/src/main/java/com/pig4cloud/pig/auth/support/handler/PigAuthenticationSuccessEventHandler.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.auth.support.handler; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import com.pig4cloud.pig.admin.api.entity.SysLog; import com.pig4cloud.pig.common.core.constant.CommonConstants; import com.pig4cloud.pig.common.core.constant.SecurityConstants; import com.pig4cloud.pig.common.core.util.SpringContextHolder; import com.pig4cloud.pig.common.log.event.SysLogEvent; import com.pig4cloud.pig.common.log.util.SysLogUtils; import com.pig4cloud.pig.common.security.component.PigCustomOAuth2AccessTokenResponseHttpMessageConverter; import com.pig4cloud.pig.common.security.service.PigUser; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2RefreshToken; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.util.CollectionUtils; import java.io.IOException; import java.time.temporal.ChronoUnit; import java.util.Map; /** * 处理认证成功事件的处理器 * * @author lengleng * @date 2025/05/30 */ @Slf4j public class PigAuthenticationSuccessEventHandler implements AuthenticationSuccessHandler { private final HttpMessageConverter accessTokenHttpResponseConverter = new PigCustomOAuth2AccessTokenResponseHttpMessageConverter(); /** * 用户认证成功时调用 * @param request 触发认证成功的请求 * @param response 响应对象 * @param authentication 认证过程中创建的认证对象 */ @SneakyThrows @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) authentication; Map map = accessTokenAuthentication.getAdditionalParameters(); if (MapUtil.isNotEmpty(map)) { // 发送异步日志事件 PigUser userInfo = (PigUser) map.get(SecurityConstants.DETAILS_USER); log.info("用户:{} 登录成功", userInfo.getName()); SecurityContextHolder.getContext().setAuthentication(accessTokenAuthentication); SysLog logVo = SysLogUtils.getSysLog(); logVo.setTitle("登录成功"); String startTimeStr = request.getHeader(CommonConstants.REQUEST_START_TIME); if (StrUtil.isNotBlank(startTimeStr)) { Long startTime = Long.parseLong(startTimeStr); Long endTime = System.currentTimeMillis(); logVo.setTime(endTime - startTime); } logVo.setCreateBy(userInfo.getName()); SpringContextHolder.publishEvent(new SysLogEvent(logVo)); } // 输出token sendAccessTokenResponse(request, response, authentication); } /** * 发送访问令牌响应 * @param request HTTP请求 * @param response HTTP响应 * @param authentication 认证信息 * @throws IOException 写入响应时可能抛出IO异常 */ private void sendAccessTokenResponse(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) authentication; OAuth2AccessToken accessToken = accessTokenAuthentication.getAccessToken(); OAuth2RefreshToken refreshToken = accessTokenAuthentication.getRefreshToken(); Map additionalParameters = accessTokenAuthentication.getAdditionalParameters(); OAuth2AccessTokenResponse.Builder builder = OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue()) .tokenType(accessToken.getTokenType()) .scopes(accessToken.getScopes()); if (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) { builder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt())); } if (refreshToken != null) { builder.refreshToken(refreshToken.getTokenValue()); } if (!CollectionUtils.isEmpty(additionalParameters)) { builder.additionalParameters(additionalParameters); } OAuth2AccessTokenResponse accessTokenResponse = builder.build(); ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); // 无状态 注意删除 context 上下文的信息 SecurityContextHolder.clearContext(); this.accessTokenHttpResponseConverter.write(accessTokenResponse, null, httpResponse); } } ================================================ FILE: pig-auth/src/main/java/com/pig4cloud/pig/auth/support/handler/PigLogoutSuccessEventHandler.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.auth.support.handler; import cn.hutool.core.util.StrUtil; import com.pig4cloud.pig.admin.api.entity.SysLog; import com.pig4cloud.pig.common.core.constant.CommonConstants; import com.pig4cloud.pig.common.core.util.SpringContextHolder; import com.pig4cloud.pig.common.core.util.WebUtils; import com.pig4cloud.pig.common.log.event.SysLogEvent; import com.pig4cloud.pig.common.log.util.SysLogUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationListener; import org.springframework.http.HttpHeaders; import org.springframework.security.authentication.event.LogoutSuccessEvent; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; import org.springframework.stereotype.Component; /** * 处理用户退出成功事件处理器 * * @author lengleng * @date 2025/05/30 */ @Slf4j @Component public class PigLogoutSuccessEventHandler implements ApplicationListener { /** * 处理登出成功事件 * @param event 登出成功事件 */ @Override public void onApplicationEvent(LogoutSuccessEvent event) { Authentication authentication = (Authentication) event.getSource(); if (authentication instanceof PreAuthenticatedAuthenticationToken) { handle(authentication); } } /** * 处理退出成功方法 *

* 获取到登录的authentication 对象 * @param authentication 登录对象 */ public void handle(Authentication authentication) { log.info("用户:{} 退出成功", authentication.getPrincipal()); SysLog logVo = SysLogUtils.getSysLog(); logVo.setTitle("退出成功"); // 设置对应的token WebUtils.getRequest().ifPresent(request -> { logVo.setParams(request.getHeader(HttpHeaders.AUTHORIZATION)); // 计算请求耗时 String startTimeStr = request.getHeader(CommonConstants.REQUEST_START_TIME); if (StrUtil.isNotBlank(startTimeStr)) { Long startTime = Long.parseLong(startTimeStr); Long endTime = System.currentTimeMillis(); logVo.setTime(endTime - startTime); } }); // 这边设置ServiceId if (authentication instanceof PreAuthenticatedAuthenticationToken) { logVo.setServiceId(authentication.getCredentials().toString()); } logVo.setCreateBy(authentication.getName()); // 发送异步日志事件 SpringContextHolder.publishEvent(new SysLogEvent(logVo)); } } ================================================ FILE: pig-auth/src/main/java/com/pig4cloud/pig/auth/support/handler/SsoLogoutSuccessHandler.java ================================================ package com.pig4cloud.pig.auth.support.handler; import cn.hutool.core.util.StrUtil; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.http.HttpHeaders; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import java.io.IOException; /** * SSO 登出成功处理器,根据客户端传入的跳转地址进行重定向 * * @author lengleng * @date 2025/05/30 */ public class SsoLogoutSuccessHandler implements LogoutSuccessHandler { private static final String REDIRECT_URL = "redirect_url"; /** * 登出成功处理逻辑 * @param request HTTP请求 * @param response HTTP响应 * @param authentication 认证信息 * @throws IOException 重定向失败时抛出IO异常 */ @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { if (response == null) { return; } // 获取请求参数中是否包含 回调地址 String redirectUrl = request.getParameter(REDIRECT_URL); if (StrUtil.isNotBlank(redirectUrl)) { response.sendRedirect(redirectUrl); } else if (StrUtil.isNotBlank(request.getHeader(HttpHeaders.REFERER))) { // 默认跳转referer 地址 String referer = request.getHeader(HttpHeaders.REFERER); response.sendRedirect(referer); } } } ================================================ FILE: pig-auth/src/main/java/com/pig4cloud/pig/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationConverter.java ================================================ package com.pig4cloud.pig.auth.support.password; import com.pig4cloud.pig.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationConverter; import com.pig4cloud.pig.common.security.util.OAuth2EndpointUtils; import jakarta.servlet.http.HttpServletRequest; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.OAuth2ErrorCodes; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import java.util.Map; import java.util.Set; import static com.pig4cloud.pig.common.core.constant.SecurityConstants.PASSWORD; /** * OAuth2 资源所有者密码认证转换器 * * @author lengleng * @author jumuning * @date 2025/05/30 */ public class OAuth2ResourceOwnerPasswordAuthenticationConverter extends OAuth2ResourceOwnerBaseAuthenticationConverter { /** * 支持密码模式 * @param grantType 授权类型 */ @Override public boolean support(String grantType) { return PASSWORD.equals(grantType); } /** * 构建OAuth2资源所有者密码认证令牌 * @param clientPrincipal 客户端主体认证信息 * @param requestedScopes 请求的作用域集合 * @param additionalParameters 附加参数映射 * @return 构建完成的OAuth2资源所有者密码认证令牌 */ @Override public OAuth2ResourceOwnerPasswordAuthenticationToken buildToken(Authentication clientPrincipal, Set requestedScopes, Map additionalParameters) { return new OAuth2ResourceOwnerPasswordAuthenticationToken(new AuthorizationGrantType(PASSWORD), clientPrincipal, requestedScopes, additionalParameters); } /** * 校验扩展参数 密码模式密码必须不为空 * @param request 参数列表 */ @Override public void checkParams(HttpServletRequest request) { MultiValueMap parameters = OAuth2EndpointUtils.getParameters(request); // username (REQUIRED) String username = parameters.getFirst(OAuth2ParameterNames.USERNAME); if (!StringUtils.hasText(username) || parameters.get(OAuth2ParameterNames.USERNAME).size() != 1) { OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.USERNAME, OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); } // password (REQUIRED) String password = parameters.getFirst(OAuth2ParameterNames.PASSWORD); if (!StringUtils.hasText(password) || parameters.get(OAuth2ParameterNames.PASSWORD).size() != 1) { OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.PASSWORD, OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); } } } ================================================ FILE: pig-auth/src/main/java/com/pig4cloud/pig/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationProvider.java ================================================ package com.pig4cloud.pig.auth.support.password; import com.pig4cloud.pig.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationProvider; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2ErrorCodes; import org.springframework.security.oauth2.core.OAuth2Token; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; import java.util.Map; import static com.pig4cloud.pig.common.core.constant.SecurityConstants.PASSWORD; /** * OAuth2 资源所有者密码认证提供者 * * @author lengleng * @author jumuning * @date 2025/05/30 * @since 0.2.3 */ public class OAuth2ResourceOwnerPasswordAuthenticationProvider extends OAuth2ResourceOwnerBaseAuthenticationProvider { private static final Logger LOGGER = LogManager.getLogger(OAuth2ResourceOwnerPasswordAuthenticationProvider.class); /** * 使用提供的参数构造一个OAuth2ResourceOwnerPasswordAuthenticationProvider * @param authenticationManager 认证管理器 * @param authorizationService 授权服务 * @param tokenGenerator 令牌生成器 * @since 0.2.3 */ public OAuth2ResourceOwnerPasswordAuthenticationProvider(AuthenticationManager authenticationManager, OAuth2AuthorizationService authorizationService, OAuth2TokenGenerator tokenGenerator) { super(authenticationManager, authorizationService, tokenGenerator); } /** * 构建用户名密码认证令牌 * @param reqParameters 请求参数映射,包含用户名和密码 * @return 用户名密码认证令牌 */ @Override public UsernamePasswordAuthenticationToken buildToken(Map reqParameters) { String username = (String) reqParameters.get(OAuth2ParameterNames.USERNAME); String password = (String) reqParameters.get(OAuth2ParameterNames.PASSWORD); return new UsernamePasswordAuthenticationToken(username, password); } /** * 判断是否支持指定的认证类型 * @param authentication 待验证的认证类型 * @return 如果支持该认证类型则返回true,否则返回false */ @Override public boolean supports(Class authentication) { boolean supports = OAuth2ResourceOwnerPasswordAuthenticationToken.class.isAssignableFrom(authentication); LOGGER.debug("supports authentication=" + authentication + " returning " + supports); return supports; } /** * 检查客户端是否支持密码授权模式 * @param registeredClient 已注册的客户端 * @throws OAuth2AuthenticationException 当客户端不支持密码授权模式时抛出异常 */ @Override public void checkClient(RegisteredClient registeredClient) { assert registeredClient != null; if (!registeredClient.getAuthorizationGrantTypes().contains(new AuthorizationGrantType(PASSWORD))) { throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT); } } } ================================================ FILE: pig-auth/src/main/java/com/pig4cloud/pig/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationToken.java ================================================ package com.pig4cloud.pig.auth.support.password; import com.pig4cloud.pig.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.AuthorizationGrantType; import java.io.Serial; import java.util.Map; import java.util.Set; /** * OAuth2资源所有者密码认证令牌 * * @author lengleng * @author jumuning * @date 2025/05/30 */ public class OAuth2ResourceOwnerPasswordAuthenticationToken extends OAuth2ResourceOwnerBaseAuthenticationToken { @Serial private static final long serialVersionUID = 1L; /** * 构造OAuth2资源所有者密码认证令牌 * @param authorizationGrantType 授权类型 * @param clientPrincipal 客户端认证主体 * @param scopes 权限范围集合 * @param additionalParameters 附加参数映射 */ public OAuth2ResourceOwnerPasswordAuthenticationToken(AuthorizationGrantType authorizationGrantType, Authentication clientPrincipal, Set scopes, Map additionalParameters) { super(authorizationGrantType, clientPrincipal, scopes, additionalParameters); } } ================================================ FILE: pig-auth/src/main/java/com/pig4cloud/pig/auth/support/password/package-info.java ================================================ /** * 密码模式 */ package com.pig4cloud.pig.auth.support.password; ================================================ FILE: pig-auth/src/main/java/com/pig4cloud/pig/auth/support/sms/OAuth2ResourceOwnerSmsAuthenticationConverter.java ================================================ package com.pig4cloud.pig.auth.support.sms; import com.pig4cloud.pig.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationConverter; import com.pig4cloud.pig.common.core.constant.SecurityConstants; import com.pig4cloud.pig.common.security.util.OAuth2EndpointUtils; import jakarta.servlet.http.HttpServletRequest; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.OAuth2ErrorCodes; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import java.util.Map; import java.util.Set; /** * @author lengleng * @date 2022-05-31 * * 短信登录转换器 */ public class OAuth2ResourceOwnerSmsAuthenticationConverter extends OAuth2ResourceOwnerBaseAuthenticationConverter { /** * 是否支持此convert * @param grantType 授权类型 * @return */ @Override public boolean support(String grantType) { return SecurityConstants.MOBILE.equals(grantType); } @Override public OAuth2ResourceOwnerSmsAuthenticationToken buildToken(Authentication clientPrincipal, Set requestedScopes, Map additionalParameters) { return new OAuth2ResourceOwnerSmsAuthenticationToken(new AuthorizationGrantType(SecurityConstants.MOBILE), clientPrincipal, requestedScopes, additionalParameters); } /** * 校验扩展参数 密码模式密码必须不为空 * @param request 参数列表 */ @Override public void checkParams(HttpServletRequest request) { MultiValueMap parameters = OAuth2EndpointUtils.getParameters(request); // PHONE (REQUIRED) String phone = parameters.getFirst(SecurityConstants.SMS_PARAMETER_NAME); if (!StringUtils.hasText(phone) || parameters.get(SecurityConstants.SMS_PARAMETER_NAME).size() != 1) { OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, SecurityConstants.SMS_PARAMETER_NAME, OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); } } } ================================================ FILE: pig-auth/src/main/java/com/pig4cloud/pig/auth/support/sms/OAuth2ResourceOwnerSmsAuthenticationProvider.java ================================================ package com.pig4cloud.pig.auth.support.sms; import com.pig4cloud.pig.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationProvider; import com.pig4cloud.pig.common.core.constant.SecurityConstants; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2ErrorCodes; import org.springframework.security.oauth2.core.OAuth2Token; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; import java.util.Map; /** * @author lengleng * @date date * * 短信登录的核心处理 */ public class OAuth2ResourceOwnerSmsAuthenticationProvider extends OAuth2ResourceOwnerBaseAuthenticationProvider { private static final Logger LOGGER = LogManager.getLogger(OAuth2ResourceOwnerSmsAuthenticationProvider.class); /** * Constructs an {@code OAuth2AuthorizationCodeAuthenticationProvider} using the * provided parameters. * @param authenticationManager * @param authorizationService the authorization service * @param tokenGenerator the token generator * @since 0.2.3 */ public OAuth2ResourceOwnerSmsAuthenticationProvider(AuthenticationManager authenticationManager, OAuth2AuthorizationService authorizationService, OAuth2TokenGenerator tokenGenerator) { super(authenticationManager, authorizationService, tokenGenerator); } @Override public boolean supports(Class authentication) { boolean supports = OAuth2ResourceOwnerSmsAuthenticationToken.class.isAssignableFrom(authentication); LOGGER.debug("supports authentication=" + authentication + " returning " + supports); return supports; } @Override public void checkClient(RegisteredClient registeredClient) { assert registeredClient != null; if (!registeredClient.getAuthorizationGrantTypes() .contains(new AuthorizationGrantType(SecurityConstants.MOBILE))) { throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT); } } @Override public UsernamePasswordAuthenticationToken buildToken(Map reqParameters) { String phone = (String) reqParameters.get(SecurityConstants.SMS_PARAMETER_NAME); return new UsernamePasswordAuthenticationToken(phone, null); } } ================================================ FILE: pig-auth/src/main/java/com/pig4cloud/pig/auth/support/sms/OAuth2ResourceOwnerSmsAuthenticationToken.java ================================================ package com.pig4cloud.pig.auth.support.sms; import java.io.Serial; import java.util.Map; import java.util.Set; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.AuthorizationGrantType; import com.pig4cloud.pig.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationToken; /** * @author lengleng * @description 短信登录token信息 */ public class OAuth2ResourceOwnerSmsAuthenticationToken extends OAuth2ResourceOwnerBaseAuthenticationToken { @Serial private static final long serialVersionUID = 1L; public OAuth2ResourceOwnerSmsAuthenticationToken(AuthorizationGrantType authorizationGrantType, Authentication clientPrincipal, Set scopes, Map additionalParameters) { super(authorizationGrantType, clientPrincipal, scopes, additionalParameters); } } ================================================ FILE: pig-auth/src/main/java/com/pig4cloud/pig/auth/support/sms/package-info.java ================================================ /** * 短信模式 */ package com.pig4cloud.pig.auth.support.sms; ================================================ FILE: pig-auth/src/main/resources/application.yml ================================================ server: port: 3000 spring: application: name: @artifactId@ cloud: nacos: username: @nacos.username@ password: @nacos.password@ discovery: server-addr: ${NACOS_HOST:127.0.0.1}:${NACOS_PORT:8848} config: server-addr: ${spring.cloud.nacos.discovery.server-addr} config: import: - nacos:application-@profiles.active@.yml - nacos:${spring.application.name}-@profiles.active@.yml ================================================ FILE: pig-auth/src/main/resources/logback-spring.xml ================================================ ${CONSOLE_LOG_PATTERN} ${log.path}/debug.log ${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz 50MB 30 ${FILE_LOG_PATTERN} ${log.path}/error.log ${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz 50MB 30 ${FILE_LOG_PATTERN} ERROR ================================================ FILE: pig-auth/src/main/resources/templates/ftl/confirm.ftl ================================================ <#assign content>

应用授权确认

<#if principalName=="anonymousUser"> 未登录用户 <#else> ${principalName}

将获得以下权限:

<#list scopeList as scope>
授权后表明你已同意 服务协议
<#include "layout/base.ftl"> ================================================ FILE: pig-auth/src/main/resources/templates/ftl/layout/base.ftl ================================================ <#if title??>${title}<#else>Pig 统一身份认证</#if> <#if extraHead??>${extraHead}
<#if content??>${content}
Copyright © 2021-2025 PIGCLOUD
================================================ FILE: pig-auth/src/main/resources/templates/ftl/login.ftl ================================================ <#assign content>

安全便捷的企业级认证服务

<#include "layout/base.ftl"> ================================================ FILE: pig-boot/Dockerfile ================================================ FROM registry.cn-hangzhou.aliyuncs.com/dockerhub_mirror/java:21-anolis WORKDIR /pig-boot ARG JAR_FILE=target/pig-boot.jar COPY ${JAR_FILE} app.jar EXPOSE 9999 ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms512m -Xmx1024m -Djava.security.egd=file:/dev/./urandom" CMD sleep 60; java $JAVA_OPTS -jar app.jar ================================================ FILE: pig-boot/pom.xml ================================================ 4.0.0 com.pig4cloud pig ${revision} pig-boot jar pig 单体版本启动 com.pig4cloud pig-auth ${revision} com.pig4cloud pig-upms-biz ${revision} com.pig4cloud pig-codegen ${revision} com.pig4cloud pig-quartz ${revision} com.pig4cloud pig-common-security org.springdoc springdoc-openapi-starter-webmvc-ui org.springdoc springdoc-openapi-starter-webmvc-api com.github.xiaoymin knife4j-openapi3-ui com.pig4cloud pig-common-swagger org.springframework.boot spring-boot-starter-undertow org.springframework.boot spring-boot-maven-plugin io.fabric8 docker-maven-plugin ================================================ FILE: pig-boot/src/main/java/com/pig4cloud/pig/PigBootApplication.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig; import com.pig4cloud.pig.common.security.annotation.EnablePigResourceServer; import com.pig4cloud.pig.common.swagger.annotation.EnablePigDoc; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * 单体版本启动器,运行此模块即可启动整个系统 * * @author lengleng * @date 2025/05/30 */ @SpringBootApplication @EnablePigResourceServer @EnablePigDoc(value = "admin", isMicro = false) public class PigBootApplication { public static void main(String[] args) { SpringApplication.run(PigBootApplication.class, args); } } ================================================ FILE: pig-boot/src/main/resources/application-dev.yml ================================================ spring: cache: type: redis # 缓存类型 Redis data: redis: database: 5 host: 127.0.0.1 # 数据库相关配置 datasource: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: root url: jdbc:mysql://127.0.0.1:3306/pig?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&allowMultiQueries=true&nullCatalogMeansCurrent=true # 本地文件系统 file: local: enable: true base-path: /Users/lengleng/Downloads/img ## 登录配置 security: # 登录报文加密根密钥 ,必须是16位 encodeKey: thanks,pig4cloud # 跳过验证码校验的客户端 ignore-clients: - test # 配置文件加密根密码 jasypt: encryptor: password: pig # 加密根密码 algorithm: PBEWithMD5AndDES # 加密算法 iv-generator-classname: org.jasypt.iv.NoIvGenerator # 无向量生成器 # 短信插件配置:https://www.yuque.com/vxixfq/pig/zw8udk sms: is-print: false # 是否打印日志 config-type: yaml # 配置类型,yaml ================================================ FILE: pig-boot/src/main/resources/application.yml ================================================ server: port: 9999 # 项目端口 servlet: context-path: /admin # 项目访问路径 spring: application: name: @project.artifactId@ # 服务名称,取 pom.xml 中的 artifactId # 上传文件大小限制 servlet: multipart: max-file-size: 100MB # 单个文件最大 max-request-size: 100MB # 接收的最大请求大小 cloud: nacos: # 单机版本关闭nacos 服务发现和配置管理的能力 config: enabled: false discovery: enabled: false freemarker: # freemarker 配置,授权码模式页面渲染使用 suffix: .ftl template-loader-path: classpath:/templates/ request-context-attribute: request main: allow-bean-definition-overriding: true # 允许覆盖bean定义 profiles: active: dev # 激活dev,对应 application-dev.yml ## spring security 对外暴露接口设置(不鉴权直接可访问) security: micro: false oauth2: ignore: urls: - /webjars/** - /v3/api-docs/** - /doc.html - /swagger-ui.html - /swagger-ui/** - /swagger-resources - /code/image - /error - /token/** - /actuator/** #--------------如下配置尽量不要变动------------- # swagger 配置 swagger: token-url: ${swagger.gateway}/admin/oauth2/token # mybatis-plus 配置 mybatis-plus: mapper-locations: classpath*:/mapper/*Mapper.xml # mapper文件位置 global-config: banner: false # 是否打印 mybatis-plus banner db-config: id-type: auto # 主键类型 where-strategy: not_empty # where 条件策略 insert-strategy: not_empty # 插入策略 update-strategy: not_null # 更新策略 type-handlers-package: com.pig4cloud.pig.common.mybatis.handler # 类型处理器包 configuration: jdbc-type-for-null: 'null' # 是否设置字段为null call-setters-on-nulls: true # 是否调用set方法时传入null值 shrink-whitespaces-in-sql: true # 去掉sql中多余的空格 ================================================ FILE: pig-boot/src/main/resources/logback-spring.xml ================================================ ${CONSOLE_LOG_PATTERN} ${log.path}/debug.log ${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz 50MB 30 ${CONSOLE_LOG_PATTERN} ${log.path}/error.log ${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz 50MB 30 ${CONSOLE_LOG_PATTERN} ERROR ================================================ FILE: pig-common/pig-common-bom/pom.xml ================================================ 4.0.0 com.pig4cloud pig-common-bom ${revision} pom pig-common-bom pig cloud parent pig cloud parent 3.9.2 7.1 3.3.5 2.29.45 9.2.0 1.7.0 3.4.3 0.0.3 5.8.42 2.4 2.2.5 4.5.0 4.3.2 1.8.4 9.0.1 2.8.14 2.18.0 4.5.0 3.0.0 17 17 3.1.0 3.1 1.10 2.2.39 3.5.16 5.4.1 1.2.83_noneautotype 0.0.47 1.6.0 UTF-8 com.pig4cloud pig-common-core ${revision} com.pig4cloud pig-common-datasource ${revision} com.pig4cloud pig-common-log ${revision} com.pig4cloud pig-common-mybatis ${revision} com.pig4cloud pig-common-oss ${revision} com.pig4cloud pig-common-security ${revision} com.pig4cloud pig-common-feign ${revision} com.pig4cloud pig-common-swagger ${revision} com.pig4cloud pig-common-xss ${revision} com.pig4cloud pig-common-excel ${revision} com.pig4cloud pig-common-websocket ${revision} com.pig4cloud pig-upms-api ${revision} com.mysql mysql-connector-j ${mysql.version} org.springdoc springdoc-openapi-starter-webflux-ui ${springdoc.version} org.springdoc springdoc-openapi-starter-webmvc-ui ${springdoc.version} org.springdoc springdoc-openapi-starter-webmvc-api ${springdoc.version} io.swagger.core.v3 swagger-annotations-jakarta ${swagger.core.version} com.alibaba fastjson ${fastjson.version} org.apache.velocity velocity-engine-core ${velocity.version} org.apache.velocity.tools velocity-tools-generic ${velocity.tool.version} com.pig4cloud.plugin captcha-core ${captcha.version} com.pig4cloud.excel excel-spring-boot-starter ${excel.version} commons-io commons-io ${common.io.version} org.apache.shardingsphere shardingsphere-jdbc-core ${shardingsphere.version} com.baomidou dynamic-datasource-spring-boot3-starter ${dynamic-ds.version} org.dromara.sms4j sms4j-spring-boot-starter ${sms.version} org.springframework.cloud spring-cloud-gateway-server ${gateway.version} com.github.xiaoymin knife4j-openapi3-ui ${knife4j.version} software.amazon.awssdk s3 ${aws.version} com.alibaba.nacos nacos-client ${nacos.client.version} com.baomidou mybatis-plus-bom ${mybatis-plus.version} pom import cn.hutool hutool-bom ${hutool.version} pom import io.github.git-commit-id git-commit-id-maven-plugin ${git.commit.plugin} io.spring.javaformat spring-javaformat-maven-plugin ${spring.checkstyle.plugin} org.codehaus.mojo flatten-maven-plugin ${flatten-maven-plugin.version} resolveCiFriendliesOnly true flatten process-resources flatten flatten.clean clean clean ================================================ FILE: pig-common/pig-common-core/pom.xml ================================================ 4.0.0 com.pig4cloud pig-common ${revision} pig-common-core jar pig 公共工具类核心包 cn.hutool hutool-core org.springframework.boot spring-boot-starter-data-redis jakarta.servlet jakarta.servlet-api org.springframework.boot spring-boot-starter-validation org.springframework.boot spring-boot-starter-json org.springframework spring-webmvc provided org.springframework.cloud spring-cloud-commons ================================================ FILE: pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/config/JacksonConfiguration.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.core.config; import cn.hutool.core.date.DatePattern; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import com.pig4cloud.pig.common.core.jackson.PigJavaTimeModule; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.context.annotation.Bean; import java.time.ZoneId; import java.util.Locale; import java.util.TimeZone; /** * Jackson配置类,用于自定义Jackson的ObjectMapper配置 * * @author lengleng * @author L.cm * @author lishangbu * @date 2025/05/30 */ @AutoConfiguration @ConditionalOnClass(ObjectMapper.class) @AutoConfigureBefore(JacksonAutoConfiguration.class) public class JacksonConfiguration { /** * 自定义Jackson2ObjectMapperBuilder配置 * @return Jackson2ObjectMapperBuilderCustomizer实例,包含以下配置: 1. 设置地区为中国 2. 设置系统默认时区 3. * 设置默认日期时间格式 4. 配置Long类型序列化为字符串 5. 注册自定义时间模块 */ @Bean @ConditionalOnMissingBean public Jackson2ObjectMapperBuilderCustomizer customizer() { return builder -> { builder.locale(Locale.CHINA); builder.timeZone(TimeZone.getTimeZone(ZoneId.systemDefault())); builder.simpleDateFormat(DatePattern.NORM_DATETIME_PATTERN); builder.serializerByType(Long.class, ToStringSerializer.instance); builder.modules(new PigJavaTimeModule()); }; } } ================================================ FILE: pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/config/RedisTemplateConfiguration.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.core.config; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.*; import org.springframework.data.redis.serializer.RedisSerializer; /** * Redis 配置类 * * @author lengleng * @date 2025/05/30 */ @EnableCaching @AutoConfiguration @AutoConfigureBefore(RedisAutoConfiguration.class) public class RedisTemplateConfiguration { /** * 创建并配置RedisTemplate实例 * @param factory Redis连接工厂 * @return 配置好的RedisTemplate实例 */ @Bean @Primary public RedisTemplate redisTemplate(RedisConnectionFactory factory) { RedisTemplate redisTemplate = new RedisTemplate<>(); redisTemplate.setKeySerializer(RedisSerializer.string()); redisTemplate.setHashKeySerializer(RedisSerializer.string()); redisTemplate.setValueSerializer(RedisSerializer.java()); redisTemplate.setHashValueSerializer(RedisSerializer.java()); redisTemplate.setConnectionFactory(factory); return redisTemplate; } /** * 创建并返回HashOperations实例 * @param redisTemplate Redis模板 * @return HashOperations实例 */ @Bean public HashOperations hashOperations(RedisTemplate redisTemplate) { return redisTemplate.opsForHash(); } /** * 创建并返回用于操作Redis String类型数据的ValueOperations实例 * @param redisTemplate Redis模板,用于操作Redis * @return ValueOperations实例,提供对Redis String类型数据的操作 */ @Bean public ValueOperations valueOperations(RedisTemplate redisTemplate) { return redisTemplate.opsForValue(); } /** * 创建并返回ListOperations实例 * @param redisTemplate Redis模板 * @return ListOperations实例 */ @Bean public ListOperations listOperations(RedisTemplate redisTemplate) { return redisTemplate.opsForList(); } /** * 创建并返回SetOperations实例 * @param redisTemplate Redis模板 * @return SetOperations实例 */ @Bean public SetOperations setOperations(RedisTemplate redisTemplate) { return redisTemplate.opsForSet(); } /** * 创建并返回ZSetOperations实例 * @param redisTemplate Redis模板对象 * @return ZSetOperations实例 */ @Bean public ZSetOperations zSetOperations(RedisTemplate redisTemplate) { return redisTemplate.opsForZSet(); } } ================================================ FILE: pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/config/RestTemplateConfiguration.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.core.config; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestClient; import org.springframework.web.client.RestTemplate; /** * RestTemplate 自动配置类 * * @author lengleng * @date 2025/05/30 */ @AutoConfiguration public class RestTemplateConfiguration { /** * 创建动态REST模板 * @return {@link RestTemplate} REST模板实例 */ @Bean @LoadBalanced @ConditionalOnProperty(value = "spring.cloud.nacos.discovery.enabled", havingValue = "true", matchIfMissing = true) public RestTemplate restTemplate() { return new RestTemplate(); } /** * 创建支持负载均衡的REST客户端构建器 * @return {@link RestClient.Builder} REST客户端构建器 */ @Bean @LoadBalanced @ConditionalOnProperty(value = "spring.cloud.nacos.discovery.enabled", havingValue = "true", matchIfMissing = true) RestClient.Builder restClientBuilder() { return RestClient.builder(); } } ================================================ FILE: pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/config/WebMvcConfiguration.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.core.config; import cn.hutool.core.date.DatePattern; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.context.MessageSource; import org.springframework.context.annotation.Bean; import org.springframework.context.support.ReloadableResourceBundleMessageSource; import org.springframework.format.FormatterRegistry; import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import static org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type.SERVLET; /** * WebMvc配置类:用于自定义Spring MVC配置 *

* 包含GET请求参数时间类型转换和系统国际化配置 * * @author lengleng * @date 2025/05/30 */ @AutoConfiguration @ConditionalOnWebApplication(type = SERVLET) public class WebMvcConfiguration implements WebMvcConfigurer { /** * 增加GET请求参数中时间类型转换 * @param registry 格式化注册器 */ @Override public void addFormatters(FormatterRegistry registry) { DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar(); registrar.setTimeFormatter(DatePattern.NORM_TIME_FORMATTER); registrar.setDateFormatter(DatePattern.NORM_DATE_FORMATTER); registrar.setDateTimeFormatter(DatePattern.NORM_DATETIME_FORMATTER); registrar.registerFormatters(registry); } /** * 创建并配置国际化消息源 * @return 可重载的资源包消息源 */ @Bean public MessageSource messageSource() { ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); messageSource.setBasename("classpath:i18n/messages"); return messageSource; } } ================================================ FILE: pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/CacheConstants.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.core.constant; /** * @author lengleng * @date 2020年01月01日 *

* 缓存的key 常量 */ public interface CacheConstants { /** * oauth 缓存前缀 */ String PROJECT_OAUTH_ACCESS = "token::access_token"; /** * 验证码前缀 */ String DEFAULT_CODE_KEY = "DEFAULT_CODE_KEY:"; /** * 菜单信息缓存 */ String MENU_DETAILS = "menu_details"; /** * 用户信息缓存 */ String USER_DETAILS = "user_details"; /** * 字典信息缓存 */ String DICT_DETAILS = "dict_details"; /** * 角色信息缓存 */ String ROLE_DETAILS = "role_details"; /** * oauth 客户端信息 */ String CLIENT_DETAILS_KEY = "client:details"; /** * 参数缓存 */ String PARAMS_DETAILS = "params_details"; } ================================================ FILE: pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/CommonConstants.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.core.constant; /** * @author lengleng * @date 2019/2/1 */ public interface CommonConstants { /** * 删除 */ String STATUS_DEL = "1"; /** * 正常 */ String STATUS_NORMAL = "0"; /** * 锁定 */ String STATUS_LOCK = "9"; /** * 菜单树根节点 */ Long MENU_TREE_ROOT_ID = -1L; /** * 菜单 */ String MENU = "0"; /** * 编码 */ String UTF8 = "UTF-8"; /** * JSON 资源 */ String CONTENT_TYPE = "application/json; charset=utf-8"; /** * 前端工程名 */ String FRONT_END_PROJECT = "pig-ui"; /** * 后端工程名 */ String BACK_END_PROJECT = "pig"; /** * 成功标记 */ Integer SUCCESS = 0; /** * 失败标记 */ Integer FAIL = 1; /** * 当前页 */ String CURRENT = "current"; /** * size */ String SIZE = "size"; /** * 请求开始时间 */ String REQUEST_START_TIME = "REQUEST-START-TIME"; } ================================================ FILE: pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/SecurityConstants.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.core.constant; /** * @author lengleng * @date 2019/2/1 */ public interface SecurityConstants { /** * 角色前缀 */ String ROLE = "ROLE_"; /** * 前缀 */ String PROJECT_PREFIX = "pig"; /** * 项目的license */ String PROJECT_LICENSE = "https://pig4cloud.com"; /** * 内部 */ String FROM_IN = "Y"; /** * 标志 */ String FROM = "from"; /** * 默认登录URL */ String OAUTH_TOKEN_URL = "/oauth2/token"; /** * grant_type */ String REFRESH_TOKEN = "refresh_token"; /** * password 模式 */ String PASSWORD = "password"; /** * 授权码 */ String AUTHORIZATION_CODE = "authorization_code"; /** * 手机号登录 */ String MOBILE = "mobile"; /** * {bcrypt} 加密的特征码 */ String BCRYPT = "{bcrypt}"; /** * {noop} 加密的特征码 */ String NOOP = "{noop}"; /** * 用户名 */ String USERNAME = "username"; /** * 用户信息 */ String DETAILS_USER = "user_info"; /** * 用户ID */ String DETAILS_USER_ID = "user_id"; /** * 协议字段 */ String DETAILS_LICENSE = "license"; /** * 验证码有效期,默认 60秒 */ long CODE_TIME = 60; /** * 验证码长度 */ String CODE_SIZE = "6"; /** * 客户端模式 */ String CLIENT_CREDENTIALS = "client_credentials"; /** * 客户端ID */ String CLIENT_ID = "clientId"; /** * 短信登录 参数名称 */ String SMS_PARAMETER_NAME = "mobile"; /** * 授权码模式confirm */ String CUSTOM_CONSENT_PAGE_URI = "/oauth2/confirm_access"; } ================================================ FILE: pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/ServiceNameConstants.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.core.constant; /** * @author lengleng * @date 2018年06月22日16:41:01 服务名称 */ public interface ServiceNameConstants { /** * 认证服务的SERVICEID */ String AUTH_SERVICE = "pig-auth"; /** * UPMS模块 */ String UPMS_SERVICE = "pig-upms-biz"; } ================================================ FILE: pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/enums/DictTypeEnum.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.core.constant.enums; import lombok.Getter; import lombok.RequiredArgsConstructor; /** * @author lengleng * @date 2019-05-16 *

* 字典类型 */ @Getter @RequiredArgsConstructor public enum DictTypeEnum { /** * 字典类型-系统内置(不可修改) */ SYSTEM("1", "系统内置"), /** * 字典类型-业务类型 */ BIZ("0", "业务类"); /** * 类型 */ private final String type; /** * 描述 */ private final String description; } ================================================ FILE: pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/enums/LoginTypeEnum.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.core.constant.enums; import lombok.Getter; import lombok.RequiredArgsConstructor; /** * @author lengleng * @date 2018/8/15 社交登录类型 */ @Getter @RequiredArgsConstructor public enum LoginTypeEnum { /** * 账号密码登录 */ PWD("PWD", "账号密码登录"), /** * 验证码登录 */ SMS("SMS", "验证码登录"); /** * 类型 */ private final String type; /** * 描述 */ private final String description; } ================================================ FILE: pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/enums/MenuTypeEnum.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.core.constant.enums; import lombok.Getter; import lombok.RequiredArgsConstructor; /** * @author lengleng * @date 2020-02-17 *

* 菜单类型 */ @Getter @RequiredArgsConstructor public enum MenuTypeEnum { /** * 左侧菜单 */ LEFT_MENU("0", "left"), /** * 顶部菜单 */ TOP_MENU("2", "top"), /** * 按钮 */ BUTTON("1", "button"); /** * 类型 */ private final String type; /** * 描述 */ private final String description; } ================================================ FILE: pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/exception/CheckedException.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.core.exception; import lombok.NoArgsConstructor; /** * 受检异常类,继承自RuntimeException * * @author lengleng * @date 2025/05/30 */ @NoArgsConstructor public class CheckedException extends RuntimeException { private static final long serialVersionUID = 1L; public CheckedException(String message) { super(message); } public CheckedException(Throwable cause) { super(cause); } public CheckedException(String message, Throwable cause) { super(message, cause); } public CheckedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } } ================================================ FILE: pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/exception/ErrorCodes.java ================================================ package com.pig4cloud.pig.common.core.exception; /** * 错误编码 * * @author lengleng * @date 2022/3/30 */ public interface ErrorCodes { /** * 系统编码错误 */ String SYS_PARAM_CONFIG_ERROR = "sys.param.config.error"; /** * 系统内置参数不能删除 */ String SYS_PARAM_DELETE_SYSTEM = "sys.param.delete.system"; /** * 用户已存在 */ String SYS_USER_EXISTING = "sys.user.existing"; /** * 用户名已存在 */ String SYS_USER_USERNAME_EXISTING = "sys.user.username.existing"; /** * 用户原密码错误,修改失败 */ String SYS_USER_UPDATE_PASSWORDERROR = "sys.user.update.passwordError"; /** * 用户信息为空 */ String SYS_USER_USERINFO_EMPTY = "sys.user.userInfo.empty"; /** * 获取当前用户信息失败 */ String SYS_USER_QUERY_ERROR = "sys.user.query.error"; /** * 部门名称不存在 */ String SYS_DEPT_DEPTNAME_INEXISTENCE = "sys.dept.deptName.inexistence"; /** * 岗位名称不存在 */ String SYS_POST_POSTNAME_INEXISTENCE = "sys.post.postName.inexistence"; /** * 岗位名称或编码已经存在 */ String SYS_POST_NAMEORCODE_EXISTING = "sys.post.nameOrCode.existing"; /** * 角色名称不存在 */ String SYS_ROLE_ROLENAME_INEXISTENCE = "sys.role.roleName.inexistence"; /** * 角色名或角色编码已经存在 */ String SYS_ROLE_NAMEORCODE_EXISTING = "sys.role.nameOrCode.existing"; /** * 菜单存在下级节点 删除失败 */ String SYS_MENU_DELETE_EXISTING = "sys.menu.delete.existing"; /** * 系统内置字典不允许删除 */ String SYS_DICT_DELETE_SYSTEM = "sys.dict.delete.system"; /** * 系统内置字典不能修改 */ String SYS_DICT_UPDATE_SYSTEM = "sys.dict.update.system"; /** * 验证码发送频繁 */ String SYS_APP_SMS_OFTEN = "sys.app.sms.often"; /** * 验证码错误 */ String SYS_APP_SMS_ERROR = "sys.app.sms.error"; /** * 手机号未注册 */ String SYS_APP_PHONE_UNREGISTERED = "sys.app.phone.unregistered"; /** * 未注册用户的短信混合系统配置键 */ String SYS_SMS_BLEND_UNREGISTERED = "sys.app.sms.blend.unregistered"; } ================================================ FILE: pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/exception/PigDeniedException.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.core.exception; import lombok.NoArgsConstructor; /** * 授权拒绝异常类 * * @author lengleng * @date 2018/06/22 */ @NoArgsConstructor public class PigDeniedException extends RuntimeException { private static final long serialVersionUID = 1L; public PigDeniedException(String message) { super(message); } public PigDeniedException(Throwable cause) { super(cause); } public PigDeniedException(String message, Throwable cause) { super(message, cause); } public PigDeniedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } } ================================================ FILE: pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/exception/ValidateCodeException.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.core.exception; /** * 验证码异常类 * * @author lengleng * @date 2018/06/22 */ public class ValidateCodeException extends RuntimeException { private static final long serialVersionUID = -7285211528095468156L; public ValidateCodeException() { } public ValidateCodeException(String msg) { super(msg); } } ================================================ FILE: pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/factory/YamlPropertySourceFactory.java ================================================ package com.pig4cloud.pig.common.core.factory; import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; import org.springframework.core.env.PropertiesPropertySource; import org.springframework.core.env.PropertySource; import org.springframework.core.io.support.EncodedResource; import org.springframework.core.io.support.PropertySourceFactory; import org.springframework.lang.Nullable; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Properties; /** * YAML属性源工厂类:用于读取自定义YAML文件并转换为属性源 * * @author lengleng * @date 2025/05/30 */ public class YamlPropertySourceFactory implements PropertySourceFactory { /** * 创建属性源 * @param name 属性源名称,可为空 * @param resource 编码资源 * @return 属性源对象 * @throws IOException 读取资源时可能抛出IO异常 */ @Override public PropertySource createPropertySource(@Nullable String name, EncodedResource resource) throws IOException { Properties propertiesFromYaml = loadYamlIntoProperties(resource); String sourceName = name != null ? name : resource.getResource().getFilename(); return new PropertiesPropertySource(sourceName, propertiesFromYaml); } /** * 将YAML资源加载为Properties对象 * @param resource 编码后的资源对象 * @return 加载后的Properties对象 * @throws FileNotFoundException 当资源文件不存在时抛出 */ private Properties loadYamlIntoProperties(EncodedResource resource) throws FileNotFoundException { try { YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); factory.setResources(resource.getResource()); factory.afterPropertiesSet(); return factory.getObject(); } catch (IllegalStateException e) { Throwable cause = e.getCause(); if (cause instanceof FileNotFoundException) { throw (FileNotFoundException) e.getCause(); } throw e; } } } ================================================ FILE: pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/jackson/PigJavaTimeModule.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.core.jackson; import cn.hutool.core.date.DatePattern; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.datatype.jsr310.PackageVersion; import com.fasterxml.jackson.datatype.jsr310.deser.*; import com.fasterxml.jackson.datatype.jsr310.ser.*; import java.io.Serial; import java.time.*; import java.time.format.DateTimeFormatter; /** * Java 8 时间默认序列化模块 * * @author L.cm * @author lishanbu * @author lengleng * @date 2025/05/30 */ public class PigJavaTimeModule extends SimpleModule { @Serial private static final long serialVersionUID = 1L; /** * PigJavaTimeModule构造函数,用于初始化时间序列化和反序列化规则 */ public PigJavaTimeModule() { super(PackageVersion.VERSION); // ======================= 时间序列化规则 =============================== // yyyy-MM-dd HH:mm:ss this.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DatePattern.NORM_DATETIME_FORMATTER)); // yyyy-MM-dd this.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ISO_LOCAL_DATE)); // HH:mm:ss this.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ISO_LOCAL_TIME)); // Instant 类型序列化 this.addSerializer(Instant.class, InstantSerializer.INSTANCE); // Duration 类型序列化 this.addSerializer(Duration.class, DurationSerializer.INSTANCE); // ======================= 时间反序列化规则 ============================== // yyyy-MM-dd HH:mm:ss this.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DatePattern.NORM_DATETIME_FORMATTER)); // yyyy-MM-dd this.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ISO_LOCAL_DATE)); // HH:mm:ss this.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ISO_LOCAL_TIME)); // Instant 反序列化 this.addDeserializer(Instant.class, InstantDeserializer.INSTANT); // Duration 反序列化 this.addDeserializer(Duration.class, DurationDeserializer.INSTANCE); } } ================================================ FILE: pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/servlet/RepeatBodyRequestWrapper.java ================================================ /* * Copyright 2023-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.core.servlet; import jakarta.servlet.ReadListener; import jakarta.servlet.ServletInputStream; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequestWrapper; import lombok.extern.slf4j.Slf4j; import org.springframework.util.ObjectUtils; import org.springframework.util.StreamUtils; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Map; /** * Request包装类:允许body重复读取 * * @author lengleng * @date 2025/05/30 */ @Slf4j public class RepeatBodyRequestWrapper extends HttpServletRequestWrapper { private final byte[] bodyByteArray; private final Map parameterMap; public RepeatBodyRequestWrapper(HttpServletRequest request) { super(request); this.bodyByteArray = getByteBody(request); // 使用 HashMap 以便后续可以修改 this.parameterMap = new HashMap<>(request.getParameterMap()); } /** * 获取BufferedReader对象 * @return 如果bodyByteArray为空则返回null,否则返回对应的BufferedReader */ @Override public BufferedReader getReader() { return ObjectUtils.isEmpty(this.bodyByteArray) ? null : new BufferedReader(new InputStreamReader(getInputStream())); } /** * 获取Servlet输入流 * @return ServletInputStream 基于bodyByteArray的输入流 */ @Override public ServletInputStream getInputStream() { final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.bodyByteArray); return new ServletInputStream() { @Override public boolean isFinished() { return byteArrayInputStream.available() == 0; } @Override public boolean isReady() { return true; // 可以读取 } @Override public void setReadListener(ReadListener readListener) { // doNothing } @Override public int read() { return byteArrayInputStream.read(); } }; } /** * 从HttpServletRequest中获取字节数组形式的请求体 * @param request HTTP请求对象 * @return 请求体字节数组,解析失败时返回空数组 */ private static byte[] getByteBody(HttpServletRequest request) { byte[] body = new byte[0]; try { body = StreamUtils.copyToByteArray(request.getInputStream()); } catch (IOException e) { log.error("解析流中数据异常", e); } return body; } /** * 获取参数映射表 * @return 可变的参数映射表 */ @Override public Map getParameterMap() { return this.parameterMap; // 返回可变的 parameterMap } /** * 设置新的参数映射 * @param parameterMap 新的参数映射,将替换现有参数映射 */ public void setParameterMap(Map parameterMap) { this.parameterMap.clear(); this.parameterMap.putAll(parameterMap); } /** * 根据参数名获取参数值 * @param name 参数名 * @return 参数值,如果不存在则返回null */ @Override public String getParameter(String name) { String[] values = parameterMap.get(name); return (values != null && values.length > 0) ? values[0] : null; } /** * 根据参数名获取参数值数组 * @param name 参数名 * @return 参数值数组,如果不存在则返回null */ @Override public String[] getParameterValues(String name) { return parameterMap.get(name); } } ================================================ FILE: pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/util/ClassUtils.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.core.util; import lombok.experimental.UtilityClass; import org.springframework.core.BridgeMethodResolver; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.MethodParameter; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.SynthesizingMethodParameter; import org.springframework.web.method.HandlerMethod; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Method; /** * 类工具类 * * @author L.cm */ @UtilityClass public class ClassUtils extends org.springframework.util.ClassUtils { private final ParameterNameDiscoverer PARAMETERNAMEDISCOVERER = new DefaultParameterNameDiscoverer(); /** * 获取方法参数信息 * @param constructor 构造器 * @param parameterIndex 参数序号 * @return {MethodParameter} */ public MethodParameter getMethodParameter(Constructor constructor, int parameterIndex) { MethodParameter methodParameter = new SynthesizingMethodParameter(constructor, parameterIndex); methodParameter.initParameterNameDiscovery(PARAMETERNAMEDISCOVERER); return methodParameter; } /** * 获取方法参数信息 * @param method 方法 * @param parameterIndex 参数序号 * @return {MethodParameter} */ public MethodParameter getMethodParameter(Method method, int parameterIndex) { MethodParameter methodParameter = new SynthesizingMethodParameter(method, parameterIndex); methodParameter.initParameterNameDiscovery(PARAMETERNAMEDISCOVERER); return methodParameter; } /** * 获取Annotation * @param method Method * @param annotationType 注解类 * @param 泛型标记 * @return {Annotation} */ public A getAnnotation(Method method, Class annotationType) { Class targetClass = method.getDeclaringClass(); // The method may be on an interface, but we need attributes from the target // class. // If the target class is null, the method will be unchanged. Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass); // If we are dealing with method with generic parameters, find the original // method. specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod); // 先找方法,再找方法上的类 A annotation = AnnotatedElementUtils.findMergedAnnotation(specificMethod, annotationType); if (null != annotation) { return annotation; } // 获取类上面的Annotation,可能包含组合注解,故采用spring的工具类 return AnnotatedElementUtils.findMergedAnnotation(specificMethod.getDeclaringClass(), annotationType); } /** * 获取Annotation * @param handlerMethod HandlerMethod * @param annotationType 注解类 * @param 泛型标记 * @return {Annotation} */ public A getAnnotation(HandlerMethod handlerMethod, Class annotationType) { // 先找方法,再找方法上的类 A annotation = handlerMethod.getMethodAnnotation(annotationType); if (null != annotation) { return annotation; } // 获取类上面的Annotation,可能包含组合注解,故采用spring的工具类 Class beanType = handlerMethod.getBeanType(); return AnnotatedElementUtils.findMergedAnnotation(beanType, annotationType); } } ================================================ FILE: pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/util/MsgUtils.java ================================================ package com.pig4cloud.pig.common.core.util; import lombok.experimental.UtilityClass; import org.springframework.context.MessageSource; import java.util.Locale; /** * i18n 工具类 * * @author lengleng * @date 2022/3/30 */ @UtilityClass public class MsgUtils { /** * 根据错误码获取中文错误信息 * @param code 错误码 * @return 对应的中文错误信息 */ public String getMessage(String code) { MessageSource messageSource = SpringContextHolder.getBean("messageSource"); return messageSource.getMessage(code, null, Locale.CHINA); } /** * 通过错误码和参数获取中文错误信息 * @param code 错误码 * @param objects 格式化参数 * @return 格式化后的中文错误信息 */ public String getMessage(String code, Object... objects) { MessageSource messageSource = SpringContextHolder.getBean("messageSource"); return messageSource.getMessage(code, objects, Locale.CHINA); } /** * 通过错误码和参数获取中文错误信息 * @param code 错误码 * @param objects 格式化参数 * @return 格式化后的中文错误信息 */ public String getSecurityMessage(String code, Object... objects) { MessageSource messageSource = SpringContextHolder.getBean("securityMessageSource"); return messageSource.getMessage(code, objects, Locale.CHINA); } } ================================================ FILE: pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/util/R.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.core.util; import com.pig4cloud.pig.common.core.constant.CommonConstants; import lombok.*; import lombok.experimental.Accessors; import lombok.experimental.FieldNameConstants; import java.io.Serializable; /** * 响应信息主体 * * @param * @author lengleng */ @ToString @NoArgsConstructor @AllArgsConstructor @Accessors(chain = true) @FieldNameConstants public class R implements Serializable { private static final long serialVersionUID = 1L; @Getter @Setter private int code; @Getter @Setter private String msg; @Getter @Setter private T data; public static R ok() { return restResult(null, CommonConstants.SUCCESS, null); } public static R ok(T data) { return restResult(data, CommonConstants.SUCCESS, null); } public static R ok(T data, String msg) { return restResult(data, CommonConstants.SUCCESS, msg); } public static R failed() { return restResult(null, CommonConstants.FAIL, null); } public static R failed(String msg) { return restResult(null, CommonConstants.FAIL, msg); } public static R failed(T data) { return restResult(data, CommonConstants.FAIL, null); } public static R failed(T data, String msg) { return restResult(data, CommonConstants.FAIL, msg); } public static R restResult(T data, int code, String msg) { R apiResult = new R<>(); apiResult.setCode(code); apiResult.setData(data); apiResult.setMsg(msg); return apiResult; } } ================================================ FILE: pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/util/RedisUtils.java ================================================ package com.pig4cloud.pig.common.core.util; import cn.hutool.core.convert.Convert; import lombok.experimental.UtilityClass; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.*; import org.springframework.data.redis.core.script.DefaultRedisScript; import org.springframework.data.redis.core.script.RedisScript; import java.util.*; import java.util.concurrent.TimeUnit; /** * 缓存工具类,注意这里都是基于RedisTemplate 来操作的 * * @author XX * @date 2023/05/12 */ @UtilityClass public class RedisUtils { private static final Long SUCCESS = 1L; /** * 指定缓存失效时间 * @param key 键 * @param time 时间(秒) */ public boolean expire(String key, long time) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); Optional.ofNullable(redisTemplate) .filter(template -> time > 0) .ifPresent(template -> template.expire(key, time, TimeUnit.SECONDS)); return true; } /** * 根据 key 获取过期时间 * @param key 键 不能为null * @return 时间(秒) 返回0代表为永久有效 */ public long getExpire(String key) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); return Optional.ofNullable(redisTemplate) .map(template -> template.getExpire(key, TimeUnit.SECONDS)) .orElse(-1L); } /** * 查找匹配key * @param pattern key * @return / */ public List scan(String pattern) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); ScanOptions options = ScanOptions.scanOptions().match(pattern).build(); return Optional.ofNullable(redisTemplate).map(template -> { RedisConnectionFactory factory = template.getConnectionFactory(); RedisConnection rc = Objects.requireNonNull(factory).getConnection(); Cursor cursor = rc.keyCommands().scan(options); List result = new ArrayList<>(); while (cursor.hasNext()) { result.add(new String(cursor.next())); } RedisConnectionUtils.releaseConnection(rc, factory); return result; }).orElse(Collections.emptyList()); } /** * 查找匹配key (使用KEYS命令) * @param pattern key模式,支持通配符 * ? [] 等 * @return 匹配的key列表 * @apiNote 注意:KEYS命令会阻塞Redis服务器,生产环境建议使用scan方法 */ public Set keys(String pattern) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); return Optional.ofNullable(redisTemplate) .map(template -> template.keys(pattern)) .orElse(Collections.emptySet()); } /** * 分页查询 key * @param patternKey key * @param page 页码 * @param size 每页数目 * @return / */ public List findKeysForPage(String patternKey, int page, int size) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); ScanOptions options = ScanOptions.scanOptions().match(patternKey).build(); RedisConnectionFactory factory = redisTemplate.getConnectionFactory(); RedisConnection rc = Objects.requireNonNull(factory).getConnection(); Cursor cursor = rc.keyCommands().scan(options); List result = new ArrayList<>(size); int tmpIndex = 0; int fromIndex = page * size; int toIndex = page * size + size; while (cursor.hasNext()) { if (tmpIndex >= fromIndex && tmpIndex < toIndex) { result.add(new String(cursor.next())); tmpIndex++; continue; } // 获取到满足条件的数据后,就可以退出了 if (tmpIndex >= toIndex) { break; } tmpIndex++; cursor.next(); } RedisConnectionUtils.releaseConnection(rc, factory); return result; } /** * 判断key是否存在 * @param key 键 * @return true 存在 false不存在 */ public boolean hasKey(String key) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); return Optional.ofNullable(redisTemplate).map(template -> template.hasKey(key)).orElse(false); } /** * 删除缓存 * @param keys 可以传一个值 或多个 */ public void delete(String... keys) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); if (keys != null) { Arrays.stream(keys).forEach(redisTemplate::delete); } } /** * 获取锁 * @param lockKey 锁key * @param value value * @param expireTime:单位-秒 * @return boolean */ public boolean getLock(String lockKey, String value, int expireTime) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); return Optional.ofNullable(redisTemplate) .map(template -> template.opsForValue().setIfAbsent(lockKey, value, expireTime, TimeUnit.SECONDS)) .orElse(false); } /** * 释放锁 * @param lockKey 锁key * @param value value * @return boolean */ public boolean releaseLock(String lockKey, String value) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; RedisScript redisScript = new DefaultRedisScript<>(script, Long.class); return Optional.ofNullable(redisTemplate.execute(redisScript, Collections.singletonList(lockKey), value)) .map(Convert::toLong) .filter(SUCCESS::equals) .isPresent(); } // ============================String============================= /** * 普通缓存获取 * @param key 键 * @return 值 */ public T get(String key) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); return redisTemplate.opsForValue().get(key); } /** * 批量获取 * @param keys * @return */ public List multiGet(List keys) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); return redisTemplate.opsForValue().multiGet(keys); } /** * 普通缓存放入 * @param key 键 * @param value 值 * @return true成功 false失败 */ public boolean set(String key, Object value) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); Optional.ofNullable(redisTemplate).map(template -> { template.opsForValue().set(key, value); return true; }); return true; } /** * 普通缓存放入并设置时间 * @param key 键 * @param value 值 * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 * @return true成功 false 失败 */ public boolean set(String key, Object value, long time) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); return Optional.ofNullable(redisTemplate).map(template -> { if (time > 0) { template.opsForValue().set(key, value, time, TimeUnit.SECONDS); } else { template.opsForValue().set(key, value); } return true; }).orElse(false); } /** * 普通缓存放入并设置时间 * @param key 键 * @param value 值 * @param time 时间 * @param timeUnit 类型 * @return true成功 false 失败 */ public boolean set(String key, T value, long time, TimeUnit timeUnit) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); Optional.ofNullable(redisTemplate).map(template -> { if (time > 0) { template.opsForValue().set(key, value, time, timeUnit); } else { template.opsForValue().set(key, value); } return true; }); return true; } /** * 执行 Redis 命令回调 * @param callback Redis回调函数 * @return 执行结果 */ public T execute(RedisCallback callback) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); return (T) redisTemplate.execute(callback); } // ================================Map================================= /** * HashGet * @param key 键 不能为null * @param hashKey 项 不能为null * @return 值 */ public HV hget(String key, HK hashKey) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); return redisTemplate.opsForHash().get(key, hashKey); } /** * 获取hashKey对应的所有键值 * @param key 键 * @return 对应的多个键值 */ public Map hmget(String key) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); return redisTemplate.opsForHash().entries(key); } /** * HashSet * @param key 键 * @param map 对应多个键值 * @return true 成功 false 失败 */ public boolean hmset(String key, Map map) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); Optional.ofNullable(redisTemplate).map(template -> { template.opsForHash().putAll(key, map); return true; }); return true; } /** * HashSet 并设置时间 * @param key 键 * @param map 对应多个键值 * @param time 时间(秒) * @return true成功 false失败 */ public boolean hmset(String key, Map map, long time) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); Optional.ofNullable(redisTemplate).map(template -> { template.opsForHash().putAll(key, map); if (time > 0) { template.expire(key, time, TimeUnit.SECONDS); } return true; }); return true; } /** * 向一张hash表中放入数据,如果不存在将创建 * @param key 键 * @param item 项 * @param value 值 * @return true 成功 false失败 */ public boolean hset(String key, String item, Object value) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); return Optional.ofNullable(redisTemplate).map(template -> { template.opsForHash().put(key, item, value); return true; }).orElse(false); } /** * 向一张hash表中放入数据,如果不存在将创建 * @param key 键 * @param item 项 * @param value 值 * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间 * @return true 成功 false失败 */ public boolean hset(String key, String item, Object value, long time) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); return Optional.ofNullable(redisTemplate).map(template -> { template.opsForHash().put(key, item, value); if (time > 0) { template.expire(key, time, TimeUnit.SECONDS); } return true; }).orElse(false); } /** * 删除hash表中的值 * @param key 键 不能为null * @param item 项 可以使多个 不能为null */ public void hdel(String key, Object... item) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); redisTemplate.opsForHash().delete(key, item); } /** * 判断hash表中是否有该项的值 * @param key 键 不能为null * @param item 项 不能为null * @return true 存在 false不存在 */ public boolean hHasKey(String key, String item) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); return redisTemplate.opsForHash().hasKey(key, item); } /** * hash递增 如果不存在,就会创建一个 并把新增后的值返回 * @param key 键 * @param item 项 * @param by 要增加几(大于0) * @return */ public double hincr(String key, String item, double by) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); return redisTemplate.opsForHash().increment(key, item, by); } /** * hash递减 * @param key 键 * @param item 项 * @param by 要减少记(小于0) * @return */ public double hdecr(String key, String item, double by) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); return redisTemplate.opsForHash().increment(key, item, -by); } // ============================set============================= /** * 根据key获取Set中的所有值 * @param key 键 * @return */ public Set sGet(String key) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); return redisTemplate.opsForSet().members(key); } /** * 根据value从一个set中查询,是否存在 * @param key 键 * @param value 值 * @return true 存在 false不存在 */ public boolean sHasKey(String key, Object value) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); return redisTemplate.opsForSet().isMember(key, value); } /** * 将数据放入set缓存 * @param key 键 * @param values 值 可以是多个 * @return 成功个数 */ public long sSet(String key, Object... values) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); return redisTemplate.opsForSet().add(key, values); } /** * 将set数据放入缓存 * @param key 键 * @param time 时间(秒) * @param values 值 可以是多个 * @return 成功个数 */ public long sSetAndTime(String key, long time, Object... values) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); Long count = redisTemplate.opsForSet().add(key, values); if (time > 0) { expire(key, time); } return count; } /** * 获取set缓存的长度 * @param key 键 * @return */ public long sGetSetSize(String key) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); return redisTemplate.opsForSet().size(key); } /** * 移除值为value的 * @param key 键 * @param values 值 可以是多个 * @return 移除的个数 */ public long setRemove(String key, Object... values) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); Long count = redisTemplate.opsForSet().remove(key, values); return count; } /** * 获集合key1和集合key2的差集元素 * @param key 键 * @return */ public Set sDifference(String key, String otherKey) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); return redisTemplate.opsForSet().difference(key, otherKey); } // ===============================list================================= /** * 获取list缓存的内容 * @param key 键 * @param start 开始 * @param end 结束 0 到 -1代表所有值 * @return */ public List lGet(String key, long start, long end) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); return redisTemplate.opsForList().range(key, start, end); } /** * 获取list缓存的长度 * @param key 键 * @return */ public long lGetListSize(String key) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); return redisTemplate.opsForList().size(key); } /** * 通过索引 获取list中的值 * @param key 键 * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推 * @return */ public Object lGetIndex(String key, long index) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); return redisTemplate.opsForList().index(key, index); } /** * 将list放入缓存 * @param key 键 * @param value 值 * @return */ public boolean lSet(String key, Object value) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); redisTemplate.opsForList().rightPush(key, value); return true; } /** * 将list放入缓存 * @param key 键 * @param value 值 * @param time 时间(秒) * @return */ public boolean lSet(String key, Object value, long time) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); redisTemplate.opsForList().rightPush(key, value); if (time > 0) { Optional.ofNullable(redisTemplate).ifPresent(template -> template.expire(key, time, TimeUnit.SECONDS)); } return true; } /** * 将list放入缓存 * @param key 键 * @param value 值 * @return */ public boolean lSet(String key, List value) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); redisTemplate.opsForList().rightPushAll(key, value); return true; } /** * 将list放入缓存 * @param key 键 * @param value 值 * @param time 时间(秒) * @return */ public boolean lSet(String key, List value, long time) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); redisTemplate.opsForList().rightPushAll(key, value); if (time > 0) { expire(key, time); } return true; } /** * 根据索引修改list中的某条数据 * @param key 键 * @param index 索引 * @param value 值 * @return / */ public boolean lUpdateIndex(String key, long index, Object value) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); redisTemplate.opsForList().set(key, index, value); return true; } /** * 移除N个值为value * @param key 键 * @param count 移除多少个 * @param value 值 * @return 移除的个数 */ public long lRemove(String key, long count, Object value) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); return redisTemplate.opsForList().remove(key, count, value); } /** * 将zSet数据放入缓存 * @param key * @param time * @param tuples * @return */ public long zSetAndTime(String key, long time, Set> tuples) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); Long count = redisTemplate.opsForZSet().add(key, tuples); if (time > 0) { expire(key, time); } return count; } /** * Sorted set:有序集合获取 * @param key * @param min * @param max * @return */ public Set zRangeByScore(String key, double min, double max) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); ZSetOperations zset = redisTemplate.opsForZSet(); return zset.rangeByScore(key, min, max); } /** * Sorted set:有序集合获取 正序 * @param key * @param start * @param end * @return */ public Set zRange(String key, long start, long end) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); ZSetOperations zset = redisTemplate.opsForZSet(); return zset.range(key, start, end); } /** * Sorted set:有序集合获取 倒叙 * @param key * @param start * @param end * @return */ public Set zReverseRange(String key, long start, long end) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); ZSetOperations zset = redisTemplate.opsForZSet(); return zset.reverseRange(key, start, end); } /** * 获取zSet缓存的长度 * @param key 键 * @return */ public long zGetSetSize(String key) { RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class); return redisTemplate.opsForZSet().size(key); } } ================================================ FILE: pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/util/RetOps.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.common.core.util; import cn.hutool.core.util.ObjectUtil; import com.pig4cloud.pig.common.core.constant.CommonConstants; import java.util.Arrays; import java.util.Objects; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; /** * 简化{@code R} 的访问操作,例子
 * R result = R.ok(0);
 * // 使用场景1: 链式操作: 断言然后消费
 * RetOps.of(result)
 * 		.assertCode(-1,r -> new RuntimeException("error "+r.getCode()))
 * 		.assertDataNotEmpty(r -> new IllegalStateException("oops!"))
 * 		.useData(System.out::println);
 *
 * // 使用场景2: 读取原始值(data),这里返回的是Optional
 * RetOps.of(result).getData().orElse(null);
 *
 * // 使用场景3: 类型转换
 * R s = RetOps.of(result)
 *        .assertDataNotNull(r -> new IllegalStateException("nani??"))
 *        .map(i -> Integer.toHexString(i))
 *        .peek();
 * 
* * @author CJ (power4j@outlook.com) * @date 2022/5/12 * @since 4.4 */ public class RetOps { /** 状态码为成功 */ public static final Predicate> CODE_SUCCESS = r -> CommonConstants.SUCCESS == r.getCode(); /** 数据有值 */ public static final Predicate> HAS_DATA = r -> ObjectUtil.isNotEmpty(r.getData()); /** 数据有值,并且包含元素 */ public static final Predicate> HAS_ELEMENT = r -> ObjectUtil.isNotEmpty(r.getData()); /** 状态码为成功并且有值 */ public static final Predicate> DATA_AVAILABLE = CODE_SUCCESS.and(HAS_DATA); private final R original; // ~ 初始化 // =================================================================================================== RetOps(R original) { this.original = original; } public static RetOps of(R original) { return new RetOps<>(Objects.requireNonNull(original)); } // ~ 杂项方法 // =================================================================================================== /** * 观察原始值 * @return R */ public R peek() { return original; } /** * 读取{@code code}的值 * @return 返回code的值 */ public int getCode() { return original.getCode(); } /** * 读取{@code data}的值 * @return 返回 Optional 包装的data */ public Optional getData() { return Optional.ofNullable(original.getData()); } /** * 有条件地读取{@code data}的值 * @param predicate 断言函数 * @return 返回 Optional 包装的data,如果断言失败返回empty */ public Optional getDataIf(Predicate> predicate) { return predicate.test(original) ? getData() : Optional.empty(); } /** * 读取{@code msg}的值 * @return 返回Optional包装的 msg */ public Optional getMsg() { return Optional.of(original.getMsg()); } /** * 对{@code code}的值进行相等性测试 * @param value 基准值 * @return 返回ture表示相等 */ public boolean codeEquals(int value) { return original.getCode() == value; } /** * 对{@code code}的值进行相等性测试 * @param value 基准值 * @return 返回ture表示不相等 */ public boolean codeNotEquals(int value) { return !codeEquals(value); } /** * 是否成功 * @return 返回ture表示成功 * @see CommonConstants#SUCCESS */ public boolean isSuccess() { return codeEquals(CommonConstants.SUCCESS); } /** * 是否失败 * @return 返回ture表示失败 */ public boolean notSuccess() { return !isSuccess(); } // ~ 链式操作 // =================================================================================================== /** * 断言{@code code}的值 * @param expect 预期的值 * @param func 用户函数,负责创建异常对象 * @param 异常类型 * @return 返回实例,以便于继续进行链式操作 * @throws Ex 断言失败时抛出 */ public RetOps assertCode(int expect, Function, ? extends Ex> func) throws Ex { if (codeNotEquals(expect)) { throw func.apply(original); } return this; } /** * 断言成功 * @param func 用户函数,负责创建异常对象 * @param 异常类型 * @return 返回实例,以便于继续进行链式操作 * @throws Ex 断言失败时抛出 */ public RetOps assertSuccess(Function, ? extends Ex> func) throws Ex { return assertCode(CommonConstants.SUCCESS, func); } /** * 断言业务数据有值 * @param func 用户函数,负责创建异常对象 * @param 异常类型 * @return 返回实例,以便于继续进行链式操作 * @throws Ex 断言失败时抛出 */ public RetOps assertDataNotNull(Function, ? extends Ex> func) throws Ex { if (Objects.isNull(original.getData())) { throw func.apply(original); } return this; } /** * 断言业务数据有值,并且包含元素 * @param func 用户函数,负责创建异常对象 * @param 异常类型 * @return 返回实例,以便于继续进行链式操作 * @throws Ex 断言失败时抛出 */ public RetOps assertDataNotEmpty(Function, ? extends Ex> func) throws Ex { if (ObjectUtil.isNotEmpty(original.getData())) { throw func.apply(original); } return this; } /** * 对业务数据(data)转换 * @param mapper 业务数据转换函数 * @param 数据类型 * @return 返回新实例,以便于继续进行链式操作 */ public RetOps map(Function mapper) { R result = R.restResult(mapper.apply(original.getData()), original.getCode(), original.getMsg()); return of(result); } /** * 对业务数据(data)转换 * @param predicate 断言函数 * @param mapper 业务数据转换函数 * @param 数据类型 * @return 返回新实例,以便于继续进行链式操作 * @see RetOps#CODE_SUCCESS * @see RetOps#HAS_DATA * @see RetOps#HAS_ELEMENT * @see RetOps#DATA_AVAILABLE */ public RetOps mapIf(Predicate> predicate, Function mapper) { R result = R.restResult(mapper.apply(original.getData()), original.getCode(), original.getMsg()); return of(result); } // ~ 数据消费 // =================================================================================================== /** * 消费数据,注意此方法保证数据可用 * @param consumer 消费函数 */ public void useData(Consumer consumer) { consumer.accept(original.getData()); } /** * 条件消费(错误代码匹配某个值) * @param consumer 消费函数 * @param codes 错误代码集合,匹配任意一个则调用消费函数 */ public void useDataOnCode(Consumer consumer, int... codes) { useDataIf(o -> Arrays.stream(codes).filter(c -> original.getCode() == c).findFirst().isPresent(), consumer); } /** * 条件消费(错误代码表示成功) * @param consumer 消费函数 */ public void useDataIfSuccess(Consumer consumer) { useDataIf(CODE_SUCCESS, consumer); } /** * 条件消费 * @param predicate 断言函数 * @param consumer 消费函数,断言函数返回{@code true}时被调用 * @see RetOps#CODE_SUCCESS * @see RetOps#HAS_DATA * @see RetOps#HAS_ELEMENT * @see RetOps#DATA_AVAILABLE */ public void useDataIf(Predicate> predicate, Consumer consumer) { if (predicate.test(original)) { consumer.accept(original.getData()); } } } ================================================ FILE: pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/util/SpringContextHolder.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.core.util; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.DisposableBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationEvent; import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.Lazy; import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; /** * @author lengleng * @date 2019/2/1 Spring 工具类 */ @Slf4j @Service @Lazy(false) public class SpringContextHolder implements ApplicationContextAware, EnvironmentAware, DisposableBean { private static ApplicationContext applicationContext = null; private static Environment environment = null; /** * 取得存储在静态变量中的ApplicationContext. */ public static ApplicationContext getApplicationContext() { return applicationContext; } /** * 获取环境 * @return {@link Environment } */ public static Environment getEnvironment() { return environment; } /** * 实现ApplicationContextAware接口, 注入Context到静态变量中. */ @Override public void setApplicationContext(ApplicationContext applicationContext) { SpringContextHolder.applicationContext = applicationContext; } /** * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. */ public static T getBean(String name) { return (T) applicationContext.getBean(name); } /** * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型. */ public static T getBean(Class requiredType) { return applicationContext.getBean(requiredType); } /** * 清除SpringContextHolder中的ApplicationContext为Null. */ public static void clearHolder() { if (log.isDebugEnabled()) { log.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext); } applicationContext = null; } /** * 发布事件 * @param event */ public static void publishEvent(ApplicationEvent event) { if (applicationContext == null) { return; } applicationContext.publishEvent(event); } /** * 是否是微服务 * @return boolean */ public static boolean isMicro() { return environment.getProperty("spring.cloud.nacos.discovery.enabled", Boolean.class, true); } /** * 实现DisposableBean接口, 在Context关闭时清理静态变量. */ @Override @SneakyThrows public void destroy() { SpringContextHolder.clearHolder(); } @Override public void setEnvironment(Environment environment) { SpringContextHolder.environment = environment; } } ================================================ FILE: pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/util/WebUtils.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.core.util; import cn.hutool.core.codec.Base64; import com.pig4cloud.pig.common.core.exception.CheckedException; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.constraints.NotNull; import lombok.SneakyThrows; import lombok.experimental.UtilityClass; import org.springframework.http.HttpHeaders; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.method.HandlerMethod; import java.nio.charset.StandardCharsets; import java.util.Optional; /** * Miscellaneous utilities for web applications. * * @author L.cm */ @UtilityClass public class WebUtils extends org.springframework.web.util.WebUtils { private final String BASIC_ = "Basic "; /** * 判断是否ajax请求 spring ajax 返回含有 ResponseBody 或者 RestController注解 * @param handlerMethod HandlerMethod * @return 是否ajax请求 */ public boolean isBody(HandlerMethod handlerMethod) { ResponseBody responseBody = ClassUtils.getAnnotation(handlerMethod, ResponseBody.class); return responseBody != null; } /** * 读取cookie * @param name cookie name * @return cookie value */ public String getCookieVal(String name) { if (WebUtils.getRequest().isPresent()) { return getCookieVal(WebUtils.getRequest().get(), name); } return null; } /** * 读取cookie * @param request HttpServletRequest * @param name cookie name * @return cookie value */ public String getCookieVal(HttpServletRequest request, String name) { Cookie cookie = getCookie(request, name); return cookie != null ? cookie.getValue() : null; } /** * 清除 某个指定的cookie * @param response HttpServletResponse * @param key cookie key */ public void removeCookie(HttpServletResponse response, String key) { setCookie(response, key, null, 0); } /** * 设置cookie * @param response HttpServletResponse * @param name cookie name * @param value cookie value * @param maxAgeInSeconds maxage */ public void setCookie(HttpServletResponse response, String name, String value, int maxAgeInSeconds) { Cookie cookie = new Cookie(name, value); cookie.setPath("/"); cookie.setMaxAge(maxAgeInSeconds); cookie.setHttpOnly(true); response.addCookie(cookie); } /** * 获取 HttpServletRequest * @return {HttpServletRequest} */ public Optional getRequest() { return Optional .ofNullable(((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest()); } /** * 获取 HttpServletResponse * @return {HttpServletResponse} */ public HttpServletResponse getResponse() { return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse(); } /** * 从request 获取CLIENT_ID * @return */ @SneakyThrows public String getClientId(ServerHttpRequest request) { String header = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION); return splitClient(header)[0]; } @SneakyThrows public String getClientId() { if (WebUtils.getRequest().isPresent()) { String header = WebUtils.getRequest().get().getHeader(HttpHeaders.AUTHORIZATION); return splitClient(header)[0]; } return null; } @NotNull private static String[] splitClient(String header) { if (header == null || !header.startsWith(BASIC_)) { throw new CheckedException("请求头中client信息为空"); } byte[] base64Token = header.substring(6).getBytes(StandardCharsets.UTF_8); byte[] decoded; try { decoded = Base64.decode(base64Token); } catch (IllegalArgumentException e) { throw new CheckedException("Failed to decode basic authentication token"); } String token = new String(decoded, StandardCharsets.UTF_8); int delim = token.indexOf(":"); if (delim == -1) { throw new CheckedException("Invalid basic authentication token"); } return new String[] { token.substring(0, delim), token.substring(delim + 1) }; } } ================================================ FILE: pig-common/pig-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ================================================ com.pig4cloud.pig.common.core.config.JacksonConfiguration com.pig4cloud.pig.common.core.config.RedisTemplateConfiguration com.pig4cloud.pig.common.core.config.RestTemplateConfiguration com.pig4cloud.pig.common.core.util.SpringContextHolder com.pig4cloud.pig.common.core.config.WebMvcConfiguration ================================================ FILE: pig-common/pig-common-core/src/main/resources/banner.txt ================================================ ${AnsiColor.BRIGHT_YELLOW} ::::::::: ::::::::::: :::::::: :+: :+: :+: :+: :+: +:+ +:+ +:+ +:+ +#++:++#+ +#+ :#: +#+ +#+ +#+ +#+# #+# #+# #+# #+# ### ########### ######## www.pig4cloud.com Pig Microservice Architecture ${AnsiColor.DEFAULT} ================================================ FILE: pig-common/pig-common-core/src/main/resources/i18n/messages_zh_CN.properties ================================================ sys.user.update.passwordError=\u539F\u5BC6\u7801\u9519\u8BEF\uFF0C\u4FEE\u6539\u5931\u8D25 sys.user.query.error=\u83B7\u53D6\u5F53\u524D\u7528\u6237\u4FE1\u606F\u5931\u8D25 sys.user.existing=\u7528\u6237\u5DF2\u5B58\u5728 sys.user.username.existing={0} \u7528\u6237\u540D\u5DF2\u5B58\u5728 sys.user.userInfo.empty={0} \u7528\u6237\u4FE1\u606F\u4E3A\u7A7A sys.dept.deptName.inexistence={0} \u90E8\u95E8\u540D\u79F0\u4E0D\u5B58\u5728 sys.post.postName.inexistence={0} \u5C97\u4F4D\u540D\u79F0\u4E0D\u5B58\u5728 sys.post.nameOrCode.existing={0} {1} \u5C97\u4F4D\u540D\u6216\u5C97\u4F4D\u7F16\u7801\u5DF2\u7ECF\u5B58\u5728 sys.role.roleName.inexistence={0} \u89D2\u8272\u540D\u79F0\u4E0D\u5B58\u5728 sys.role.nameOrCode.existing={0} {1} \u89D2\u8272\u540D\u6216\u89D2\u8272\u7F16\u7801\u5DF2\u7ECF\u5B58\u5728 sys.param.delete.system=\u7CFB\u7EDF\u5185\u7F6E\u53C2\u6570\u4E0D\u80FD\u5220\u9664 sys.param.config.error={0} \u7CFB\u7EDF\u53C2\u6570\u914D\u7F6E\u9519\u8BEF sys.menu.delete.existing=\u83DC\u5355\u542B\u6709\u4E0B\u7EA7\u4E0D\u80FD\u5220\u9664 sys.app.sms.often=\u9A8C\u8BC1\u7801\u53D1\u9001\u8FC7\u9891\u7E41 sys.app.sms.error=\u9A8C\u8BC1\u7801\u9519\u8BEF sys.app.phone.unregistered={0} \u624B\u673A\u53F7\u672A\u6CE8\u518C sys.app.sms.blend.unregistered=\u77ED\u4FE1\u6E20\u9053\u672A\u914D\u7F6E\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458 sys.dict.delete.system=\u7CFB\u7EDF\u5185\u7F6E\u5B57\u5178\u9879\u76EE\u4E0D\u80FD\u5220\u9664 sys.dict.update.system=\u7CFB\u7EDF\u5185\u7F6E\u5B57\u5178\u9879\u76EE\u4E0D\u80FD\u4FEE\u6539 ================================================ FILE: pig-common/pig-common-core/src/main/resources/logback-spring.xml ================================================ ${CONSOLE_LOG_PATTERN} ${log.path}/debug.log ${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz 50MB 30 ${FILE_LOG_PATTERN} ${log.path}/error.log ${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz 50MB 30 ${FILE_LOG_PATTERN} ERROR ================================================ FILE: pig-common/pig-common-datasource/pom.xml ================================================ pig-common com.pig4cloud ${revision} 4.0.0 pig-common-datasource jar pig 动态切换数据源 com.baomidou dynamic-datasource-spring-boot3-starter jakarta.servlet jakarta.servlet-api ================================================ FILE: pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/DynamicDataSourceAutoConfiguration.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.common.datasource; import com.baomidou.dynamic.datasource.creator.DataSourceCreator; import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator; import com.baomidou.dynamic.datasource.creator.hikaricp.HikariDataSourceCreator; import com.baomidou.dynamic.datasource.processor.DsJakartaHeaderProcessor; import com.baomidou.dynamic.datasource.processor.DsJakartaSessionProcessor; import com.baomidou.dynamic.datasource.processor.DsProcessor; import com.baomidou.dynamic.datasource.processor.DsSpelExpressionProcessor; import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider; import com.pig4cloud.pig.common.datasource.config.*; import lombok.RequiredArgsConstructor; import org.jasypt.encryption.StringEncryptor; import org.springframework.beans.factory.BeanFactory; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.expression.BeanFactoryResolver; import java.util.ArrayList; import java.util.List; /** * 动态数据源切换配置 * * @author lengleng * @date 2020-02-06 */ @Configuration @RequiredArgsConstructor @AutoConfigureAfter(DataSourceAutoConfiguration.class) @EnableConfigurationProperties(DataSourceProperties.class) public class DynamicDataSourceAutoConfiguration { /** * 获取动态数据源提供者 * @param defaultDataSourceCreator 默认数据源创建器 * @param stringEncryptor 字符串加密器 * @param properties 数据源属性 * @return 动态数据源提供者 */ @Bean public DynamicDataSourceProvider dynamicDataSourceProvider(DefaultDataSourceCreator defaultDataSourceCreator, StringEncryptor stringEncryptor, DataSourceProperties properties) { return new JdbcDynamicDataSourceProvider(defaultDataSourceCreator, stringEncryptor, properties); } /** * 主数据源提供程序 * @param defaultDataSourceCreator 默认数据源创建者 * @param properties 性能 * @return {@link DynamicDataSourceProvider } */ @Bean public DynamicDataSourceProvider masterDataSourceProvider(DefaultDataSourceCreator defaultDataSourceCreator, DataSourceProperties properties) { return new MasterDataSourceProvider(defaultDataSourceCreator, properties); } /** * 获取默认数据源创建器 * @param druidDataSourceCreator Druid数据源创建器 * @return 默认数据源创建器 */ @Bean public DefaultDataSourceCreator defaultDataSourceCreator(HikariDataSourceCreator druidDataSourceCreator) { DefaultDataSourceCreator defaultDataSourceCreator = new DefaultDataSourceCreator(); List creators = new ArrayList<>(); creators.add(druidDataSourceCreator); defaultDataSourceCreator.setCreators(creators); return defaultDataSourceCreator; } /** * 获取数据源处理器 * @return 数据源处理器 */ @Bean public DsProcessor dsProcessor(BeanFactory beanFactory) { DsProcessor lastParamDsProcessor = new LastParamDsProcessor(); DsProcessor headerProcessor = new DsJakartaHeaderProcessor(); DsProcessor sessionProcessor = new DsJakartaSessionProcessor(); DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor(); spelExpressionProcessor.setBeanResolver(new BeanFactoryResolver(beanFactory)); lastParamDsProcessor.setNextProcessor(headerProcessor); headerProcessor.setNextProcessor(sessionProcessor); sessionProcessor.setNextProcessor(spelExpressionProcessor); return lastParamDsProcessor; } /** * 获取清除TTL数据源过滤器 * @return 清除TTL数据源过滤器 */ @Bean public ClearTtlDataSourceFilter clearTtlDsFilter() { return new ClearTtlDataSourceFilter(); } } ================================================ FILE: pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/annotation/EnableDynamicDataSource.java ================================================ package com.pig4cloud.pig.common.datasource.annotation; import com.pig4cloud.pig.common.datasource.DynamicDataSourceAutoConfiguration; import org.springframework.context.annotation.Import; import java.lang.annotation.*; /** * 开启动态数据源注解 *

* 用于启用动态数据源自动配置功能 * * @author lengleng * @date 2025/07/14 */ @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Import(DynamicDataSourceAutoConfiguration.class) public @interface EnableDynamicDataSource { } ================================================ FILE: pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/config/ClearTtlDataSourceFilter.java ================================================ package com.pig4cloud.pig.common.datasource.config; import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder; import org.springframework.core.Ordered; import org.springframework.web.filter.GenericFilterBean; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import java.io.IOException; /** * 清空上文的DS 设置避免污染当前线程 * * @author lengleng * @date 2020/12/11 */ public class ClearTtlDataSourceFilter extends GenericFilterBean implements Ordered { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { DynamicDataSourceContextHolder.clear(); filterChain.doFilter(servletRequest, servletResponse); DynamicDataSourceContextHolder.clear(); } @Override public int getOrder() { return Integer.MIN_VALUE; } } ================================================ FILE: pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/config/DataSourceProperties.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.datasource.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; /** * 数据源配置属性 * * @author lengleng * @date 2025/07/14 */ @Data @ConfigurationProperties("spring.datasource") public class DataSourceProperties { /** * 用户名 */ private String username; /** * 密码 */ private String password; /** * jdbcurl */ private String url; /** * 驱动类型 */ private String driverClassName; /** * 查询数据源的SQL */ private String queryDsSql = "select * from gen_datasource_conf where del_flag = '0'"; } ================================================ FILE: pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/config/JdbcDynamicDataSourceProvider.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.common.datasource.config; import com.baomidou.dynamic.datasource.creator.DataSourceProperty; import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator; import com.baomidou.dynamic.datasource.provider.AbstractJdbcDataSourceProvider; import com.pig4cloud.pig.common.datasource.support.DataSourceConstants; import com.pig4cloud.pig.common.datasource.util.DsConfTypeEnum; import com.pig4cloud.pig.common.datasource.util.DsJdbcUrlEnum; import lombok.extern.slf4j.Slf4j; import org.jasypt.encryption.StringEncryptor; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.HashMap; import java.util.Map; /** * JDBC动态数据源提供者:从数据源中获取配置信息 * * @author lengleng * @date 2025/07/14 */ @Slf4j public class JdbcDynamicDataSourceProvider extends AbstractJdbcDataSourceProvider { private final DataSourceProperties properties; private final StringEncryptor stringEncryptor; public JdbcDynamicDataSourceProvider(DefaultDataSourceCreator defaultDataSourceCreator, StringEncryptor stringEncryptor, DataSourceProperties properties) { super(defaultDataSourceCreator, properties.getDriverClassName(), properties.getUrl(), properties.getUsername(), properties.getPassword()); this.stringEncryptor = stringEncryptor; this.properties = properties; } /** * 执行语句获得数据源参数 * @param statement 语句 * @return 数据源参数 * @throws SQLException sql异常 */ @Override protected Map executeStmt(Statement statement) throws SQLException { Map map = new HashMap<>(8); try { ResultSet rs = statement.executeQuery(properties.getQueryDsSql()); while (rs.next()) { String name = rs.getString(DataSourceConstants.NAME); String username = rs.getString(DataSourceConstants.DS_USER_NAME); String password = rs.getString(DataSourceConstants.DS_USER_PWD); Integer confType = rs.getInt(DataSourceConstants.DS_CONFIG_TYPE); String dsType = rs.getString(DataSourceConstants.DS_TYPE); DataSourceProperty property = new DataSourceProperty(); property.setUsername(username); property.setPassword(stringEncryptor.decrypt(password)); String url; // JDBC 配置形式 DsJdbcUrlEnum urlEnum = DsJdbcUrlEnum.get(dsType); if (DsConfTypeEnum.JDBC.getType().equals(confType)) { url = rs.getString(DataSourceConstants.DS_JDBC_URL); } else { String host = rs.getString(DataSourceConstants.DS_HOST); String port = rs.getString(DataSourceConstants.DS_PORT); String dsName = rs.getString(DataSourceConstants.DS_NAME); url = String.format(urlEnum.getUrl(), host, port, dsName); } property.setUrl(url); map.put(name, property); } } catch (Exception e) { log.warn("动态数据源配置表异常:{}", e.getMessage()); } return map; } } ================================================ FILE: pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/config/LastParamDsProcessor.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.common.datasource.config; import com.baomidou.dynamic.datasource.processor.DsProcessor; import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder; import org.aopalliance.intercept.MethodInvocation; /** * 参数数据源解析 @DS("#last") * * @author lengleng * @date 2020/2/6 */ public class LastParamDsProcessor extends DsProcessor { private static final String LAST_PREFIX = "#last"; /** * 抽象匹配条件 匹配才会走当前执行器否则走下一级执行器 * @param key DS注解里的内容 * @return 是否匹配 */ @Override public boolean matches(String key) { if (key.startsWith(LAST_PREFIX)) { // https://github.com/baomidou/dynamic-datasource-spring-boot-starter/issues/213 DynamicDataSourceContextHolder.clear(); return true; } return false; } /** * 抽象最终决定数据源 * @param invocation 方法执行信息 * @param key DS注解里的内容 * @return 数据源名称 */ @Override public String doDetermineDatasource(MethodInvocation invocation, String key) { Object[] arguments = invocation.getArguments(); return String.valueOf(arguments[arguments.length - 1]); } } ================================================ FILE: pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/config/MasterDataSourceProvider.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.common.datasource.config; import com.baomidou.dynamic.datasource.creator.DataSourceProperty; import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator; import com.baomidou.dynamic.datasource.provider.AbstractDataSourceProvider; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; import static com.pig4cloud.pig.common.datasource.support.DataSourceConstants.DS_MASTER; /** * 主数据源提供者,用于保证原有配置有效性并扩展其他数据源,和原有spring.datasource配置兼容。 * * @author lengleng * @date 2025/07/14 */ public class MasterDataSourceProvider extends AbstractDataSourceProvider { private final DataSourceProperties properties; private final DefaultDataSourceCreator defaultDataSourceCreator; public MasterDataSourceProvider(DefaultDataSourceCreator defaultDataSourceCreator, DataSourceProperties properties) { super(defaultDataSourceCreator); this.properties = properties; this.defaultDataSourceCreator = defaultDataSourceCreator; } /** * 加载所有数据源 * @return 所有数据源,key为数据源名称 */ @Override public Map loadDataSources() { Map map = new HashMap<>(); // 添加默认主数据源 DataSourceProperty property = new DataSourceProperty(); property.setUsername(properties.getUsername()); property.setPassword(properties.getPassword()); property.setUrl(properties.getUrl()); map.put(DS_MASTER, defaultDataSourceCreator.createDataSource(property)); return map; } } ================================================ FILE: pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/support/DataSourceConstants.java ================================================ package com.pig4cloud.pig.common.datasource.support; /** * 数据源相关常量 * * @author lengleng * @date 2019-04-01 */ public interface DataSourceConstants { /** * 数据源名称 */ String NAME = "name"; /** * 默认数据源(master) */ String DS_MASTER = "master"; /** * jdbcurl */ String DS_JDBC_URL = "url"; /** * 配置类型 */ String DS_CONFIG_TYPE = "conf_type"; /** * 用户名 */ String DS_USER_NAME = "username"; /** * 密码 */ String DS_USER_PWD = "password"; /** * 数据库类型 */ String DS_TYPE = "ds_type"; /** * 数据库名称 */ String DS_NAME = "ds_name"; /** * 主机类型 */ String DS_HOST = "host"; /** * 端口 */ String DS_PORT = "port"; /** * 实例名称 */ String DS_INSTANCE = "instance"; } ================================================ FILE: pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/util/DsConfTypeEnum.java ================================================ package com.pig4cloud.pig.common.datasource.util; import lombok.AllArgsConstructor; import lombok.Getter; /** * 数据源配置类型 * * @author lengleng * @date 2020/12/11 */ @Getter @AllArgsConstructor public enum DsConfTypeEnum { /** * 主机链接 */ HOST(0, "主机链接"), /** * JDBC链接 */ JDBC(1, "JDBC链接"); private final Integer type; private final String description; } ================================================ FILE: pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/util/DsJdbcUrlEnum.java ================================================ package com.pig4cloud.pig.common.datasource.util; import lombok.AllArgsConstructor; import lombok.Getter; import java.util.Arrays; /** * JDBC URL 枚举 * * @author lengleng * @date 2020/12/11 */ @Getter @AllArgsConstructor public enum DsJdbcUrlEnum { /** * mysql 数据库 */ MYSQL("mysql", "jdbc:mysql://%s:%s/%s?characterEncoding=utf8" + "&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true" + "&useLegacyDatetimeCode=false&allowMultiQueries=true&allowPublicKeyRetrieval=true", "select 1", "mysql8 链接"), /** * pg 数据库 */ PG("pg", "jdbc:postgresql://%s:%s/%s", "select 1", "postgresql 链接"), /** * SQL SERVER */ MSSQL("mssql", "jdbc:sqlserver://%s:%s;database=%s;characterEncoding=UTF-8", "select 1", "sqlserver 链接"), /** * oracle */ ORACLE("oracle", "jdbc:oracle:thin:@%s:%s:%s", "select 1 from dual", "oracle 链接"), /** * db2 */ DB2("db2", "jdbc:db2://%s:%s/%s", "select 1 from sysibm.sysdummy1", "DB2 TYPE4 连接"), /** * 达梦 */ DM("dm", "jdbc:dm://%s:%s/%s", "select 1 from dual", "达梦连接"), /** * pg 数据库 */ HIGHGO("highgo", "jdbc:highgo://%s:%s/%s", "select 1", "highgo 链接"); private final String dbName; private final String url; private final String validationQuery; private final String description; public static DsJdbcUrlEnum get(String dsType) { return Arrays.stream(DsJdbcUrlEnum.values()) .filter(dsJdbcUrlEnum -> dsType.equals(dsJdbcUrlEnum.getDbName())) .findFirst() .get(); } } ================================================ FILE: pig-common/pig-common-excel/pom.xml ================================================ 4.0.0 com.pig4cloud pig-common ${revision} pig-common-excel jar excel 导入导出处理模块 com.pig4cloud pig-common-core com.pig4cloud.excel excel-spring-boot-starter ================================================ FILE: pig-common/pig-common-excel/src/main/java/com/pig4cloud/pig/common/excel/ExcelAutoConfiguration.java ================================================ package com.pig4cloud.pig.common.excel; import com.pig4cloud.pig.common.core.constant.SecurityConstants; import com.pig4cloud.pig.common.core.constant.ServiceNameConstants; import com.pig4cloud.pig.common.core.util.SpringContextHolder; import com.pig4cloud.pig.common.excel.provider.RemoteDictApiService; import com.pig4cloud.pig.common.excel.provider.RemoteDictDataProvider; import com.pig4cloud.plugin.excel.handler.DictDataProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestClient; import org.springframework.web.client.support.RestClientAdapter; import org.springframework.web.service.invoker.HttpServiceProxyFactory; import java.util.Optional; /** * Excel 自动装配类 * * @author lengleng * @date 2025/05/31 */ @AutoConfiguration public class ExcelAutoConfiguration { /** * 创建远程字典API服务实例 * @param restClientBuilderOptional RestClient构建器的可选对象 * @return {@link RemoteDictApiService} 远程字典API服务实例 */ @Bean @ConditionalOnMissingBean public RemoteDictApiService remoteDictApiService(Optional restClientBuilderOptional) { RestClient client = restClientBuilderOptional.orElseGet(RestClient::builder) .baseUrl(getBaseUrl()) .defaultHeader(SecurityConstants.FROM, SecurityConstants.FROM_IN) .build(); HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(RestClientAdapter.create(client)).build(); return factory.createClient(RemoteDictApiService.class); } /** * 创建字典数据提供程序 * @param remoteDictApiService 远程字典API服务 * @return 字典数据提供程序实例 */ @Bean @ConditionalOnMissingBean public DictDataProvider dictDataProvider(RemoteDictApiService remoteDictApiService) { return new RemoteDictDataProvider(remoteDictApiService); } /** * 获取基础URL * @return 根据当前架构模式组装的基础URL字符串 */ private String getBaseUrl() { // 根据当前架构模式,组装URL if (SpringContextHolder.isMicro()) { return String.format("http://%s", ServiceNameConstants.UPMS_SERVICE); } else { return String.format("http://%s", SpringContextHolder.getEnvironment() .resolvePlaceholders("127.0.0.1:${server.port}${server.servlet.context-path}")); } } } ================================================ FILE: pig-common/pig-common-excel/src/main/java/com/pig4cloud/pig/common/excel/provider/RemoteDictApiService.java ================================================ package com.pig4cloud.pig.common.excel.provider; import com.pig4cloud.pig.common.core.util.R; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.service.annotation.GetExchange; import java.util.List; import java.util.Map; /** * 远程字典API服务接口,基于RestClient GetExchange实现 * * @author lengleng * @date 2025/05/31 */ public interface RemoteDictApiService { /** * 根据类型获取字典数据 * @param type 字典类型 * @return 包含字典数据的响应对象,字典数据以Map列表形式返回 */ @GetExchange("/dict/remote/type/{type}") R>> getDictByType(@PathVariable String type); } ================================================ FILE: pig-common/pig-common-excel/src/main/java/com/pig4cloud/pig/common/excel/provider/RemoteDictDataProvider.java ================================================ package com.pig4cloud.pig.common.excel.provider; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.map.MapUtil; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.plugin.excel.handler.DictDataProvider; import com.pig4cloud.plugin.excel.vo.DictEnum; import lombok.RequiredArgsConstructor; import java.util.List; import java.util.Map; /** * 远程字典数据提供程序实现类 * * @author lengleng * @date 2025/05/31 */ @RequiredArgsConstructor public class RemoteDictDataProvider implements DictDataProvider { private final RemoteDictApiService remoteDictApiService; /** * 根据类型获取字典枚举数组 * @param type 字典类型 * @return 字典枚举数组,无数据时返回空数组 */ @Override public DictEnum[] getDict(String type) { R>> dictDataListR = remoteDictApiService.getDictByType(type); List> dictDataList = dictDataListR.getData(); if (CollUtil.isEmpty(dictDataList)) { return new DictEnum[0]; } // 构建 DictEnum 数组 DictEnum.Builder dictEnumBuilder = DictEnum.builder(); for (Map dictData : dictDataList) { String value = MapUtil.getStr(dictData, "value"); String label = MapUtil.getStr(dictData, "label"); dictEnumBuilder.add(value, label); } return dictEnumBuilder.build(); } } ================================================ FILE: pig-common/pig-common-excel/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ================================================ com.pig4cloud.pig.common.excel.ExcelAutoConfiguration ================================================ FILE: pig-common/pig-common-feign/pom.xml ================================================ com.pig4cloud pig-common ${revision} 4.0.0 jar pig-common-feign feign-sentinel服务降级熔断、限流组件 com.pig4cloud pig-common-core com.alibaba.cloud spring-cloud-starter-alibaba-sentinel org.springframework.cloud spring-cloud-starter-openfeign io.github.openfeign feign-okhttp org.springframework.cloud spring-cloud-starter-loadbalancer com.github.ben-manes.caffeine caffeine org.springframework.security spring-security-core org.springframework spring-webmvc ================================================ FILE: pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/PigFeignAutoConfiguration.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.feign; import com.alibaba.cloud.sentinel.feign.SentinelFeignAutoConfiguration; import com.pig4cloud.pig.common.feign.core.PigFeignInnerRequestInterceptor; import com.pig4cloud.pig.common.feign.core.PigFeignRequestCloseInterceptor; import com.pig4cloud.pig.common.feign.sentinel.ext.PigSentinelFeign; import feign.Feign; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.openfeign.PigFeignClientsRegistrar; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Scope; /** * Sentinel Feign 自动配置类 * * @author lengleng * @date 2025/05/31 */ @Configuration(proxyBeanMethods = false) @Import(PigFeignClientsRegistrar.class) @AutoConfigureBefore(SentinelFeignAutoConfiguration.class) public class PigFeignAutoConfiguration { /** * 创建Feign.Builder实例,支持Sentinel功能 * @return Feign.Builder实例 * @ConditionalOnMissingBean 当容器中不存在该类型bean时创建 * @ConditionalOnProperty 当配置feign.sentinel.enabled为true时生效 * @Scope 指定bean作用域为prototype */ @Bean @Scope("prototype") @ConditionalOnMissingBean @ConditionalOnProperty(name = "feign.sentinel.enabled") public Feign.Builder feignSentinelBuilder() { return PigSentinelFeign.builder(); } /** * 创建并返回PigFeignRequestCloseInterceptor实例 * @return PigFeignRequestCloseInterceptor实例 */ @Bean public PigFeignRequestCloseInterceptor pigFeignRequestCloseInterceptor() { return new PigFeignRequestCloseInterceptor(); } /** * 创建并返回PigFeignInnerRequestInterceptor实例 * @return PigFeignInnerRequestInterceptor 内部请求拦截器实例 */ @Bean public PigFeignInnerRequestInterceptor pigFeignInnerRequestInterceptor() { return new PigFeignInnerRequestInterceptor(); } } ================================================ FILE: pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/annotation/EnablePigFeignClients.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.feign.annotation; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.core.annotation.AliasFor; import java.lang.annotation.*; /** * 启用Pig Feign客户端注解 * * @author lengleng * @date 2025/05/31 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @EnableFeignClients public @interface EnablePigFeignClients { /** * {@link #basePackages()}属性的别名。允许更简洁的注解声明 * @return 'basePackages'数组 */ String[] value() default {}; /** * 扫描注解组件的基础包路径 *

* 与{@link #value()}互为别名且互斥 *

* 对于基于字符串的包名,可使用{@link #basePackageClasses()}作为类型安全的替代方案 * @return 基础包路径数组 */ @AliasFor(annotation = EnableFeignClients.class, attribute = "basePackages") String[] basePackages() default { "com.pig4cloud.pig" }; } ================================================ FILE: pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/annotation/NoToken.java ================================================ package com.pig4cloud.pig.common.feign.annotation; import java.lang.annotation.*; /** * 服务无token调用声明注解 *

* 只有发起方没有 token 时候才需要添加此注解, @NoToken + @Inner *

*/ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface NoToken { } ================================================ FILE: pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/core/PigFeignInnerRequestInterceptor.java ================================================ package com.pig4cloud.pig.common.feign.core; import com.pig4cloud.pig.common.core.constant.SecurityConstants; import com.pig4cloud.pig.common.feign.annotation.NoToken; import feign.RequestInterceptor; import feign.RequestTemplate; import org.springframework.core.Ordered; import java.lang.reflect.Method; /** * PigFeign 内部请求拦截器,用于处理 Feign 请求的 Token 校验 * * @author lengleng * @date 2025/05/31 */ public class PigFeignInnerRequestInterceptor implements RequestInterceptor, Ordered { /** * 为每个请求调用,使用提供的{@link RequestTemplate}方法添加数据 * @param template 请求模板 */ @Override public void apply(RequestTemplate template) { Method method = template.methodMetadata().method(); NoToken noToken = method.getAnnotation(NoToken.class); if (noToken != null) { template.header(SecurityConstants.FROM, SecurityConstants.FROM_IN); } } @Override public int getOrder() { return Integer.MIN_VALUE; } } ================================================ FILE: pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/core/PigFeignRequestCloseInterceptor.java ================================================ package com.pig4cloud.pig.common.feign.core; import feign.RequestInterceptor; import org.springframework.http.HttpHeaders; /** * Feign请求连接关闭拦截器 *

* 用于设置HTTP连接为关闭状态 * * @author lengleng * @date 2025/05/31 */ public class PigFeignRequestCloseInterceptor implements RequestInterceptor { /** * 设置连接关闭 * @param template 请求模板 */ @Override public void apply(feign.RequestTemplate template) { template.header(HttpHeaders.CONNECTION, "close"); } } ================================================ FILE: pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/sentinel/SentinelAutoConfiguration.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.common.feign.sentinel; import com.alibaba.cloud.sentinel.feign.SentinelFeignAutoConfiguration; import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.BlockExceptionHandler; import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.RequestOriginParser; import com.fasterxml.jackson.databind.ObjectMapper; import com.pig4cloud.pig.common.feign.sentinel.ext.PigSentinelFeign; import com.pig4cloud.pig.common.feign.sentinel.handle.PigUrlBlockHandler; import com.pig4cloud.pig.common.feign.sentinel.parser.PigHeaderRequestOriginParser; import feign.Feign; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; /** * Sentinel 自动配置类 * * @author lengleng * @date 2025/05/31 */ @Configuration(proxyBeanMethods = false) @AutoConfigureBefore(SentinelFeignAutoConfiguration.class) public class SentinelAutoConfiguration { /** * 创建Feign Sentinel构建器 * @return Feign.Builder实例 * @ConditionalOnMissingBean 当容器中不存在该类型bean时创建 * @ConditionalOnProperty 当配置项spring.cloud.openfeign.sentinel.enabled为true时生效 * @Scope 指定bean作用域为prototype */ @Bean @Scope("prototype") @ConditionalOnMissingBean @ConditionalOnProperty(name = "spring.cloud.openfeign.sentinel.enabled") public Feign.Builder feignSentinelBuilder() { return PigSentinelFeign.builder(); } /** * 创建默认的BlockExceptionHandler bean * @param objectMapper 对象映射器 * @return PigUrlBlockHandler实例 * @ConditionalOnMissingBean 当容器中不存在该类型bean时创建 */ @Bean @ConditionalOnMissingBean public BlockExceptionHandler blockExceptionHandler(ObjectMapper objectMapper) { return new PigUrlBlockHandler(objectMapper); } /** * 创建并返回一个RequestOriginParser bean,当容器中不存在该类型的bean时生效 * @return 默认的PigHeaderRequestOriginParser实例 */ @Bean @ConditionalOnMissingBean public RequestOriginParser requestOriginParser() { return new PigHeaderRequestOriginParser(); } } ================================================ FILE: pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/sentinel/ext/PigSentinelFeign.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.feign.sentinel.ext; import com.alibaba.cloud.sentinel.feign.SentinelContractHolder; import feign.Contract; import feign.Feign; import feign.InvocationHandlerFactory; import feign.Target; import org.springframework.beans.BeansException; import org.springframework.cloud.openfeign.FallbackFactory; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClientFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.StringUtils; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Map; /** * 支持自动降级注入的Feign构建器,重写 {@link com.alibaba.cloud.sentinel.feign.SentinelFeign} * * @author lengleng * @date 2025/05/31 */ public final class PigSentinelFeign { private PigSentinelFeign() { } public static PigSentinelFeign.Builder builder() { return new PigSentinelFeign.Builder(); } public static final class Builder extends Feign.Builder implements ApplicationContextAware { private Contract contract = new Contract.Default(); private ApplicationContext applicationContext; private FeignClientFactory feignClientFactory; @Override public Feign.Builder invocationHandlerFactory(InvocationHandlerFactory invocationHandlerFactory) { throw new UnsupportedOperationException(); } @Override public PigSentinelFeign.Builder contract(Contract contract) { this.contract = contract; return this; } @Override public Feign internalBuild() { super.invocationHandlerFactory(new InvocationHandlerFactory() { @Override public InvocationHandler create(Target target, Map dispatch) { // 查找 FeignClient 上的 降级策略 FeignClient feignClient = AnnotationUtils.findAnnotation(target.type(), FeignClient.class); Class fallback = feignClient.fallback(); Class fallbackFactory = feignClient.fallbackFactory(); String beanName = feignClient.contextId(); if (!StringUtils.hasText(beanName)) { beanName = feignClient.name(); } Object fallbackInstance; FallbackFactory fallbackFactoryInstance; if (void.class != fallback) { fallbackInstance = getFromContext(beanName, "fallback", fallback, target.type()); return new PigSentinelInvocationHandler(target, dispatch, new FallbackFactory.Default(fallbackInstance)); } if (void.class != fallbackFactory) { fallbackFactoryInstance = (FallbackFactory) getFromContext(beanName, "fallbackFactory", fallbackFactory, FallbackFactory.class); return new PigSentinelInvocationHandler(target, dispatch, fallbackFactoryInstance); } return new PigSentinelInvocationHandler(target, dispatch); } private Object getFromContext(String name, String type, Class fallbackType, Class targetType) { Object fallbackInstance = feignClientFactory.getInstance(name, fallbackType); if (fallbackInstance == null) { throw new IllegalStateException(String .format("No %s instance of type %s found for feign client %s", type, fallbackType, name)); } if (!targetType.isAssignableFrom(fallbackType)) { throw new IllegalStateException(String.format( "Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s", type, fallbackType, targetType, name)); } return fallbackInstance; } }); super.contract(new SentinelContractHolder(contract)); return super.internalBuild(); } /** * private Object getFieldValue(Object instance, String fieldName) { Field field = * ReflectionUtils.findField(instance.getClass(), fieldName); * field.setAccessible(true); try { return field.get(instance); } catch * (IllegalAccessException e) { // ignore } return null; } **/ @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; this.feignClientFactory = this.applicationContext.getBean(FeignClientFactory.class); } } } ================================================ FILE: pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/sentinel/ext/PigSentinelInvocationHandler.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.feign.sentinel.ext; import com.alibaba.cloud.sentinel.feign.SentinelContractHolder; import com.alibaba.cloud.sentinel.feign.SentinelInvocationHandler; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.Tracer; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.pig4cloud.pig.common.core.util.R; import feign.Feign; import feign.InvocationHandlerFactory; import feign.MethodMetadata; import feign.Target; import lombok.extern.slf4j.Slf4j; import org.springframework.cloud.openfeign.FallbackFactory; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.LinkedHashMap; import java.util.Map; import static feign.Util.checkNotNull; /** * 支持自动降级注入的Sentinel调用处理器,重写{@link SentinelInvocationHandler} * * @author lengleng * @date 2025/05/31 */ @Slf4j public class PigSentinelInvocationHandler implements InvocationHandler { public static final String EQUALS = "equals"; public static final String HASH_CODE = "hashCode"; public static final String TO_STRING = "toString"; private final Target target; private final Map dispatch; private FallbackFactory fallbackFactory; private Map fallbackMethodMap; PigSentinelInvocationHandler(Target target, Map dispatch, FallbackFactory fallbackFactory) { this.target = checkNotNull(target, "target"); this.dispatch = checkNotNull(dispatch, "dispatch"); this.fallbackFactory = fallbackFactory; this.fallbackMethodMap = toFallbackMethod(dispatch); } PigSentinelInvocationHandler(Target target, Map dispatch) { this.target = checkNotNull(target, "target"); this.dispatch = checkNotNull(dispatch, "dispatch"); } @Override public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { if (EQUALS.equals(method.getName())) { try { Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null; return equals(otherHandler); } catch (IllegalArgumentException e) { return false; } } else if (HASH_CODE.equals(method.getName())) { return hashCode(); } else if (TO_STRING.equals(method.getName())) { return toString(); } Object result; InvocationHandlerFactory.MethodHandler methodHandler = this.dispatch.get(method); // only handle by HardCodedTarget if (target instanceof Target.HardCodedTarget) { Target.HardCodedTarget hardCodedTarget = (Target.HardCodedTarget) target; MethodMetadata methodMetadata = SentinelContractHolder.METADATA_MAP .get(hardCodedTarget.type().getName() + Feign.configKey(hardCodedTarget.type(), method)); // resource default is HttpMethod:protocol://url if (methodMetadata == null) { result = methodHandler.invoke(args); } else { String resourceName = methodMetadata.template().method().toUpperCase() + ':' + hardCodedTarget.url() + methodMetadata.template().path(); Entry entry = null; try { ContextUtil.enter(resourceName); entry = SphU.entry(resourceName, EntryType.OUT, 1, args); result = methodHandler.invoke(args); } catch (Throwable ex) { // fallback handle if (!BlockException.isBlockException(ex)) { Tracer.trace(ex); } if (fallbackFactory != null) { try { return fallbackMethodMap.get(method).invoke(fallbackFactory.create(ex), args); } catch (IllegalAccessException e) { // shouldn't happen as method is public due to being an // interface throw new AssertionError(e); } catch (InvocationTargetException e) { throw new AssertionError(e.getCause()); } } else { // 若是R类型 执行自动降级返回R if (R.class == method.getReturnType()) { log.error("feign 服务间调用异常", ex); return R.failed(ex.getLocalizedMessage()); } else { throw ex; } } } finally { if (entry != null) { entry.exit(1, args); } ContextUtil.exit(); } } } else { // other target type using default strategy result = methodHandler.invoke(args); } return result; } @Override public boolean equals(Object obj) { if (obj instanceof SentinelInvocationHandler) { PigSentinelInvocationHandler other = (PigSentinelInvocationHandler) obj; return target.equals(other.target); } return false; } @Override public int hashCode() { return target.hashCode(); } @Override public String toString() { return target.toString(); } static Map toFallbackMethod(Map dispatch) { Map result = new LinkedHashMap<>(); for (Method method : dispatch.keySet()) { method.setAccessible(true); result.put(method, method); } return result; } } ================================================ FILE: pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/sentinel/handle/GlobalBizExceptionHandler.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.feign.sentinel.handle; import com.alibaba.csp.sentinel.Tracer; import com.pig4cloud.pig.common.core.util.R; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.core.SpringSecurityMessageSource; import org.springframework.util.Assert; import org.springframework.validation.BindException; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.resource.NoResourceFoundException; import java.util.List; /** * 全局业务异常处理器,结合Sentinel处理系统异常 *

* 注意:全局异常处理器不能作用在OAuth Server *

* * @author lengleng * @date 2025/05/31 */ @Slf4j @Order(10000) @RestControllerAdvice @ConditionalOnExpression("!'${security.oauth2.client.clientId}'.isEmpty()") public class GlobalBizExceptionHandler { /** * 全局异常. * @param e the e * @return R */ @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public R handleGlobalException(Exception e) { log.error("全局异常信息 ex={}", e.getMessage(), e); // 业务异常交由 sentinel 记录 Tracer.trace(e); return R.failed(e.getLocalizedMessage()); } /** * 处理业务校验过程中碰到的非法参数异常 该异常基本由{@link org.springframework.util.Assert}抛出 * @param exception 参数校验异常 * @return API返回结果对象包装后的错误输出结果 * @see Assert#hasLength(String, String) * @see Assert#hasText(String, String) * @see Assert#isTrue(boolean, String) * @see Assert#isNull(Object, String) * @see Assert#notNull(Object, String) */ @ExceptionHandler(IllegalArgumentException.class) @ResponseStatus(HttpStatus.OK) public R handleIllegalArgumentException(IllegalArgumentException exception) { log.error("非法参数,ex = {}", exception.getMessage(), exception); return R.failed(exception.getMessage()); } /** * AccessDeniedException * @param e the e * @return R */ @ExceptionHandler(AccessDeniedException.class) @ResponseStatus(HttpStatus.FORBIDDEN) public R handleAccessDeniedException(AccessDeniedException e) { String msg = SpringSecurityMessageSource.getAccessor() .getMessage("AbstractAccessDecisionManager.accessDenied", e.getMessage()); log.warn("拒绝授权异常信息 ex={}", msg); return R.failed(msg); } /** * validation Exception * @param exception * @return R */ @ExceptionHandler({ MethodArgumentNotValidException.class }) @ResponseStatus(HttpStatus.BAD_REQUEST) public R handleBodyValidException(MethodArgumentNotValidException exception) { List fieldErrors = exception.getBindingResult().getFieldErrors(); log.warn("参数绑定异常,ex = {}", fieldErrors.get(0).getDefaultMessage()); return R.failed(String.format("%s %s", fieldErrors.get(0).getField(), fieldErrors.get(0).getDefaultMessage())); } /** * validation Exception (以form-data形式传参) * @param exception * @return R */ @ExceptionHandler({ BindException.class }) @ResponseStatus(HttpStatus.BAD_REQUEST) public R bindExceptionHandler(BindException exception) { List fieldErrors = exception.getBindingResult().getFieldErrors(); log.warn("参数绑定异常,ex = {}", fieldErrors.get(0).getDefaultMessage()); return R.failed(fieldErrors.get(0).getDefaultMessage()); } /** * 保持和低版本请求路径不存在的行为一致 *

* [Spring Boot * 3.2.0] 404 Not Found behavior #38733 * @param exception * @return R */ @ExceptionHandler({ NoResourceFoundException.class }) @ResponseStatus(HttpStatus.NOT_FOUND) public R notFoundExceptionHandler(NoResourceFoundException exception) { log.debug("请求路径 404 {}", exception.getMessage()); return R.failed(exception.getMessage()); } } ================================================ FILE: pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/sentinel/handle/PigUrlBlockHandler.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.feign.sentinel.handle; import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.BlockExceptionHandler; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.fasterxml.jackson.databind.ObjectMapper; import com.pig4cloud.pig.common.core.util.R; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; /** * Sentinel统一降级限流策略处理器 *

* 实现BlockExceptionHandler接口,处理Sentinel限流降级异常 * * @author lengleng * @date 2025/05/31 */ @Slf4j @RequiredArgsConstructor public class PigUrlBlockHandler implements BlockExceptionHandler { private final ObjectMapper objectMapper; @Override public void handle(HttpServletRequest request, HttpServletResponse response, String resourceName, BlockException e) throws Exception { log.error("sentinel 降级 资源名称{}", resourceName, e); response.setContentType(MediaType.APPLICATION_JSON.getType()); response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value()); response.getWriter().print(objectMapper.writeValueAsString(R.failed(e.getMessage()))); } } ================================================ FILE: pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/sentinel/parser/PigHeaderRequestOriginParser.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.feign.sentinel.parser; import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.RequestOriginParser; import jakarta.servlet.http.HttpServletRequest; /** * Sentinel 请求头解析判断实现类,用于从HTTP请求头中获取Allow字段值 * * @author lengleng * @date 2025/05/31 */ public class PigHeaderRequestOriginParser implements RequestOriginParser { /** * 请求头获取allow */ private static final String ALLOW = "Allow"; /** * 解析HTTP请求中的来源信息 * @param request HTTP请求对象 * @return 解析出的来源信息 */ @Override public String parseOrigin(HttpServletRequest request) { return request.getHeader(ALLOW); } } ================================================ FILE: pig-common/pig-common-feign/src/main/java/org/springframework/cloud/openfeign/PigFeignClientsRegistrar.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package org.springframework.cloud.openfeign; import lombok.Getter; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.boot.context.annotation.ImportCandidates; import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotationMetadata; import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * Feign客户端注册器,用于自动配置Feign客户端 * * @author lengleng * @date 2025/05/31 * @see ImportBeanDefinitionRegistrar * @see BeanClassLoaderAware * @see EnvironmentAware */ public class PigFeignClientsRegistrar implements ImportBeanDefinitionRegistrar, BeanClassLoaderAware, EnvironmentAware { private final static String BASE_URL = "http://127.0.0.1:${server.port}${server.servlet.context-path}"; @Getter private ClassLoader beanClassLoader; @Getter private Environment environment; /** * 注册Bean定义 * @param metadata 注解元数据 * @param registry Bean定义注册器 */ @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { registerFeignClients(registry); } /** * 设置Bean类加载器 * @param classLoader 要设置的类加载器 */ @Override public void setBeanClassLoader(ClassLoader classLoader) { this.beanClassLoader = classLoader; } /** * 注册Feign客户端到Spring容器 * @param registry Bean定义注册器,用于注册Bean定义 */ private void registerFeignClients(BeanDefinitionRegistry registry) { List feignClients = new ArrayList<>(); // 支持 springboot 2.7 + 最新版本的配置方式 ImportCandidates.load(FeignClient.class, getBeanClassLoader()).forEach(feignClients::add); // 如果 spring.factories 里为空 if (feignClients.isEmpty()) { return; } for (String className : feignClients) { try { Class clazz = beanClassLoader.loadClass(className); AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(clazz, FeignClient.class); if (attributes == null) { continue; } // 如果是单体项目自动注入 & url 为空 Boolean isMicro = environment.getProperty("spring.cloud.nacos.discovery.enabled", Boolean.class, true); // 如果已经存在该 bean,支持原生的 Feign if (registry.containsBeanDefinition(className) && isMicro) { continue; } registerClientConfiguration(registry, getClientName(attributes), className, attributes.get("configuration")); validate(attributes); BeanDefinitionBuilder definition = BeanDefinitionBuilder .genericBeanDefinition(FeignClientFactoryBean.class); definition.addPropertyValue("url", getUrl(attributes)); definition.addPropertyValue("path", getPath(attributes)); String name = getName(attributes); definition.addPropertyValue("name", name); // 兼容最新版本的 spring-cloud-openfeign,尚未发布 StringBuilder aliasBuilder = new StringBuilder(18); if (attributes.containsKey("contextId")) { String contextId = getContextId(attributes); aliasBuilder.append(contextId); definition.addPropertyValue("contextId", contextId); } else { aliasBuilder.append(name); } definition.addPropertyValue("type", className); definition.addPropertyValue("dismiss404", Boolean.parseBoolean(String.valueOf(attributes.get("dismiss404")))); Object fallbackFactory = attributes.get("fallbackFactory"); if (fallbackFactory != null) { definition.addPropertyValue("fallbackFactory", fallbackFactory instanceof Class ? fallbackFactory : ClassUtils.resolveClassName(fallbackFactory.toString(), null)); } definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory")); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); AbstractBeanDefinition beanDefinition = definition.getBeanDefinition(); // alias String alias = aliasBuilder.append("FeignClient").toString(); // has a default, won't be null boolean primary = (Boolean) attributes.get("primary"); beanDefinition.setPrimary(primary); String qualifier = getQualifier(attributes); if (StringUtils.hasText(qualifier)) { alias = qualifier; } BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias }); BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } /** * 验证Feign客户端配置属性 * @param attributes 配置属性Map */ private void validate(Map attributes) { AnnotationAttributes annotation = AnnotationAttributes.fromMap(attributes); // This blows up if an aliased property is overspecified FeignClientsRegistrar.validateFallback(annotation.getClass("fallback")); FeignClientsRegistrar.validateFallbackFactory(annotation.getClass("fallbackFactory")); } /** * 从属性Map中获取名称 * @param attributes 属性Map * @return 解析后的名称 */ private String getName(Map attributes) { String name = (String) attributes.get("serviceId"); if (!StringUtils.hasText(name)) { name = (String) attributes.get("name"); } if (!StringUtils.hasText(name)) { name = (String) attributes.get("value"); } name = resolve(name); return FeignClientsRegistrar.getName(name); } /** * 获取上下文ID * @param attributes 属性Map * @return 解析后的上下文ID,如果contextId为空则返回名称 */ private String getContextId(Map attributes) { String contextId = (String) attributes.get("contextId"); if (!StringUtils.hasText(contextId)) { return getName(attributes); } contextId = resolve(contextId); return FeignClientsRegistrar.getName(contextId); } /** * 解析字符串中的占位符 * @param value 待解析的字符串 * @return 解析后的字符串,若输入为空则返回原值 */ private String resolve(String value) { if (StringUtils.hasText(value)) { return this.environment.resolvePlaceholders(value); } return value; } /** * 获取URL地址 * @param attributes 属性集合 * @return 解析后的URL地址,微服务环境下返回null */ private String getUrl(Map attributes) { // 如果是单体项目自动注入 & url 为空 Boolean isMicro = environment.getProperty("spring.cloud.nacos.discovery.enabled", Boolean.class, true); if (isMicro) { return null; } Object objUrl = attributes.get("url"); String url = ""; if (StringUtils.hasText(objUrl.toString())) { url = resolve(objUrl.toString()); } else { url = resolve(BASE_URL); } return FeignClientsRegistrar.getUrl(url); } /** * 获取路径 * @param attributes 属性Map,包含路径信息 * @return 解析后的路径 */ private String getPath(Map attributes) { String path = resolve((String) attributes.get("path")); return FeignClientsRegistrar.getPath(path); } /** * 从客户端信息中获取qualifier字段值 * @param client 客户端信息映射表,可能为null * @return qualifier字段值,若无有效值则返回null */ @Nullable private String getQualifier(@Nullable Map client) { if (client == null) { return null; } String qualifier = (String) client.get("qualifier"); if (StringUtils.hasText(qualifier)) { return qualifier; } return null; } /** * 获取客户端名称,依次从contextId、value、name、serviceId字段中获取 * @param client 客户端信息Map,可为null * @return 客户端名称,可能为null * @throws IllegalStateException 当无法从client中获取到有效名称时抛出 */ @Nullable private String getClientName(@Nullable Map client) { if (client == null) { return null; } String value = (String) client.get("contextId"); if (!StringUtils.hasText(value)) { value = (String) client.get("value"); } if (!StringUtils.hasText(value)) { value = (String) client.get("name"); } if (!StringUtils.hasText(value)) { value = (String) client.get("serviceId"); } if (StringUtils.hasText(value)) { return value; } throw new IllegalStateException( "Either 'name' or 'value' must be provided in @" + FeignClient.class.getSimpleName()); } /** * 注册客户端配置到BeanDefinitionRegistry * @param registry Bean定义注册器 * @param name 客户端名称 * @param className 类名 * @param configuration 配置对象 */ private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object className, Object configuration) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class); builder.addConstructorArgValue(name); builder.addConstructorArgValue(className); builder.addConstructorArgValue(configuration); registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(), builder.getBeanDefinition()); } /** * 设置环境变量 * @param environment 要设置的环境变量对象 */ @Override public void setEnvironment(Environment environment) { this.environment = environment; } } ================================================ FILE: pig-common/pig-common-feign/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ================================================ com.pig4cloud.pig.common.feign.PigFeignAutoConfiguration com.pig4cloud.pig.common.feign.sentinel.SentinelAutoConfiguration com.pig4cloud.pig.common.feign.sentinel.handle.GlobalBizExceptionHandler ================================================ FILE: pig-common/pig-common-log/pom.xml ================================================ 4.0.0 com.pig4cloud pig-common ${revision} pig-common-log jar pig 日志服务 com.pig4cloud pig-common-core cn.hutool hutool-extra cn.hutool hutool-http com.pig4cloud pig-upms-api org.springframework.security spring-security-core org.springframework.security spring-security-oauth2-core ================================================ FILE: pig-common/pig-common-log/src/main/java/com/pig4cloud/pig/common/log/LogAutoConfiguration.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.log; import com.pig4cloud.pig.admin.api.feign.RemoteLogService; import com.pig4cloud.pig.common.log.aspect.SysLogAspect; import com.pig4cloud.pig.common.log.config.PigLogProperties; import com.pig4cloud.pig.common.log.event.SysLogListener; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; /** * 日志自动配置类,用于配置系统日志相关功能 * * @author lengleng * @date 2025/05/31 */ @EnableAsync @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(PigLogProperties.class) @ConditionalOnProperty(value = "security.log.enabled", matchIfMissing = true) public class LogAutoConfiguration { /** * 创建并返回SysLogListener的Bean实例 * @param logProperties 日志属性配置 * @param remoteLogService 远程日志服务 * @return SysLogListener实例 */ @Bean public SysLogListener sysLogListener(PigLogProperties logProperties, RemoteLogService remoteLogService) { return new SysLogListener(remoteLogService, logProperties); } /** * 创建并返回SysLogAspect的Bean实例 * @return SysLogAspect实例 */ @Bean public SysLogAspect sysLogAspect() { return new SysLogAspect(); } } ================================================ FILE: pig-common/pig-common-log/src/main/java/com/pig4cloud/pig/common/log/annotation/SysLog.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.log.annotation; import java.lang.annotation.*; /** * 系统日志注解:用于标记需要记录操作日志的方法 * * @author lengleng * @date 2025/05/31 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface SysLog { /** * 描述 * @return {String} */ String value() default ""; /** * spel 表达式 * @return 日志描述 */ String expression() default ""; } ================================================ FILE: pig-common/pig-common-log/src/main/java/com/pig4cloud/pig/common/log/aspect/SysLogAspect.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.log.aspect; import cn.hutool.core.util.StrUtil; import com.pig4cloud.pig.common.core.util.SpringContextHolder; import com.pig4cloud.pig.common.log.event.SysLogEvent; import com.pig4cloud.pig.common.log.event.SysLogEventSource; import com.pig4cloud.pig.common.log.util.LogTypeEnum; import com.pig4cloud.pig.common.log.util.SysLogUtils; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; 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.springframework.expression.EvaluationContext; /** * 系统日志切面类,通过Spring AOP实现操作日志的异步记录 * * @author lengleng * @date 2025/05/31 */ @Aspect @Slf4j @RequiredArgsConstructor public class SysLogAspect { /** * 环绕通知方法,用于处理系统日志记录 * @param point 连接点对象 * @param sysLog 系统日志注解 * @return 目标方法执行结果 * @throws Throwable 目标方法执行可能抛出的异常 */ @Around("@annotation(sysLog)") @SneakyThrows public Object around(ProceedingJoinPoint point, com.pig4cloud.pig.common.log.annotation.SysLog sysLog) { String strClassName = point.getTarget().getClass().getName(); String strMethodName = point.getSignature().getName(); log.debug("[类名]:{},[方法]:{}", strClassName, strMethodName); String value = sysLog.value(); String expression = sysLog.expression(); // 当前表达式存在 SPEL,会覆盖 value 的值 if (StrUtil.isNotBlank(expression)) { // 解析SPEL MethodSignature signature = (MethodSignature) point.getSignature(); EvaluationContext context = SysLogUtils.getContext(point.getArgs(), signature.getMethod()); try { value = SysLogUtils.getValue(context, expression, String.class); } catch (Exception e) { // SPEL 表达式异常,获取 value 的值 log.error("@SysLog 解析SPEL {} 异常", expression); } } SysLogEventSource logVo = SysLogUtils.getSysLog(); logVo.setTitle(value); // 获取请求body参数 if (StrUtil.isBlank(logVo.getParams())) { logVo.setBody(point.getArgs()); } // 发送异步日志事件 Long startTime = System.currentTimeMillis(); Object obj; try { obj = point.proceed(); } catch (Exception e) { logVo.setLogType(LogTypeEnum.ERROR.getType()); logVo.setException(e.getMessage()); throw e; } finally { Long endTime = System.currentTimeMillis(); logVo.setTime(endTime - startTime); SpringContextHolder.publishEvent(new SysLogEvent(logVo)); } return obj; } } ================================================ FILE: pig-common/pig-common-log/src/main/java/com/pig4cloud/pig/common/log/config/PigLogProperties.java ================================================ /* * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net). *

* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* http://www.gnu.org/licenses/lgpl.html *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.log.config; import lombok.Getter; import lombok.Setter; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import java.util.List; /** * 日志配置类 * * @author lengleng * @date 2025/05/31 */ @Getter @Setter @ConfigurationProperties(PigLogProperties.PREFIX) public class PigLogProperties { public static final String PREFIX = "security.log"; /** * 开启日志记录 */ private boolean enabled = true; /** * 放行字段,password,mobile,idcard,phone */ @Value("${security.log.exclude-fields:password,mobile,idcard,phone}") private List excludeFields; /** * 请求报文最大存储长度 */ private Integer maxLength = 2000; } ================================================ FILE: pig-common/pig-common-log/src/main/java/com/pig4cloud/pig/common/log/event/SysLogEvent.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.log.event; import java.io.Serial; import org.springframework.context.ApplicationEvent; import com.pig4cloud.pig.admin.api.entity.SysLog; /** * 系统日志事件类 * * @author lengleng * @date 2025/05/31 */ public class SysLogEvent extends ApplicationEvent { @Serial private static final long serialVersionUID = 1L; /** * 构造方法,根据源SysLog对象创建SysLogEvent * @param source 源SysLog对象 */ public SysLogEvent(SysLog source) { super(source); } } ================================================ FILE: pig-common/pig-common-log/src/main/java/com/pig4cloud/pig/common/log/event/SysLogEventSource.java ================================================ package com.pig4cloud.pig.common.log.event; import java.io.Serial; import com.pig4cloud.pig.admin.api.entity.SysLog; import lombok.Data; import lombok.EqualsAndHashCode; /** * 系统日志事件源类,继承自SysLog * * @author lengleng * @date 2025/05/31 */ @Data @EqualsAndHashCode(callSuper = false) public class SysLogEventSource extends SysLog { @Serial private static final long serialVersionUID = 1L; /** * 参数重写成object */ private Object body; } ================================================ FILE: pig-common/pig-common-log/src/main/java/com/pig4cloud/pig/common/log/event/SysLogListener.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.log.event; import java.util.Objects; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.event.EventListener; import org.springframework.core.annotation.Order; import org.springframework.scheduling.annotation.Async; import com.fasterxml.jackson.annotation.JsonFilter; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ser.FilterProvider; import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; import com.pig4cloud.pig.admin.api.entity.SysLog; import com.pig4cloud.pig.admin.api.feign.RemoteLogService; import com.pig4cloud.pig.common.core.jackson.PigJavaTimeModule; import com.pig4cloud.pig.common.log.config.PigLogProperties; import cn.hutool.core.util.StrUtil; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; /** * 系统日志监听器:异步处理系统日志事件 * * @author lengleng * @date 2025/05/31 */ @RequiredArgsConstructor public class SysLogListener implements InitializingBean { // new 一个 避免日志脱敏策略影响全局ObjectMapper private final static ObjectMapper objectMapper = new ObjectMapper(); private final RemoteLogService remoteLogService; private final PigLogProperties logProperties; /** * 异步保存系统日志 * @param event 系统日志事件 */ @SneakyThrows @Async @Order @EventListener(SysLogEvent.class) public void saveSysLog(SysLogEvent event) { SysLogEventSource source = (SysLogEventSource) event.getSource(); SysLog sysLog = new SysLog(); BeanUtils.copyProperties(source, sysLog); // json 格式刷参数放在异步中处理,提升性能 if (Objects.nonNull(source.getBody())) { String params = objectMapper.writeValueAsString(source.getBody()); sysLog.setParams(StrUtil.subPre(params, logProperties.getMaxLength())); } remoteLogService.saveLog(sysLog); } @Override public void afterPropertiesSet() { objectMapper.addMixIn(Object.class, PropertyFilterMixIn.class); String[] ignorableFieldNames = logProperties.getExcludeFields().toArray(new String[0]); FilterProvider filters = new SimpleFilterProvider().addFilter("filter properties by name", SimpleBeanPropertyFilter.serializeAllExcept(ignorableFieldNames)); objectMapper.setFilterProvider(filters); objectMapper.registerModule(new PigJavaTimeModule()); } /** * 属性过滤混合类:用于通过名称过滤属性 * * @author lengleng * @date 2025/05/31 */ @JsonFilter("filter properties by name") class PropertyFilterMixIn { } } ================================================ FILE: pig-common/pig-common-log/src/main/java/com/pig4cloud/pig/common/log/init/ApplicationLoggerInitializer.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.log.init; import org.springframework.boot.SpringApplication; import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.core.Ordered; import org.springframework.core.env.ConfigurableEnvironment; /** * 应用日志初始化类:通过环境变量注入 logging.file 自动维护 Spring Boot Admin Logger Viewer * * @author lengleng * @date 2025/05/31 */ public class ApplicationLoggerInitializer implements EnvironmentPostProcessor, Ordered { /** * 后处理环境配置,设置日志路径和相关系统属性 * @param environment 可配置的环境对象 * @param application Spring应用对象 */ @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { String appName = environment.getProperty("spring.application.name"); String logBase = environment.getProperty("LOGGING_PATH", "logs"); // spring boot admin 直接加载日志 System.setProperty("logging.file.name", String.format("%s/%s/debug.log", logBase, appName)); // 避免各种依赖的地方组件造成 BeanPostProcessorChecker 警告 System.setProperty("logging.level.org.springframework.context.support.PostProcessorRegistrationDelegate", "ERROR"); // 避免 sentinel 1.8.4+ 心跳日志过大 System.setProperty("csp.sentinel.log.level", "OFF"); // 避免 sentinel 健康检查 server System.setProperty("management.health.sentinel.enabled", "false"); } @Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE; } } ================================================ FILE: pig-common/pig-common-log/src/main/java/com/pig4cloud/pig/common/log/util/LogTypeEnum.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.log.util; import lombok.Getter; import lombok.RequiredArgsConstructor; /** * 日志类型枚举 * * @author lengleng * @date 2025/05/31 */ @Getter @RequiredArgsConstructor public enum LogTypeEnum { /** * 正常日志类型 */ NORMAL("0", "正常日志"), /** * 错误日志类型 */ ERROR("9", "错误日志"); /** * 类型 */ private final String type; /** * 描述 */ private final String description; } ================================================ FILE: pig-common/pig-common-log/src/main/java/com/pig4cloud/pig/common/log/util/SysLogUtils.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.log.util; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.URLUtil; import cn.hutool.extra.servlet.JakartaServletUtil; import cn.hutool.extra.spring.SpringUtil; import cn.hutool.http.HttpUtil; import com.pig4cloud.pig.common.core.util.SpringContextHolder; import com.pig4cloud.pig.common.log.config.PigLogProperties; import com.pig4cloud.pig.common.log.event.SysLogEventSource; import jakarta.servlet.http.HttpServletRequest; import lombok.experimental.UtilityClass; import org.springframework.core.StandardReflectionParameterNameDiscoverer; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.http.HttpHeaders; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.Objects; /** * 系统日志工具类 * * @author lengleng * @date 2025/05/31 */ @UtilityClass public class SysLogUtils { /** * 获取系统日志事件源 * @return 系统日志事件源对象 */ public SysLogEventSource getSysLog() { HttpServletRequest request = ((ServletRequestAttributes) Objects .requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); SysLogEventSource sysLog = new SysLogEventSource(); sysLog.setLogType(LogTypeEnum.NORMAL.getType()); sysLog.setRequestUri(URLUtil.getPath(request.getRequestURI())); sysLog.setMethod(request.getMethod()); sysLog.setRemoteAddr(JakartaServletUtil.getClientIP(request)); sysLog.setUserAgent(request.getHeader(HttpHeaders.USER_AGENT)); sysLog.setCreateBy(getUsername()); sysLog.setServiceId(SpringUtil.getProperty("spring.application.name")); // get 参数脱敏 PigLogProperties logProperties = SpringContextHolder.getBean(PigLogProperties.class); Map paramsMap = MapUtil.removeAny(new HashMap<>(request.getParameterMap()), ArrayUtil.toArray(logProperties.getExcludeFields(), String.class)); sysLog.setParams(HttpUtil.toParams(paramsMap)); return sysLog; } /** * 获取用户名称 * @return username */ private String getUsername() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null) { return null; } return authentication.getName(); } /** * 获取spel 定义的参数值 * @param context 参数容器 * @param key key * @param clazz 需要返回的类型 * @param 返回泛型 * @return 参数值 */ public T getValue(EvaluationContext context, String key, Class clazz) { SpelExpressionParser spelExpressionParser = new SpelExpressionParser(); Expression expression = spelExpressionParser.parseExpression(key); return expression.getValue(context, clazz); } /** * 获取参数容器 * @param arguments 方法的参数列表 * @param signatureMethod 被执行的方法体 * @return 装载参数的容器 */ public EvaluationContext getContext(Object[] arguments, Method signatureMethod) { String[] parameterNames = new StandardReflectionParameterNameDiscoverer().getParameterNames(signatureMethod); EvaluationContext context = new StandardEvaluationContext(); if (parameterNames == null) { return context; } for (int i = 0; i < arguments.length; i++) { context.setVariable(parameterNames[i], arguments[i]); } return context; } } ================================================ FILE: pig-common/pig-common-log/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ================================================ com.pig4cloud.pig.common.log.LogAutoConfiguration ================================================ FILE: pig-common/pig-common-log/src/main/resources/META-INF/spring-configuration-metadata.json ================================================ { "groups": [ { "name": "security.log", "type": "com.pig4cloud.pig.common.log.config.PigLogProperties", "sourceType": "com.pig4cloud.pig.common.log.config.PigLogProperties" } ], "properties": [ { "name": "security.log.enabled", "type": "java.lang.Boolean", "description": "开启日志记录", "sourceType": "com.pig4cloud.pig.common.log.config.PigLogProperties" }, { "name": "security.log.exclude-fields", "type": "java.util.List", "description": "放行字段,password,mobile,idcard,phone", "sourceType": "com.pig4cloud.pig.common.log.config.PigLogProperties" }, { "name": "security.log.max-length", "type": "java.lang.Integer", "description": "请求报文最大存储长度", "sourceType": "com.pig4cloud.pig.common.log.config.PigLogProperties" } ], "hints": [] } ================================================ FILE: pig-common/pig-common-log/src/main/resources/META-INF/spring.factories ================================================ org.springframework.boot.env.EnvironmentPostProcessor=\ com.pig4cloud.pig.common.log.init.ApplicationLoggerInitializer ================================================ FILE: pig-common/pig-common-mybatis/pom.xml ================================================ 4.0.0 com.pig4cloud pig-common ${revision} pig-common-mybatis jar pig mybatis 封装 cn.hutool hutool-core com.baomidou mybatis-plus-spring com.baomidou mybatis-plus-jsqlparser io.swagger.core.v3 swagger-annotations-jakarta jakarta.servlet jakarta.servlet-api provided org.springframework spring-webmvc true org.springframework.security spring-security-core true com.pig4cloud pig-common-core ================================================ FILE: pig-common/pig-common-mybatis/src/main/java/com/pig4cloud/pig/common/mybatis/MybatisAutoConfiguration.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.mybatis; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.pig4cloud.pig.common.mybatis.config.MybatisPlusMetaObjectHandler; import com.pig4cloud.pig.common.mybatis.plugins.PigPaginationInnerInterceptor; import com.pig4cloud.pig.common.mybatis.resolver.SqlFilterArgumentResolver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; /** * MyBatis Plus 统一自动配置类 *

* 提供SQL过滤器、分页插件及审计字段自动填充等配置 * * @author lengleng * @date 2025/05/31 */ @Configuration(proxyBeanMethods = false) public class MybatisAutoConfiguration implements WebMvcConfigurer { /** * 添加SQL过滤器参数解析器,避免SQL注入 * @param argumentResolvers 方法参数解析器列表 */ @Override public void addArgumentResolvers(List argumentResolvers) { argumentResolvers.add(new SqlFilterArgumentResolver()); } /** * 创建并配置MybatisPlus分页拦截器 * @return 配置好的MybatisPlus拦截器实例 */ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PigPaginationInnerInterceptor()); return interceptor; } /** * 创建并返回MybatisPlusMetaObjectHandler实例,用于审计字段自动填充 * @return MybatisPlusMetaObjectHandler实例 */ @Bean public MybatisPlusMetaObjectHandler mybatisPlusMetaObjectHandler() { return new MybatisPlusMetaObjectHandler(); } } ================================================ FILE: pig-common/pig-common-mybatis/src/main/java/com/pig4cloud/pig/common/mybatis/base/BaseEntity.java ================================================ package com.pig4cloud.pig.common.mybatis.base; import java.io.Serial; import java.io.Serializable; import java.time.LocalDateTime; import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.TableField; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.Setter; /** * 基础实体抽象类,包含通用实体字段 * * @author lengleng * @date 2025/05/31 */ @Getter @Setter public class BaseEntity implements Serializable { @Serial private static final long serialVersionUID = 1L; /** * 创建者 */ @Schema(description = "创建人") @TableField(fill = FieldFill.INSERT) private String createBy; /** * 创建时间 */ @Schema(description = "创建时间") @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; /** * 更新者 */ @Schema(description = "更新人") @TableField(fill = FieldFill.INSERT_UPDATE) private String updateBy; /** * 更新时间 */ @Schema(description = "更新时间") @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; } ================================================ FILE: pig-common/pig-common-mybatis/src/main/java/com/pig4cloud/pig/common/mybatis/config/MybatisPlusMetaObjectHandler.java ================================================ package com.pig4cloud.pig.common.mybatis.config; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import com.pig4cloud.pig.common.core.constant.CommonConstants; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.reflection.MetaObject; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.ClassUtils; import java.nio.charset.Charset; import java.time.LocalDateTime; import java.util.Optional; /** * MybatisPlus 自动填充处理器,用于实体类字段的自动填充 * * @author lengleng * @date 2025/05/31 */ @Slf4j public class MybatisPlusMetaObjectHandler implements MetaObjectHandler { /** * 插入时自动填充字段 * @param metaObject 元对象,用于操作实体类属性 */ @Override public void insertFill(MetaObject metaObject) { log.debug("mybatis plus start insert fill ...."); LocalDateTime now = LocalDateTime.now(); fillValIfNullByName("createTime", now, metaObject, true); fillValIfNullByName("updateTime", now, metaObject, true); fillValIfNullByName("createBy", getUserName(), metaObject, true); fillValIfNullByName("updateBy", getUserName(), metaObject, true); // 删除标记自动填充 fillValIfNullByName("delFlag", CommonConstants.STATUS_NORMAL, metaObject, true); } /** * 更新时自动填充字段 * @param metaObject 元对象 */ @Override public void updateFill(MetaObject metaObject) { log.debug("mybatis plus start update fill ...."); fillValIfNullByName("updateTime", LocalDateTime.now(), metaObject, true); fillValIfNullByName("updateBy", getUserName(), metaObject, true); } /** * 填充值,先判断是否有手动设置,优先手动设置的值,例如:job必须手动设置 * @param fieldName 属性名 * @param fieldVal 属性值 * @param metaObject MetaObject * @param isCover 是否覆盖原有值,避免更新操作手动入参 */ private static void fillValIfNullByName(String fieldName, Object fieldVal, MetaObject metaObject, boolean isCover) { // 0. 如果填充值为空 if (fieldVal == null) { return; } // 1. 没有 set 方法 if (!metaObject.hasSetter(fieldName)) { return; } // 2. 如果用户有手动设置的值 Object userSetValue = metaObject.getValue(fieldName); String setValueStr = StrUtil.str(userSetValue, Charset.defaultCharset()); if (StrUtil.isNotBlank(setValueStr) && !isCover) { return; } // 3. field 类型相同时设置 Class getterType = metaObject.getGetterType(fieldName); if (ClassUtils.isAssignableValue(getterType, fieldVal)) { metaObject.setValue(fieldName, fieldVal); } } /** * 获取 spring security 当前的用户名 * @return 当前用户名 */ private String getUserName() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); // 匿名接口直接返回 if (authentication instanceof AnonymousAuthenticationToken) { return null; } if (Optional.ofNullable(authentication).isPresent()) { return authentication.getName(); } return null; } } ================================================ FILE: pig-common/pig-common-mybatis/src/main/java/com/pig4cloud/pig/common/mybatis/handler/JsonLongArrayTypeHandler.java ================================================ package com.pig4cloud.pig.common.mybatis.handler; import cn.hutool.core.convert.Convert; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import lombok.SneakyThrows; import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import org.apache.ibatis.type.MappedJdbcTypes; import org.apache.ibatis.type.MappedTypes; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; /** * MyBatis 长整型数组与字符串类型转换处理器 *

* 实现数据库VARCHAR类型与Java Long数组类型的相互转换 * * @author lengleng * @date 2025/05/31 * @see MappedTypes 映射的Java类型为Long[] * @see MappedJdbcTypes 映射的JDBC类型为VARCHAR */ @MappedTypes(value = { Long[].class }) @MappedJdbcTypes(value = JdbcType.VARCHAR) public class JsonLongArrayTypeHandler extends BaseTypeHandler { /** * 设置非空参数到PreparedStatement中 * @param ps PreparedStatement对象 * @param i 参数位置 * @param parameter 长整型数组参数 * @param jdbcType JDBC类型 * @throws SQLException 数据库操作异常 */ @Override public void setNonNullParameter(PreparedStatement ps, int i, Long[] parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, ArrayUtil.join(parameter, StrUtil.COMMA)); } /** * 从ResultSet中获取指定列名的长整型数组结果 * @param rs 结果集 * @param columnName 列名 * @return 转换后的长整型数组,可能为null * @throws SQLException 数据库访问错误时抛出 */ @Override @SneakyThrows public Long[] getNullableResult(ResultSet rs, String columnName) { String reString = rs.getString(columnName); return Convert.toLongArray(reString); } /** * 从ResultSet中获取指定列的长整型数组 * @param rs 结果集 * @param columnIndex 列索引 * @return 长整型数组,可能为null * @throws SQLException 数据库访问错误时抛出 */ @Override @SneakyThrows public Long[] getNullableResult(ResultSet rs, int columnIndex) { String reString = rs.getString(columnIndex); return Convert.toLongArray(reString); } /** * 从CallableStatement中获取指定列的长整型数组 * @param cs CallableStatement对象 * @param columnIndex 列索引 * @return 转换后的长整型数组,可能为null * @throws Exception 数据库访问出错或转换异常时抛出 */ @Override @SneakyThrows public Long[] getNullableResult(CallableStatement cs, int columnIndex) { String reString = cs.getString(columnIndex); return Convert.toLongArray(reString); } } ================================================ FILE: pig-common/pig-common-mybatis/src/main/java/com/pig4cloud/pig/common/mybatis/handler/JsonStringArrayTypeHandler.java ================================================ package com.pig4cloud.pig.common.mybatis.handler; import cn.hutool.core.convert.Convert; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import lombok.SneakyThrows; import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import org.apache.ibatis.type.MappedJdbcTypes; import org.apache.ibatis.type.MappedTypes; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; /** * MyBatis 字符串数组类型处理器,用于数据库VARCHAR类型与Java字符串数组的相互转换 * * @author lengleng * @date 2025/05/31 * @see MappedTypes 指定处理的Java类型 * @see MappedJdbcTypes 指定处理的JDBC类型 */ @MappedTypes(value = { String[].class }) @MappedJdbcTypes(value = JdbcType.VARCHAR) public class JsonStringArrayTypeHandler extends BaseTypeHandler { /** * 设置非空参数到PreparedStatement * @param ps PreparedStatement对象 * @param i 参数位置 * @param parameter 字符串数组参数 * @param jdbcType JDBC类型 * @throws SQLException 数据库操作异常 */ @Override public void setNonNullParameter(PreparedStatement ps, int i, String[] parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, ArrayUtil.join(parameter, StrUtil.COMMA)); } /** * 从ResultSet中获取指定列名的字符串数组结果,允许为null * @param rs 结果集 * @param columnName 列名 * @return 转换后的字符串数组,可能为null * @throws SQLException 数据库访问错误时抛出 */ @Override @SneakyThrows public String[] getNullableResult(ResultSet rs, String columnName) { String reString = rs.getString(columnName); return Convert.toStrArray(reString); } /** * 从ResultSet中获取指定列的可空字符串数组结果 * @param rs 结果集 * @param columnIndex 列索引 * @return 转换后的字符串数组,可能为null * @throws SQLException 数据库访问错误时抛出 */ @Override @SneakyThrows public String[] getNullableResult(ResultSet rs, int columnIndex) { String reString = rs.getString(columnIndex); return Convert.toStrArray(reString); } /** * 从CallableStatement中获取指定列的可空字符串结果并转换为字符串数组 * @param cs CallableStatement对象 * @param columnIndex 列索引 * @return 转换后的字符串数组 * @throws Exception 如果操作过程中发生错误 */ @Override @SneakyThrows public String[] getNullableResult(CallableStatement cs, int columnIndex) { String reString = cs.getString(columnIndex); return Convert.toStrArray(reString); } } ================================================ FILE: pig-common/pig-common-mybatis/src/main/java/com/pig4cloud/pig/common/mybatis/plugins/PigPaginationInnerInterceptor.java ================================================ package com.pig4cloud.pig.common.mybatis.plugins; 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 com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.ParameterUtils; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.pagination.dialects.IDialect; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; /** * 分页拦截器实现类,用于处理分页查询逻辑 *

* 当分页大小小于0时自动设置为0,防止全表查询 * * @author lengleng * @date 2025/05/31 * @since 2021年10月11日 */ @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = false) public class PigPaginationInnerInterceptor extends PaginationInnerInterceptor { /** * 数据库类型 *

* 查看 {@link #findIDialect(Executor)} 逻辑 */ private DbType dbType; /** * 方言实现类 *

* 查看 {@link #findIDialect(Executor)} 逻辑 */ private IDialect dialect; public PigPaginationInnerInterceptor(DbType dbType) { this.dbType = dbType; } public PigPaginationInnerInterceptor(IDialect dialect) { this.dialect = dialect; } /** * 在执行查询前处理分页参数 * @param executor 执行器 * @param ms 映射语句 * @param parameter 参数对象 * @param rowBounds 行边界 * @param resultHandler 结果处理器 * @param boundSql 绑定SQL */ @Override public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { IPage page = ParameterUtils.findPage(parameter).orElse(null); // size 小于 0 直接设置为 0 , 即不查询任何数据 if (null != page && page.getSize() < 0) { page.setSize(0); } super.beforeQuery(executor, ms, page, rowBounds, resultHandler, boundSql); } } ================================================ FILE: pig-common/pig-common-mybatis/src/main/java/com/pig4cloud/pig/common/mybatis/resolver/SqlFilterArgumentResolver.java ================================================ /* * * * Copyright (c) 2019-2020, 冷冷 (wangiegie@gmail.com). * *

* * Licensed under the GNU Lesser General Public License 3.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * *

* * https://www.gnu.org/licenses/lgpl.html * *

* * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ package com.pig4cloud.pig.common.mybatis.resolver; import cn.hutool.core.convert.Convert; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.metadata.OrderItem; import com.baomidou.mybatisplus.core.toolkit.sql.SqlInjectionUtils; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import jakarta.servlet.http.HttpServletRequest; import org.springframework.core.MethodParameter; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; /** * Mybatis Plus Order By SQL注入问题解决类 * * @author lengleng * @date 2019-06-24 */ public class SqlFilterArgumentResolver implements HandlerMethodArgumentResolver { /** * 判断Controller方法参数是否为Page类型 * @param parameter 方法参数 * @return 如果参数类型是Page则返回true,否则返回false */ @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.getParameterType().equals(Page.class); } /** * 解析分页参数并构建Page对象 * @param parameter 方法参数信息 * @param mavContainer 模型和视图容器 * @param webRequest web请求对象 * @param binderFactory 数据绑定工厂 * @return 包含分页和排序信息的Page对象 * @throws NumberFormatException 当分页参数转换失败时抛出 */ @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); String[] ascs = request.getParameterValues("ascs"); String[] descs = request.getParameterValues("descs"); String current = request.getParameter("current"); String size = request.getParameter("size"); Page page = new Page<>(); if (StrUtil.isNotBlank(current)) { page.setCurrent(Convert.toLong(current, 0L)); } if (StrUtil.isNotBlank(size)) { page.setSize(Convert.toLong(size, 10L)); } List orderItemList = new ArrayList<>(); Optional.ofNullable(ascs) .ifPresent(s -> orderItemList .addAll(Arrays.stream(s).filter(asc -> !SqlInjectionUtils.check(asc)).map(OrderItem::asc).toList())); Optional.ofNullable(descs) .ifPresent(s -> orderItemList .addAll(Arrays.stream(s).filter(desc -> !SqlInjectionUtils.check(desc)).map(OrderItem::desc).toList())); page.addOrder(orderItemList); return page; } } ================================================ FILE: pig-common/pig-common-mybatis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ================================================ com.pig4cloud.pig.common.mybatis.MybatisAutoConfiguration ================================================ FILE: pig-common/pig-common-oss/pom.xml ================================================ 4.0.0 com.pig4cloud pig-common ${revision} pig-common-oss jar pig 文件系统依赖 software.amazon.awssdk s3 cn.hutool hutool-core ================================================ FILE: pig-common/pig-common-oss/src/main/java/com/pig4cloud/pig/common/file/FileAutoConfiguration.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.common.file; import com.pig4cloud.pig.common.file.core.FileProperties; import com.pig4cloud.pig.common.file.local.LocalFileAutoConfiguration; import com.pig4cloud.pig.common.file.oss.OssAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Import; /** * AWS 自动配置类 * * @author lengleng * @author 858695266 * @date 2025/05/31 */ @Import({ LocalFileAutoConfiguration.class, OssAutoConfiguration.class }) @EnableConfigurationProperties({ FileProperties.class }) public class FileAutoConfiguration { } ================================================ FILE: pig-common/pig-common-oss/src/main/java/com/pig4cloud/pig/common/file/core/FileProperties.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.common.file.core; import com.pig4cloud.pig.common.file.local.LocalFileProperties; import com.pig4cloud.pig.common.file.oss.OssProperties; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.NestedConfigurationProperty; /** * 文件 配置信息 * * @author lengleng *

* bucket 设置公共读权限 */ @Data @ConfigurationProperties(prefix = "file") public class FileProperties { /** * 默认的存储桶名称 */ private String bucketName = "local"; /** * 本地文件配置信息 */ @NestedConfigurationProperty private LocalFileProperties local; /** * oss 文件配置信息 */ @NestedConfigurationProperty private OssProperties oss; } ================================================ FILE: pig-common/pig-common-oss/src/main/java/com/pig4cloud/pig/common/file/core/FileTemplate.java ================================================ package com.pig4cloud.pig.common.file.core; import org.springframework.beans.factory.InitializingBean; import java.io.InputStream; import java.util.List; /** * 文件操作模板 * * @author lengleng * @date 2022/4/19 */ public interface FileTemplate extends InitializingBean { /** * 创建bucket * @param bucketName bucket名称 */ void createBucket(String bucketName); /** * 获取全部bucket *

* * API Documentation */ List getAllBuckets(); /** * @param bucketName bucket名称 * @see */ void removeBucket(String bucketName); /** * 上传文件 * @param bucketName bucket名称 * @param objectName 文件名称 * @param stream 文件流 * @param contextType 文件类型 * @throws Exception */ void putObject(String bucketName, String objectName, InputStream stream, String contextType) throws Exception; /** * 上传文件 * @param bucketName bucket名称 * @param objectName 文件名称 * @param stream 文件流 * @param contextType 文件类型 * @throws Exception */ void putObject(String bucketName, String objectName, InputStream stream) throws Exception; /** * 获取文件 * @param bucketName bucket名称 * @param objectName 文件名称 * @return 文件对象 API Documentation */ Object getObject(String bucketName, String objectName); void removeObject(String bucketName, String objectName) throws Exception; /** * @throws Exception */ @Override default void afterPropertiesSet() throws Exception { } /** * 根据文件前置查询文件 * @param bucketName bucket名称 * @param prefix 前缀 * @param recursive 是否递归查询 * @return 文件对象列表 * @see AWS * API Documentation */ List getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive); } ================================================ FILE: pig-common/pig-common-oss/src/main/java/com/pig4cloud/pig/common/file/local/LocalFileAutoConfiguration.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.common.file.local; import com.pig4cloud.pig.common.file.core.FileProperties; import com.pig4cloud.pig.common.file.core.FileTemplate; import lombok.AllArgsConstructor; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; /** * aws 自动配置类 * * @author lengleng * @author 858695266 */ @AllArgsConstructor public class LocalFileAutoConfiguration { private final FileProperties properties; @Bean @ConditionalOnMissingBean(LocalFileTemplate.class) @ConditionalOnProperty(name = "file.local.enable", havingValue = "true", matchIfMissing = true) public FileTemplate localFileTemplate() { return new LocalFileTemplate(properties); } } ================================================ FILE: pig-common/pig-common-oss/src/main/java/com/pig4cloud/pig/common/file/local/LocalFileProperties.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.common.file.local; import lombok.Data; /** * 本地文件 配置信息 * * @author lengleng *

* bucket 设置公共读权限 */ @Data public class LocalFileProperties { /** * 是否开启 */ private boolean enable; /** * 默认路径 */ private String basePath; } ================================================ FILE: pig-common/pig-common-oss/src/main/java/com/pig4cloud/pig/common/file/local/LocalFileTemplate.java ================================================ package com.pig4cloud.pig.common.file.local; import cn.hutool.core.io.FileUtil; import com.pig4cloud.pig.common.file.core.FileProperties; import com.pig4cloud.pig.common.file.core.FileTemplate; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import java.io.File; import java.io.InputStream; import java.util.Arrays; import java.util.List; /** * 本地文件读取模式 * * @author lengleng * @date 2022/4/19 */ @RequiredArgsConstructor public class LocalFileTemplate implements FileTemplate { private final FileProperties properties; /** * 简单的 Bucket 数据对象 */ public record SimpleBucket(String name) { } /** * 简单的 ObjectSummary 数据对象 */ public record SimpleObjectSummary(String key) { } /** * 创建bucket * @param bucketName bucket名称 */ @Override public void createBucket(String bucketName) { FileUtil.mkdir(properties.getLocal().getBasePath() + FileUtil.FILE_SEPARATOR + bucketName); } /** * 获取全部bucket *

*

* API Documentation */ @Override public List getAllBuckets() { return Arrays.stream(FileUtil.ls(properties.getLocal().getBasePath())) .filter(FileUtil::isDirectory) .map(dir -> new SimpleBucket(dir.getName())) .toList(); } /** * @param bucketName bucket名称 * @see */ @Override public void removeBucket(String bucketName) { FileUtil.del(properties.getLocal().getBasePath() + FileUtil.FILE_SEPARATOR + bucketName); } /** * 上传文件 * @param bucketName bucket名称 * @param objectName 文件名称 * @param stream 文件流 * @param contextType 文件类型 */ @Override public void putObject(String bucketName, String objectName, InputStream stream, String contextType) { // 当 Bucket 不存在时创建 String dir = properties.getLocal().getBasePath() + FileUtil.FILE_SEPARATOR + bucketName; if (!FileUtil.isDirectory(properties.getLocal().getBasePath() + FileUtil.FILE_SEPARATOR + bucketName)) { createBucket(bucketName); } // 写入文件 File file = FileUtil.file(dir + FileUtil.FILE_SEPARATOR + objectName); FileUtil.writeFromStream(stream, file); } /** * 获取文件 * @param bucketName bucket名称 * @param objectName 文件名称 * @return 文件输入流 */ @Override @SneakyThrows public InputStream getObject(String bucketName, String objectName) { String dir = properties.getLocal().getBasePath() + FileUtil.FILE_SEPARATOR + bucketName; return FileUtil.getInputStream(dir + FileUtil.FILE_SEPARATOR + objectName); } /** * 删除指定存储桶中的对象 * @param bucketName 存储桶名称 * @param objectName 对象名称 * @throws Exception 删除过程中可能抛出的异常 */ @Override public void removeObject(String bucketName, String objectName) throws Exception { String dir = properties.getLocal().getBasePath() + FileUtil.FILE_SEPARATOR + bucketName; FileUtil.del(dir + FileUtil.FILE_SEPARATOR + objectName); } /** * 上传文件到指定存储桶 * @param bucketName 存储桶名称 * @param objectName 文件名称 * @param stream 文件输入流 * @throws Exception 上传过程中可能发生的异常 */ @Override public void putObject(String bucketName, String objectName, InputStream stream) throws Exception { putObject(bucketName, objectName, stream, null); } /** * 根据文件前置查询文件 * @param bucketName bucket名称 * @param prefix 前缀 * @param recursive 是否递归查询 * @return 文件对象摘要列表 * @see AWS * API Documentation */ @Override public List getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive) { String dir = properties.getLocal().getBasePath() + FileUtil.FILE_SEPARATOR + bucketName; return Arrays.stream(FileUtil.ls(dir)) .filter(file -> file.getName().startsWith(prefix)) .map(file -> new SimpleObjectSummary(file.getName())) .toList(); } } ================================================ FILE: pig-common/pig-common-oss/src/main/java/com/pig4cloud/pig/common/file/oss/OssAutoConfiguration.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.common.file.oss; import com.pig4cloud.pig.common.file.core.FileProperties; import com.pig4cloud.pig.common.file.core.FileTemplate; import com.pig4cloud.pig.common.file.oss.http.OssEndpoint; import com.pig4cloud.pig.common.file.oss.service.OssTemplate; import lombok.AllArgsConstructor; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; /** * AWS 对象存储自动配置类 * * @author lengleng * @author 858695266 * @date 2025/05/31 */ @AllArgsConstructor public class OssAutoConfiguration { private final FileProperties properties; /** * 创建OssTemplate Bean * @return 文件模板实例 * @ConditionalOnMissingBean 当容器中不存在OssTemplate Bean时创建 * @ConditionalOnProperty 当配置项file.oss.enable为true时生效 */ @Bean @Primary @ConditionalOnMissingBean(OssTemplate.class) @ConditionalOnProperty(name = "file.oss.enable", havingValue = "true") public FileTemplate ossTemplate() { return new OssTemplate(properties); } /** * 创建OssEndpoint Bean * @param template OssTemplate实例 * @return OssEndpoint实例 * @ConditionalOnMissingBean 当容器中不存在该类型Bean时创建 * @ConditionalOnProperty 当配置项file.oss.info为true时生效 */ @Bean @ConditionalOnMissingBean @ConditionalOnProperty(name = "file.oss.info", havingValue = "true") public OssEndpoint ossEndpoint(OssTemplate template) { return new OssEndpoint(template); } } ================================================ FILE: pig-common/pig-common-oss/src/main/java/com/pig4cloud/pig/common/file/oss/OssProperties.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.common.file.oss; import lombok.Data; /** * aws 配置信息 * * @author lengleng * @author 858695266 配置文件添加: oss: enable: true endpoint: http://127.0.0.1:9000 # * pathStyleAccess 采用nginx反向代理或者AWS S3 配置成true,支持第三方云存储配置成false pathStyleAccess: false * access-key: lengleng secret-key: lengleng bucket-name: lengleng region: custom-domain: * https://oss.xxx.com/lengleng *

* bucket 设置公共读权限 */ @Data public class OssProperties { /** * 对象存储服务的URL */ private String endpoint; /** * 自定义域名 */ private String customDomain; /** * true path-style nginx 反向代理和S3默认支持 pathStyle {http://endpoint/bucketname} false * supports virtual-hosted-style 阿里云等需要配置为 virtual-hosted-style * 模式{http://bucketname.endpoint} */ private Boolean pathStyleAccess = true; /** * 应用ID */ private String appId; /** * 区域 */ private String region; /** * Access key就像用户ID,可以唯一标识你的账户 */ private String accessKey; /** * Secret key是你账户的密码 */ private String secretKey; /** * 最大线程数,默认: 100 */ private Integer maxConnections = 100; } ================================================ FILE: pig-common/pig-common-oss/src/main/java/com/pig4cloud/pig/common/file/oss/http/OssEndpoint.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.common.file.oss.http; import com.pig4cloud.pig.common.file.oss.service.OssTemplate; import lombok.AllArgsConstructor; import lombok.Cleanup; import lombok.SneakyThrows; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import software.amazon.awssdk.services.s3.model.Bucket; import software.amazon.awssdk.services.s3.model.S3Object; import java.io.InputStream; import java.util.HashMap; import java.util.List; import java.util.Map; /** * AWS 对象存储服务端点 * * @author lengleng * @author 858695266 * @date 2025/05/31 */ @RestController @AllArgsConstructor @RequestMapping("/oss") @ConditionalOnProperty(name = "file.oss.info", havingValue = "true") public class OssEndpoint { private final OssTemplate template; /** * 创建指定名称的存储桶 * @param bucketName 存储桶名称 * @return 创建的存储桶对象 * @throws Exception 创建过程中可能抛出的异常 */ @SneakyThrows @PostMapping("/bucket/{bucketName}") public Bucket createBucket(@PathVariable String bucketName) { template.createBucket(bucketName); return template.getBucket(bucketName).get(); } /** * 获取所有存储桶列表 * @return 存储桶列表 * @throws Exception 获取过程中可能抛出的异常 */ @SneakyThrows @GetMapping("/bucket") public List getBuckets() { return template.getAllBuckets(); } /** * 根据桶名称获取桶信息 * @param bucketName 桶名称 * @return 对应的桶对象 * @throws IllegalArgumentException 当桶不存在时抛出异常 */ @SneakyThrows @GetMapping("/bucket/{bucketName}") public Bucket getBucket(@PathVariable String bucketName) { return template.getBucket(bucketName).orElseThrow(() -> new IllegalArgumentException("Bucket Name not found!")); } /** * 删除指定名称的存储桶 * @param bucketName 要删除的存储桶名称 * @throws Exception 删除过程中可能抛出的异常 */ @SneakyThrows @DeleteMapping("/bucket/{bucketName}") @ResponseStatus(HttpStatus.ACCEPTED) public void deleteBucket(@PathVariable String bucketName) { template.removeBucket(bucketName); } /** * 创建对象到指定存储桶 * @param object 要上传的文件对象 * @param bucketName 目标存储桶名称 * @return 上传后的对象信息响应 * @throws IOException 文件操作异常 */ @SneakyThrows @PostMapping("/object/{bucketName}") public Map createObject(@RequestBody MultipartFile object, @PathVariable String bucketName) { String name = object.getOriginalFilename(); @Cleanup InputStream inputStream = object.getInputStream(); template.putObject(bucketName, name, inputStream, object.getSize(), object.getContentType()); Map result = new HashMap<>(); result.put("bucket", bucketName); result.put("object", name); result.put("size", object.getSize()); result.put("contentType", object.getContentType()); return result; } /** * 创建对象到指定存储桶 * @param object 上传的文件对象 * @param bucketName 存储桶名称 * @param objectName 对象名称 * @return 创建成功的对象信息 * @throws Exception 当文件上传或获取对象信息失败时抛出异常 */ @SneakyThrows @PostMapping("/object/{bucketName}/{objectName}") public Map createObject(@RequestBody MultipartFile object, @PathVariable String bucketName, @PathVariable String objectName) { @Cleanup InputStream inputStream = object.getInputStream(); template.putObject(bucketName, objectName, inputStream, object.getSize(), object.getContentType()); Map result = new HashMap<>(); result.put("bucket", bucketName); result.put("object", objectName); result.put("size", object.getSize()); result.put("contentType", object.getContentType()); return result; } /** * 根据对象名前缀过滤对象列表 * @param bucketName 存储桶名称 * @param objectName 对象名前缀 * @return 匹配前缀的S3对象列表 * @throws Exception 操作执行过程中可能抛出的异常 */ @SneakyThrows @GetMapping("/object/{bucketName}/{objectName}") public List filterObject(@PathVariable String bucketName, @PathVariable String objectName) { return template.getAllObjectsByPrefix(bucketName, objectName, true); } /** * 获取对象信息及访问URL * @param bucketName 存储桶名称 * @param objectName 对象名称 * @param expires URL过期时间(秒) * @return 包含存储桶、对象、URL和过期时间的Map */ @SneakyThrows @GetMapping("/object/{bucketName}/{objectName}/{expires}") public Map getObject(@PathVariable String bucketName, @PathVariable String objectName, @PathVariable Integer expires) { Map responseBody = new HashMap<>(8); // Put Object info responseBody.put("bucket", bucketName); responseBody.put("object", objectName); responseBody.put("url", template.getObjectURL(bucketName, objectName, expires)); responseBody.put("expires", expires); return responseBody; } /** * 删除指定存储桶中的对象 * @param bucketName 存储桶名称 * @param objectName 对象名称 * @throws Exception 删除对象时可能抛出的异常 */ @SneakyThrows @ResponseStatus(HttpStatus.ACCEPTED) @DeleteMapping("/object/{bucketName}/{objectName}/") public void deleteObject(@PathVariable String bucketName, @PathVariable String objectName) { template.removeObject(bucketName, objectName); } } ================================================ FILE: pig-common/pig-common-oss/src/main/java/com/pig4cloud/pig/common/file/oss/service/OssTemplate.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.common.file.oss.service; import com.pig4cloud.pig.common.file.core.FileProperties; import com.pig4cloud.pig.common.file.core.FileTemplate; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import org.springframework.beans.factory.InitializingBean; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.S3Configuration; import software.amazon.awssdk.services.s3.model.*; import software.amazon.awssdk.services.s3.presigner.S3Presigner; import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest; import java.io.InputStream; import java.net.URI; import java.time.Duration; import java.util.List; import java.util.Optional; /** * AWS S3通用存储操作模板类 * *

* 支持所有兼容S3协议的云存储服务,包括AWS S3、MinIO、阿里云OSS、腾讯云COS等 *

*

* 提供存储桶管理、文件对象管理、预签名URL生成等功能 *

* * @author lengleng * @author 858695266 * @date 2025/05/31 * @since 1.0 */ @RequiredArgsConstructor public class OssTemplate implements InitializingBean, FileTemplate { /** * 文件存储配置属性 */ private final FileProperties properties; /** * S3客户端实例,用于执行S3 API操作 */ private S3Client s3Client; /** * S3预签名器,用于生成预签名URL */ private S3Presigner s3Presigner; /** * 创建存储桶 * @param bucketName 存储桶名称,必须全局唯一且符合DNS命名规范 * @throws Exception 创建失败时抛出异常 */ @SneakyThrows public void createBucket(String bucketName) { // 检查存储桶是否已存在,避免重复创建 if (!doesBucketExist(bucketName)) { CreateBucketRequest request = CreateBucketRequest.builder().bucket(bucketName).build(); s3Client.createBucket(request); } } /** * 检查存储桶是否存在 * @param bucketName 存储桶名称 * @return 存在返回true,否则返回false */ private boolean doesBucketExist(String bucketName) { try { HeadBucketRequest request = HeadBucketRequest.builder().bucket(bucketName).build(); s3Client.headBucket(request); return true; } catch (NoSuchBucketException e) { return false; } } /** * 获取所有存储桶列表 * @return 存储桶列表 * @see AWS * API Documentation */ @SneakyThrows public List getAllBuckets() { ListBucketsResponse response = s3Client.listBuckets(); return response.buckets(); } /** * 根据名称查找特定存储桶 * @param bucketName 存储桶名称 * @return Optional包装的Bucket对象 * @see AWS * API Documentation */ @SneakyThrows public Optional getBucket(String bucketName) { return getAllBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst(); } /** * 删除存储桶 * *

* 注意:存储桶必须为空才能删除,删除操作不可逆 *

* @param bucketName 存储桶名称 * @throws Exception 删除失败时抛出异常 * @see AWS API * Documentation */ @SneakyThrows public void removeBucket(String bucketName) { DeleteBucketRequest request = DeleteBucketRequest.builder().bucket(bucketName).build(); s3Client.deleteBucket(request); } /** * 根据前缀查询文件对象 * @param bucketName 存储桶名称 * @param prefix 文件名前缀,可为null或空字符串 * @param recursive 是否递归查询子目录 * @return S3Object列表 * @see AWS * API Documentation */ @SneakyThrows public List getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive) { ListObjectsV2Request.Builder requestBuilder = ListObjectsV2Request.builder().bucket(bucketName); // 设置前缀过滤条件 if (prefix != null && !prefix.isEmpty()) { requestBuilder.prefix(prefix); } // 非递归查询时设置分隔符 if (!recursive) { requestBuilder.delimiter("/"); } ListObjectsV2Response response = s3Client.listObjectsV2(requestBuilder.build()); return response.contents(); } /** * 生成文件的预签名访问URL * @param bucketName 存储桶名称 * @param objectName 文件对象名称 * @param expires 过期时间(天数) * @return 预签名的访问URL * @throws Exception 生成失败时抛出异常 */ @SneakyThrows public String getObjectURL(String bucketName, String objectName, Integer expires) { GetObjectRequest getObjectRequest = GetObjectRequest.builder().bucket(bucketName).key(objectName).build(); GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder() .signatureDuration(Duration.ofDays(expires)) .getObjectRequest(getObjectRequest) .build(); return s3Presigner.presignGetObject(presignRequest).url().toString(); } /** * 获取文件对象 * @param bucketName 存储桶名称 * @param objectName 文件对象名称 * @return S3响应对象,包含文件流和元数据 * @throws Exception 获取失败时抛出异常 * @see AWS * API Documentation */ @SneakyThrows public Object getObject(String bucketName, String objectName) { GetObjectRequest request = GetObjectRequest.builder().bucket(bucketName).key(objectName).build(); return s3Client.getObject(request); } /** * 上传文件(使用默认内容类型) * @param bucketName 存储桶名称 * @param objectName 文件对象名称 * @param stream 文件输入流 * @throws Exception 上传失败时抛出异常 */ public void putObject(String bucketName, String objectName, InputStream stream) throws Exception { putObject(bucketName, objectName, stream, "application/octet-stream"); } /** * 上传文件(指定内容类型) * @param bucketName 存储桶名称 * @param objectName 文件对象名称 * @param stream 文件输入流 * @param contextType 文件MIME类型 * @throws Exception 上传失败时抛出异常 */ public void putObject(String bucketName, String objectName, InputStream stream, String contextType) throws Exception { PutObjectRequest request = PutObjectRequest.builder() .bucket(bucketName) .key(objectName) .contentType(contextType) .build(); s3Client.putObject(request, RequestBody.fromInputStream(stream, stream.available())); } /** * 上传文件(指定文件大小) * @param bucketName 存储桶名称 * @param objectName 文件对象名称 * @param stream 文件输入流 * @param size 文件大小(字节数) * @param contextType 文件MIME类型 * @return PutObjectResponse 上传响应对象 * @throws Exception 上传失败时抛出异常 * @see AWS * API Documentation */ public PutObjectResponse putObject(String bucketName, String objectName, InputStream stream, long size, String contextType) throws Exception { PutObjectRequest request = PutObjectRequest.builder() .bucket(bucketName) .key(objectName) .contentType(contextType) .contentLength(size) .build(); return s3Client.putObject(request, RequestBody.fromInputStream(stream, size)); } /** * 获取文件元数据信息 * @param bucketName 存储桶名称 * @param objectName 文件对象名称 * @return HeadObjectResponse 文件元数据响应对象 * @throws Exception 获取失败时抛出异常 * @see AWS * API Documentation */ public HeadObjectResponse getObjectInfo(String bucketName, String objectName) throws Exception { HeadObjectRequest request = HeadObjectRequest.builder().bucket(bucketName).key(objectName).build(); return s3Client.headObject(request); } /** * 删除文件对象 * *

* 注意:删除操作不可逆,删除不存在的文件不会报错 *

* @param bucketName 存储桶名称 * @param objectName 文件对象名称 * @throws Exception 删除失败时抛出异常 * @see AWS API * Documentation */ public void removeObject(String bucketName, String objectName) throws Exception { DeleteObjectRequest request = DeleteObjectRequest.builder().bucket(bucketName).key(objectName).build(); s3Client.deleteObject(request); } /** * 初始化S3客户端和预签名器实例 * *

* 在Spring Bean属性设置完成后自动调用,配置端点地址、区域、访问凭证等 *

* @throws Exception 初始化失败时抛出异常 */ @Override public void afterPropertiesSet() { // 创建认证凭据 AwsBasicCredentials awsCredentials = AwsBasicCredentials.create(properties.getOss().getAccessKey(), properties.getOss().getSecretKey()); // 构建S3配置 S3Configuration.Builder s3ConfigBuilder = S3Configuration.builder() .pathStyleAccessEnabled(properties.getOss().getPathStyleAccess()); // 创建S3客户端 this.s3Client = S3Client.builder() .endpointOverride(URI.create(properties.getOss().getEndpoint())) .region(Region.of(properties.getOss().getRegion() != null ? properties.getOss().getRegion() : "us-east-1")) .credentialsProvider(StaticCredentialsProvider.create(awsCredentials)) .serviceConfiguration(s3ConfigBuilder.build()) .build(); // 创建S3预签名器 this.s3Presigner = S3Presigner.builder() .endpointOverride(URI.create(properties.getOss().getEndpoint())) .region(Region.of(properties.getOss().getRegion() != null ? properties.getOss().getRegion() : "us-east-1")) .credentialsProvider(StaticCredentialsProvider.create(awsCredentials)) .serviceConfiguration(s3ConfigBuilder.build()) .build(); } } ================================================ FILE: pig-common/pig-common-oss/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ================================================ com.pig4cloud.pig.common.file.FileAutoConfiguration ================================================ FILE: pig-common/pig-common-seata/pom.xml ================================================ 4.0.0 com.pig4cloud pig-common ${revision} pig-common-seata jar pig 分布式事务处理模块 com.pig4cloud pig-common-core com.alibaba.cloud spring-cloud-starter-alibaba-seata ================================================ FILE: pig-common/pig-common-seata/src/main/java/com/pig4cloud/pig/common/seata/config/SeataAutoConfiguration.java ================================================ package com.pig4cloud.pig.common.seata.config; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import com.pig4cloud.pig.common.core.factory.YamlPropertySourceFactory; /** * Seata 自动配置类 * * @author lengleng * @date 2025/05/31 */ @PropertySource(value = "classpath:seata-config.yml", factory = YamlPropertySourceFactory.class) @Configuration(proxyBeanMethods = false) public class SeataAutoConfiguration { } ================================================ FILE: pig-common/pig-common-seata/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ================================================ com.pig4cloud.pig.common.seata.config.SeataAutoConfiguration ================================================ FILE: pig-common/pig-common-seata/src/main/resources/seata-config.yml ================================================ seata: enabled: true tx-service-group: pig_tx_group # 事务群组(可以每个应用独立取名,也可以使用相同的名字) client: rm-report-success-enable: true rm-table-meta-check-enable: false # 自动刷新缓存中的表结构(默认false) rm-report-retry-count: 5 # 一阶段结果上报TC重试次数(默认5) rm-async-commit-buffer-limit: 10000 # 异步提交缓存队列长度(默认10000) rm: lock: lock-retry-internal: 10 # 校验或占用全局锁重试间隔(默认10ms) lock-retry-times: 30 # 校验或占用全局锁重试次数(默认30) lock-retry-policy-branch-rollback-on-conflict: true # 分支事务与其它全局回滚事务冲突时锁策略(优先释放本地锁让回滚成功) tm-commit-retry-count: 3 # 一阶段全局提交结果上报TC重试次数(默认1次,建议大于1) tm-rollback-retry-count: 3 # 一阶段全局回滚结果上报TC重试次数(默认1次,建议大于1) undo: data-validation: true # 二阶段回滚镜像校验(默认true开启) log-serialization: jackson # undo序列化方式(默认jackson 不支持 LocalDateTime) log-table: undo_log # 自定义undo表名(默认undo_log) log: exceptionRate: 100 # 日志异常输出概率(默认100) support: spring: datasource-autoproxy: true service: vgroup-mapping: pig_tx_group: default # TC 集群(必须与seata-server保持一致) enable-degrade: false # 降级开关 disable-global-transaction: false # 禁用全局事务(默认false) grouplist: default: pig-seata:8091 transport: shutdown: wait: 3 thread-factory: boss-thread-prefix: NettyBoss worker-thread-prefix: NettyServerNIOWorker server-executor-thread-prefix: NettyServerBizHandler share-boss-worker: false client-selector-thread-prefix: NettyClientSelector client-selector-thread-size: 1 client-worker-thread-prefix: NettyClientWorkerThread type: TCP server: NIO heartbeat: true serialization: seata compressor: none enable-client-batch-send-request: true # 客户端事务消息请求是否批量合并发送(默认true) registry: file: name: file.conf type: file config: file: name: file.conf type: file ================================================ FILE: pig-common/pig-common-security/pom.xml ================================================ 4.0.0 com.pig4cloud pig-common ${revision} pig-common-security jar pig 安全工具类 com.pig4cloud pig-common-core cn.hutool hutool-extra com.pig4cloud pig-upms-api org.springframework.cloud spring-cloud-commons org.springframework.cloud spring-cloud-starter-openfeign org.springframework.security spring-security-oauth2-jose org.springframework.security spring-security-oauth2-authorization-server org.springframework spring-webmvc ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/annotation/EnablePigResourceServer.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.security.annotation; import com.pig4cloud.pig.common.security.component.PigResourceServerAutoConfiguration; import com.pig4cloud.pig.common.security.component.PigResourceServerConfiguration; import com.pig4cloud.pig.common.security.feign.PigFeignClientConfiguration; import org.springframework.context.annotation.Import; import java.lang.annotation.*; /** * 启用Pig资源服务器注解 *

* 通过导入相关配置类启用Pig资源服务器功能 * * @author lengleng * @date 2025/05/31 */ @Documented @Inherited @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Import({ PigResourceServerAutoConfiguration.class, PigResourceServerConfiguration.class, PigFeignClientConfiguration.class }) public @interface EnablePigResourceServer { } ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/annotation/HasPermission.java ================================================ package com.pig4cloud.pig.common.security.annotation; import org.springframework.security.access.prepost.PreAuthorize; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 权限注解:用于方法级别的权限控制 * * @author lengleng * @date 2025/05/31 */ @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @PreAuthorize("@pms.hasPermission('{value}'.split(','))") public @interface HasPermission { /** * 权限字符串 * @return {@link String[] } */ String[] value(); } ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/annotation/Inner.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.security.annotation; import java.lang.annotation.*; /** * 内部注解:用于标记方法或类型是否需要AOP统一处理 * * @author lengleng * @date 2025/05/31 */ @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Inner { /** * 是否AOP统一处理 * @return false, true */ boolean value() default true; /** * 需要特殊判空的字段(预留) * @return {} */ String[] field() default {}; } ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/component/PermissionService.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.security.component; import cn.hutool.core.util.ArrayUtil; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.PatternMatchUtils; import org.springframework.util.StringUtils; import java.util.Collection; /** * 接口权限判断工具类 * * @author lengleng * @date 2025/05/31 */ public class PermissionService { /** * 判断接口是否有任意xxx,xxx权限 * @param permissions 权限 * @return {boolean} */ public boolean hasPermission(String... permissions) { if (ArrayUtil.isEmpty(permissions)) { return false; } Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null) { return false; } Collection authorities = authentication.getAuthorities(); return authorities.stream() .map(GrantedAuthority::getAuthority) .filter(StringUtils::hasText) .anyMatch(x -> PatternMatchUtils.simpleMatch(permissions, x)); } } ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/component/PermitAllUrlProperties.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.security.component; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.regex.Pattern; import org.springframework.beans.factory.InitializingBean; import org.springframework.boot.context.properties.ConfigurationProperties; 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.pig4cloud.pig.common.security.annotation.Inner; import cn.hutool.core.util.ReUtil; import cn.hutool.extra.spring.SpringUtil; import lombok.Getter; import lombok.Setter; /** * 资源服务器对外直接暴露URL配置类 *

* 用于配置不需要认证即可访问的URL路径,支持路径变量替换 * * @author lengleng * @date 2025/05/31 */ @ConfigurationProperties(prefix = "security.oauth2.ignore") public class PermitAllUrlProperties implements InitializingBean { private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}"); private static final String[] DEFAULT_IGNORE_URLS = new String[] { "/actuator/**", "/error", "/v3/api-docs" }; @Getter @Setter private List urls = new ArrayList<>(); /** * 初始化方法,在属性设置完成后执行 收集带有@Inner注解的Controller方法路径,并将路径中的变量替换为* */ @Override public void afterPropertiesSet() { urls.addAll(Arrays.asList(DEFAULT_IGNORE_URLS)); RequestMappingHandlerMapping mapping = SpringUtil.getBean("requestMappingHandlerMapping"); Map map = mapping.getHandlerMethods(); map.keySet().forEach(info -> { HandlerMethod handlerMethod = map.get(info); // 获取方法上边的注解 替代path variable 为 * Inner method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Inner.class); Optional.ofNullable(method) .ifPresent(inner -> Objects.requireNonNull(info.getPathPatternsCondition()) .getPatternValues() .forEach(url -> urls.add(ReUtil.replaceAll(url, PATTERN, "*")))); // 获取类上边的注解, 替代path variable 为 * Inner controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Inner.class); Optional.ofNullable(controller) .ifPresent(inner -> Objects.requireNonNull(info.getPathPatternsCondition()) .getPatternValues() .forEach(url -> urls.add(ReUtil.replaceAll(url, PATTERN, "*")))); }); } } ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/component/PigBearerTokenExtractor.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.security.component; import jakarta.servlet.http.HttpServletRequest; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.server.resource.BearerTokenError; import org.springframework.security.oauth2.server.resource.BearerTokenErrors; import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; import org.springframework.util.AntPathMatcher; import org.springframework.util.PathMatcher; import org.springframework.util.StringUtils; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @author caiqy * @date 2020.05.15 */ public class PigBearerTokenExtractor implements BearerTokenResolver { private static final Pattern authorizationPattern = Pattern.compile("^Bearer (?[a-zA-Z0-9-:._~+/]+=*)$", Pattern.CASE_INSENSITIVE); private boolean allowFormEncodedBodyParameter = false; private boolean allowUriQueryParameter = true; private String bearerTokenHeaderName = HttpHeaders.AUTHORIZATION; private final PathMatcher pathMatcher = new AntPathMatcher(); private final PermitAllUrlProperties urlProperties; public PigBearerTokenExtractor(PermitAllUrlProperties urlProperties) { this.urlProperties = urlProperties; } @Override public String resolve(HttpServletRequest request) { boolean match = urlProperties.getUrls() .stream() .anyMatch(url -> pathMatcher.match(url, request.getRequestURI())); if (match) { return null; } final String authorizationHeaderToken = resolveFromAuthorizationHeader(request); final String parameterToken = isParameterTokenSupportedForRequest(request) ? resolveFromRequestParameters(request) : null; if (authorizationHeaderToken != null) { if (parameterToken != null) { final BearerTokenError error = BearerTokenErrors .invalidRequest("Found multiple bearer tokens in the request"); throw new OAuth2AuthenticationException(error); } return authorizationHeaderToken; } if (parameterToken != null && isParameterTokenEnabledForRequest(request)) { return parameterToken; } return null; } private String resolveFromAuthorizationHeader(HttpServletRequest request) { String authorization = request.getHeader(this.bearerTokenHeaderName); if (!StringUtils.startsWithIgnoreCase(authorization, "bearer")) { return null; } Matcher matcher = authorizationPattern.matcher(authorization); if (!matcher.matches()) { BearerTokenError error = BearerTokenErrors.invalidToken("Bearer token is malformed"); throw new OAuth2AuthenticationException(error); } return matcher.group("token"); } private static String resolveFromRequestParameters(HttpServletRequest request) { String[] values = request.getParameterValues("access_token"); if (values == null || values.length == 0) { return null; } if (values.length == 1) { return values[0]; } BearerTokenError error = BearerTokenErrors.invalidRequest("Found multiple bearer tokens in the request"); throw new OAuth2AuthenticationException(error); } private boolean isParameterTokenSupportedForRequest(final HttpServletRequest request) { return (("POST".equals(request.getMethod()) && MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType())) || "GET".equals(request.getMethod())); } private boolean isParameterTokenEnabledForRequest(final HttpServletRequest request) { return ((this.allowFormEncodedBodyParameter && "POST".equals(request.getMethod()) && MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType())) || (this.allowUriQueryParameter && "GET".equals(request.getMethod()))); } } ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/component/PigBootCorsProperties.java ================================================ package com.pig4cloud.pig.common.security.component; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import java.util.ArrayList; import java.util.List; /** * @author lengleng * @date 2025-11-12 *

* CORS 跨域资源共享配置属性 */ @Data @ConfigurationProperties(prefix = "security.cors") public class PigBootCorsProperties { /** * 是否启用CORS跨域支持 */ private Boolean enabled = false; /** * 允许的源模式列表,支持通配符 例如:http://localhost:*, https://*.example.com */ private List allowedOriginPatterns = new ArrayList<>(List.of("*")); /** * 允许的请求头列表 默认允许所有请求头 */ private List allowedHeaders = new ArrayList<>(List.of("*")); /** * 允许的HTTP方法列表 默认允许常用的HTTP方法 */ private List allowedMethods = new ArrayList<>(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH")); /** * 是否允许携带凭证(如Cookie) 当设置为true时,allowedOriginPatterns不能使用通配符* */ private Boolean allowCredentials = true; /** * 应用CORS配置的路径模式 默认应用到所有路径 */ private String pathPattern = "/**"; } ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/component/PigClientCredentialsOAuth2AuthenticatedPrincipal.java ================================================ package com.pig4cloud.pig.common.security.component; import lombok.RequiredArgsConstructor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import java.util.Collection; import java.util.Map; /** * 客户端模式凭证认证主体实现类 * * @author lengleng * @date 2025/05/31 */ @RequiredArgsConstructor public class PigClientCredentialsOAuth2AuthenticatedPrincipal implements OAuth2AuthenticatedPrincipal { private final Map attributes; private final Collection authorities; private final String name; /** * 获取属性集合 * @return 属性键值对集合 */ @Override public Map getAttributes() { return this.attributes; } /** * 获取用户权限集合 * @return 用户权限集合 */ @Override public Collection getAuthorities() { return this.authorities; } /** * 获取名称 * @return 当前对象的名称 */ @Override public String getName() { return this.name; } } ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/component/PigCustomOAuth2AccessTokenResponseHttpMessageConverter.java ================================================ package com.pig4cloud.pig.common.security.component; import com.fasterxml.jackson.databind.ObjectMapper; import com.pig4cloud.pig.common.core.util.SpringContextHolder; import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageNotWritableException; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.security.oauth2.core.endpoint.DefaultOAuth2AccessTokenResponseMapConverter; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; import java.util.Map; /** * 扩展OAuth2AccessTokenResponseHttpMessageConverter,支持Long类型转String的Token响应转换 * * @author lengleng * @date 2025/05/31 */ public class PigCustomOAuth2AccessTokenResponseHttpMessageConverter extends OAuth2AccessTokenResponseHttpMessageConverter { /** * 字符串到对象的映射类型引用 */ private static final ParameterizedTypeReference> STRING_OBJECT_MAP = new ParameterizedTypeReference>() { }; /** * OAuth2访问令牌响应参数转换器,用于将OAuth2AccessTokenResponse转换为Map */ private Converter> accessTokenResponseParametersConverter = new DefaultOAuth2AccessTokenResponseMapConverter(); /** * 将OAuth2访问令牌响应写入HTTP输出消息 * @param tokenResponse OAuth2访问令牌响应 * @param outputMessage HTTP输出消息 * @throws HttpMessageNotWritableException 写入响应时发生错误抛出异常 */ @Override protected void writeInternal(OAuth2AccessTokenResponse tokenResponse, HttpOutputMessage outputMessage) throws HttpMessageNotWritableException { try { Map tokenResponseParameters = this.accessTokenResponseParametersConverter .convert(tokenResponse); ObjectMapper objectMapper = SpringContextHolder.getBean(ObjectMapper.class); GenericHttpMessageConverter jsonMessageConverter = new MappingJackson2HttpMessageConverter( objectMapper); jsonMessageConverter.write(tokenResponseParameters, STRING_OBJECT_MAP.getType(), MediaType.APPLICATION_JSON, outputMessage); } catch (Exception ex) { throw new HttpMessageNotWritableException( "An error occurred writing the OAuth 2.0 Access Token Response: " + ex.getMessage(), ex); } } } ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/component/PigCustomOpaqueTokenIntrospector.java ================================================ package com.pig4cloud.pig.common.security.component; import cn.hutool.extra.spring.SpringUtil; import com.pig4cloud.pig.common.core.constant.SecurityConstants; import com.pig4cloud.pig.common.security.service.PigUser; import com.pig4cloud.pig.common.security.service.PigUserDetailsService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.core.Ordered; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException; import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; import java.security.Principal; import java.util.Comparator; import java.util.Map; import java.util.Objects; import java.util.Optional; /** * 自定义不透明令牌内省器,用于处理OAuth2不透明令牌的验证和用户信息获取 * * @author lengleng * @date 2025/05/31 */ @Slf4j @RequiredArgsConstructor public class PigCustomOpaqueTokenIntrospector implements OpaqueTokenIntrospector { /** * OAuth2授权服务 */ private final OAuth2AuthorizationService authorizationService; /** * 根据token内省获取认证主体信息 * @param token 访问令牌 * @return OAuth2认证主体信息 * @throws InvalidBearerTokenException 当token对应的授权信息不存在时抛出 * @throws UsernameNotFoundException 当用户不存在时抛出 */ @Override public OAuth2AuthenticatedPrincipal introspect(String token) { OAuth2Authorization oldAuthorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN); if (Objects.isNull(oldAuthorization)) { throw new InvalidBearerTokenException(token); } // 客户端模式默认返回 if (AuthorizationGrantType.CLIENT_CREDENTIALS.equals(oldAuthorization.getAuthorizationGrantType())) { return new DefaultOAuth2AuthenticatedPrincipal(oldAuthorization.getPrincipalName(), Objects.requireNonNull(oldAuthorization.getAccessToken().getClaims()), AuthorityUtils.NO_AUTHORITIES); } Map userDetailsServiceMap = SpringUtil .getBeansOfType(PigUserDetailsService.class); Optional optional = userDetailsServiceMap.values() .stream() .filter(service -> service.support(Objects.requireNonNull(oldAuthorization).getRegisteredClientId(), oldAuthorization.getAuthorizationGrantType().getValue())) .max(Comparator.comparingInt(Ordered::getOrder)); UserDetails userDetails = null; try { Object principal = Objects.requireNonNull(oldAuthorization).getAttributes().get(Principal.class.getName()); UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = (UsernamePasswordAuthenticationToken) principal; Object tokenPrincipal = usernamePasswordAuthenticationToken.getPrincipal(); userDetails = optional.get().loadUserByUser((PigUser) tokenPrincipal); } catch (UsernameNotFoundException notFoundException) { log.warn("用户不不存在 {}", notFoundException.getLocalizedMessage()); throw notFoundException; } catch (Exception ex) { log.error("资源服务器 introspect Token error {}", ex.getLocalizedMessage()); } // 注入客户端信息,方便上下文中获取 PigUser pigUser = (PigUser) userDetails; Objects.requireNonNull(pigUser) .getAttributes() .put(SecurityConstants.CLIENT_ID, oldAuthorization.getRegisteredClientId()); return pigUser; } } ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/component/PigResourceServerAutoConfiguration.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.security.component; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.MessageSource; import org.springframework.context.annotation.Bean; import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; /** * 资源服务器自动配置类 * * @author lengleng * @date 2025/05/31 */ @RequiredArgsConstructor @EnableConfigurationProperties(PermitAllUrlProperties.class) public class PigResourceServerAutoConfiguration { /** * 鉴权具体的实现逻辑 * @return (#pms.xxx) */ @Bean("pms") public PermissionService permissionService() { return new PermissionService(); } /** * 请求令牌的抽取逻辑 * @param urlProperties 对外暴露的接口列表 * @return BearerTokenExtractor */ @Bean public PigBearerTokenExtractor pigBearerTokenExtractor(PermitAllUrlProperties urlProperties) { return new PigBearerTokenExtractor(urlProperties); } /** * 资源服务器异常处理 * @param objectMapper jackson 输出对象 * @param securityMessageSource 自定义国际化处理器 * @return ResourceAuthExceptionEntryPoint */ @Bean public ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint(ObjectMapper objectMapper, MessageSource securityMessageSource) { return new ResourceAuthExceptionEntryPoint(objectMapper, securityMessageSource); } /** * 资源服务器toke内省处理器 * @param authorizationService token 存储实现 * @return TokenIntrospector */ @Bean public OpaqueTokenIntrospector opaqueTokenIntrospector(OAuth2AuthorizationService authorizationService) { return new PigCustomOpaqueTokenIntrospector(authorizationService); } /** * 支持自定义权限表达式 * @return {@link PrePostTemplateDefaults } */ @Bean AnnotationTemplateExpressionDefaults prePostTemplateDefaults() { return new AnnotationTemplateExpressionDefaults(); } } ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/component/PigResourceServerConfiguration.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.security.component; import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import lombok.RequiredArgsConstructor; /** * 资源服务器认证授权配置 * * @author lengleng * @date 2025/05/31 */ @EnableWebSecurity @EnableMethodSecurity @RequiredArgsConstructor public class PigResourceServerConfiguration { /** * 资源认证异常处理入口点 */ protected final ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint; /** * 允许所有URL的配置属性 */ private final PermitAllUrlProperties permitAllUrl; /** * PigBearerToken提取器 */ private final PigBearerTokenExtractor pigBearerTokenExtractor; /** * 自定义不透明令牌解析器 */ private final OpaqueTokenIntrospector customOpaqueTokenIntrospector; /** * CORS跨域资源共享配置属性 */ private final PigBootCorsProperties pigBootCorsProperties; /** * 资源服务器安全配置 * @param http http * @return {@link SecurityFilterChain } * @throws Exception 异常 */ @Bean SecurityFilterChain resourceServer(HttpSecurity http) throws Exception { /** * AntPathRequestMatcher[] permitMatchers = permitAllUrl.getUrls() .stream() * .map(AntPathRequestMatcher::new) .toList() .toArray(new AntPathRequestMatcher[] * {}); **/ PathPatternRequestMatcher[] permitMatchers = permitAllUrl.getUrls() .stream() .map(url -> PathPatternRequestMatcher.withDefaults().matcher(url)) .toList() .toArray(new PathPatternRequestMatcher[] {}); http.authorizeHttpRequests(authorizeRequests -> authorizeRequests.requestMatchers(permitMatchers) .permitAll() .anyRequest() .authenticated()) .oauth2ResourceServer( oauth2 -> oauth2.opaqueToken(token -> token.introspector(customOpaqueTokenIntrospector)) .authenticationEntryPoint(resourceAuthExceptionEntryPoint) .bearerTokenResolver(pigBearerTokenExtractor)) .headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable)) .csrf(AbstractHttpConfigurer::disable); // 配置 CORS 跨域资源共享 if (Boolean.TRUE.equals(pigBootCorsProperties.getEnabled())) { http.cors(cors -> cors.configurationSource(corsConfigurationSource())); } return http.build(); } /** * 配置 CORS 跨域资源共享 * @return UrlBasedCorsConfigurationSource CORS配置源 */ private UrlBasedCorsConfigurationSource corsConfigurationSource() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration corsConfiguration = new CorsConfiguration(); // 从配置文件读取允许的源模式 pigBootCorsProperties.getAllowedOriginPatterns().forEach(corsConfiguration::addAllowedOriginPattern); // 从配置文件读取允许的请求头 pigBootCorsProperties.getAllowedHeaders().forEach(corsConfiguration::addAllowedHeader); // 从配置文件读取允许的HTTP方法 pigBootCorsProperties.getAllowedMethods().forEach(corsConfiguration::addAllowedMethod); // 从配置文件读取是否允许携带凭证 corsConfiguration.setAllowCredentials(pigBootCorsProperties.getAllowCredentials()); // 注册CORS配置到指定路径 source.registerCorsConfiguration(pigBootCorsProperties.getPathPattern(), corsConfiguration); return source; } } ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/component/PigSecurityInnerAspect.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.security.component; import cn.hutool.core.util.StrUtil; import com.pig4cloud.pig.common.core.constant.SecurityConstants; import com.pig4cloud.pig.common.security.annotation.Inner; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.security.access.AccessDeniedException; /** * 服务间接口不鉴权处理切面 * * @author lengleng * @date 2025/05/31 */ @Slf4j @Aspect @RequiredArgsConstructor public class PigSecurityInnerAspect implements Ordered { private final HttpServletRequest request; /** * 环绕通知,用于检查内部调用权限 * @param point 切点对象 * @param inner 内部调用注解 * @throws AccessDeniedException 当无权限访问时抛出异常 */ @SneakyThrows @Before("@within(inner) || @annotation(inner)") public void around(JoinPoint point, Inner inner) { // 实际注入的inner实体由表达式后一个注解决定,即是方法上的@Inner注解实体,若方法上无@Inner注解,则获取类上的 if (inner == null) { Class clazz = point.getTarget().getClass(); inner = AnnotationUtils.findAnnotation(clazz, Inner.class); } String header = request.getHeader(SecurityConstants.FROM); if (inner.value() && !StrUtil.equals(SecurityConstants.FROM_IN, header)) { log.warn("访问接口 {} 没有权限", point.getSignature().getName()); throw new AccessDeniedException("Access is denied"); } } @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE + 1; } } ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/component/PigSecurityMessageSourceConfiguration.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.security.component; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.context.MessageSource; import org.springframework.context.annotation.Bean; import org.springframework.context.support.ReloadableResourceBundleMessageSource; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.Locale; import static org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type.SERVLET; /** * 创建并配置安全相关的消息源 * * @return 配置好的ReloadableResourceBundleMessageSource实例 */ @ConditionalOnWebApplication(type = SERVLET) public class PigSecurityMessageSourceConfiguration implements WebMvcConfigurer { /** * 创建并配置安全相关的消息源 * @return 配置好的ReloadableResourceBundleMessageSource实例 */ @Bean public MessageSource securityMessageSource() { ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); messageSource.addBasenames("classpath:i18n/errors/messages"); messageSource.setDefaultLocale(Locale.CHINA); return messageSource; } } ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/component/ResourceAuthExceptionEntryPoint.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.security.component; import com.fasterxml.jackson.databind.ObjectMapper; import com.pig4cloud.pig.common.core.constant.CommonConstants; import com.pig4cloud.pig.common.core.util.R; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import org.springframework.context.MessageSource; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.http.HttpStatus; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException; import org.springframework.security.web.AuthenticationEntryPoint; import java.io.PrintWriter; /** * 资源认证异常处理入口点,用于处理客户端认证异常 * * @author lengleng * @date 2019/2/1 */ @RequiredArgsConstructor public class ResourceAuthExceptionEntryPoint implements AuthenticationEntryPoint { private final ObjectMapper objectMapper; private final MessageSource messageSource; /** * 处理认证失败的响应 * @param request HTTP请求 * @param response HTTP响应 * @param authException 认证异常 * @throws Exception 写入响应时可能抛出异常 */ @Override @SneakyThrows public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) { response.setCharacterEncoding(CommonConstants.UTF8); response.setContentType(CommonConstants.CONTENT_TYPE); R result = new R<>(); result.setCode(CommonConstants.FAIL); response.setStatus(HttpStatus.UNAUTHORIZED.value()); if (authException != null) { result.setMsg("error"); result.setData(authException.getMessage()); } // 针对令牌过期返回特殊的 424 if (authException instanceof InvalidBearerTokenException || authException instanceof InsufficientAuthenticationException) { response.setStatus(org.springframework.http.HttpStatus.FAILED_DEPENDENCY.value()); result.setMsg(this.messageSource.getMessage("OAuth2ResourceOwnerBaseAuthenticationProvider.tokenExpired", null, LocaleContextHolder.getLocale())); } PrintWriter printWriter = response.getWriter(); printWriter.append(objectMapper.writeValueAsString(result)); } } ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/feign/PigFeignClientConfiguration.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.security.feign; import feign.RequestInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; /** * Pig Feign 客户端配置类 * * @author lengleng * @date 2025/05/31 */ public class PigFeignClientConfiguration { /** * 注入 oauth2 feign token 增强 * @param tokenResolver token获取处理器 * @return 拦截器 */ @Bean public RequestInterceptor oauthRequestInterceptor(BearerTokenResolver tokenResolver) { return new PigOAuthRequestInterceptor(tokenResolver); } } ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/feign/PigOAuthRequestInterceptor.java ================================================ package com.pig4cloud.pig.common.security.feign; import java.util.Collection; import org.springframework.http.HttpHeaders; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; import com.pig4cloud.pig.common.core.constant.SecurityConstants; import com.pig4cloud.pig.common.core.util.WebUtils; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import feign.RequestInterceptor; import feign.RequestTemplate; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; /** * oauth2 feign token传递 * * 重新 OAuth2FeignRequestInterceptor ,官方实现部分常见不适用 * * @author lengleng * @date 2022/5/29 */ @RequiredArgsConstructor public class PigOAuthRequestInterceptor implements RequestInterceptor { /** * 用于解析Bearer令牌的解析器 */ private final BearerTokenResolver tokenResolver; /** * Create a template with the header of provided name and extracted extract
* * 1. 如果使用 非web 请求,header 区别
* * 2. 根据authentication 还原请求token * @param template */ @Override public void apply(RequestTemplate template) { Collection fromHeader = template.headers().get(SecurityConstants.FROM); // 带from 请求直接跳过 if (CollUtil.isNotEmpty(fromHeader) && fromHeader.contains(SecurityConstants.FROM_IN)) { return; } // 非web 请求直接跳过 if (!WebUtils.getRequest().isPresent()) { return; } HttpServletRequest request = WebUtils.getRequest().get(); // 避免请求参数的 query token 无法传递 String token = tokenResolver.resolve(request); if (StrUtil.isBlank(token)) { return; } template.header(HttpHeaders.AUTHORIZATION, String.format("%s %s", OAuth2AccessToken.TokenType.BEARER.getValue(), token)); } } ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/service/PigAppUserDetailsServiceImpl.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.security.service; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.security.core.userdetails.UserDetails; import com.pig4cloud.pig.admin.api.dto.UserDTO; import com.pig4cloud.pig.admin.api.dto.UserInfo; import com.pig4cloud.pig.admin.api.feign.RemoteUserService; import com.pig4cloud.pig.common.core.constant.CacheConstants; import com.pig4cloud.pig.common.core.constant.SecurityConstants; import com.pig4cloud.pig.common.core.util.R; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; /** * 用户详细信息服务实现类,提供基于手机号的用户信息加载功能 * * @author lengleng hccake * @date 2025/05/31 */ @RequiredArgsConstructor public class PigAppUserDetailsServiceImpl implements PigUserDetailsService { private final RemoteUserService remoteUserService; private final CacheManager cacheManager; /** * 根据手机号加载用户信息 * @param phone 用户手机号 * @return 用户详细信息 * @throws Exception 获取用户信息过程中可能抛出的异常 */ @Override @SneakyThrows public UserDetails loadUserByUsername(String phone) { Cache cache = cacheManager.getCache(CacheConstants.USER_DETAILS); if (cache != null && cache.get(phone) != null) { return (PigUser) cache.get(phone).get(); } UserDTO userDTO = new UserDTO(); userDTO.setPhone(phone); R result = remoteUserService.info(userDTO); UserDetails userDetails = getUserDetails(result); if (cache != null) { cache.put(phone, userDetails); } return userDetails; } /** * 根据用户信息加载用户详情 * @param pigUser 用户信息对象 * @return 用户详情 */ @Override public UserDetails loadUserByUser(PigUser pigUser) { return this.loadUserByUsername(pigUser.getPhone()); } /** * 是否支持此客户端校验 * @param clientId 目标客户端 * @return true/false */ @Override public boolean support(String clientId, String grantType) { return SecurityConstants.MOBILE.equals(grantType); } } ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/service/PigRedisOAuth2AuthorizationConsentService.java ================================================ package com.pig4cloud.pig.common.security.service; import com.pig4cloud.pig.common.core.util.RedisUtils; import lombok.RequiredArgsConstructor; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; import org.springframework.util.Assert; import java.util.concurrent.TimeUnit; /** * 基于Redis实现的OAuth2授权同意服务 * * @author lengleng * @date 2025/05/31 */ @RequiredArgsConstructor public class PigRedisOAuth2AuthorizationConsentService implements OAuth2AuthorizationConsentService { private final static Long TIMEOUT = 10L; /** * 保存OAuth2授权同意信息 * @param authorizationConsent 授权同意信息,不能为null * @throws IllegalArgumentException 当authorizationConsent为null时抛出 */ @Override public void save(OAuth2AuthorizationConsent authorizationConsent) { Assert.notNull(authorizationConsent, "authorizationConsent cannot be null"); RedisUtils.set(buildKey(authorizationConsent), authorizationConsent, TIMEOUT, TimeUnit.MINUTES); } /** * 移除OAuth2授权同意信息 * @param authorizationConsent 授权同意信息,不能为null * @throws IllegalArgumentException 当authorizationConsent为null时抛出 */ @Override public void remove(OAuth2AuthorizationConsent authorizationConsent) { Assert.notNull(authorizationConsent, "authorizationConsent cannot be null"); RedisUtils.delete(buildKey(authorizationConsent)); } /** * 根据注册客户端ID和主体名称查找OAuth2授权同意信息 * @param registeredClientId 注册客户端ID,不能为空 * @param principalName 主体名称,不能为空 * @return 查找到的OAuth2授权同意信息,可能为null */ @Override public OAuth2AuthorizationConsent findById(String registeredClientId, String principalName) { Assert.hasText(registeredClientId, "registeredClientId cannot be empty"); Assert.hasText(principalName, "principalName cannot be empty"); return RedisUtils.get(buildKey(registeredClientId, principalName)); } /** * 构建授权确认信息的key * @param registeredClientId 注册客户端ID * @param principalName 主体名称 * @return 拼接后的key字符串 */ private static String buildKey(String registeredClientId, String principalName) { return "token:consent:" + registeredClientId + ":" + principalName; } /** * 构建授权同意的键值 * @param authorizationConsent 授权同意对象 * @return 构建的键值字符串 */ private static String buildKey(OAuth2AuthorizationConsent authorizationConsent) { return buildKey(authorizationConsent.getRegisteredClientId(), authorizationConsent.getPrincipalName()); } } ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/service/PigRedisOAuth2AuthorizationService.java ================================================ package com.pig4cloud.pig.common.security.service; import com.pig4cloud.pig.common.core.util.RedisUtils; import lombok.RequiredArgsConstructor; import org.springframework.lang.Nullable; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2RefreshToken; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; import org.springframework.util.Assert; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; /** * 基于Redis实现的OAuth2授权服务类 * * @author lengleng * @date 2025/05/31 */ @RequiredArgsConstructor public class PigRedisOAuth2AuthorizationService implements OAuth2AuthorizationService { private final static Long TIMEOUT = 10L; private static final String AUTHORIZATION = "token"; /** * 保存OAuth2授权信息到Redis * @param authorization 授权信息对象,不能为null * @throws IllegalArgumentException 当authorization为null时抛出异常 */ @Override public void save(OAuth2Authorization authorization) { Assert.notNull(authorization, "authorization cannot be null"); if (isState(authorization)) { String token = authorization.getAttribute("state"); RedisUtils.set(buildKey(OAuth2ParameterNames.STATE, token), authorization, TIMEOUT, TimeUnit.MINUTES); } if (isCode(authorization)) { OAuth2Authorization.Token authorizationCode = authorization .getToken(OAuth2AuthorizationCode.class); OAuth2AuthorizationCode authorizationCodeToken = authorizationCode.getToken(); long between = ChronoUnit.MINUTES.between(authorizationCodeToken.getIssuedAt(), authorizationCodeToken.getExpiresAt()); RedisUtils.set(buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue()), authorization, between, TimeUnit.MINUTES); } if (isRefreshToken(authorization)) { OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken(); long between = ChronoUnit.SECONDS.between(refreshToken.getIssuedAt(), refreshToken.getExpiresAt()); RedisUtils.set(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken.getTokenValue()), authorization, between, TimeUnit.SECONDS); } if (isAccessToken(authorization)) { OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); long between = ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()); RedisUtils.set(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue()), authorization, between, TimeUnit.SECONDS); } } /** * 移除OAuth2授权信息 * @param authorization 要移除的授权信息,不能为null * @throws IllegalArgumentException 当authorization为null时抛出 */ @Override public void remove(OAuth2Authorization authorization) { Assert.notNull(authorization, "authorization cannot be null"); List keys = new ArrayList<>(); if (isState(authorization)) { String token = authorization.getAttribute("state"); keys.add(buildKey(OAuth2ParameterNames.STATE, token)); } if (isCode(authorization)) { OAuth2Authorization.Token authorizationCode = authorization .getToken(OAuth2AuthorizationCode.class); OAuth2AuthorizationCode authorizationCodeToken = authorizationCode.getToken(); keys.add(buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue())); } if (isRefreshToken(authorization)) { OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken(); keys.add(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken.getTokenValue())); } if (isAccessToken(authorization)) { OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); keys.add(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue())); } RedisUtils.delete(keys.toArray(String[]::new)); } /** * 根据ID查询OAuth2授权信息 * @param id 授权ID * @return 授权信息,可能为null * @throws UnsupportedOperationException 当前不支持此操作 */ @Override @Nullable public OAuth2Authorization findById(String id) { throw new UnsupportedOperationException(); } /** * 根据token和token类型查询OAuth2授权信息 * @param token token值 * @param tokenType token类型,不能为null * @return 授权信息对象,可能为null * @throws IllegalArgumentException 当token为空或tokenType为null时抛出 */ @Override @Nullable public OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType) { Assert.hasText(token, "token cannot be empty"); Assert.notNull(tokenType, "tokenType cannot be empty"); return RedisUtils.get(buildKey(tokenType.getValue(), token)); } /** * 构建key * @param type 类型 * @param id ID * @return 拼接后的key字符串 */ private String buildKey(String type, String id) { return String.format("%s::%s::%s", AUTHORIZATION, type, id); } /** * 检查授权对象是否包含state属性 * @param authorization 授权对象 * @return 如果包含state属性返回true,否则返回false */ private static boolean isState(OAuth2Authorization authorization) { return Objects.nonNull(authorization.getAttribute("state")); } /** * 检查授权对象是否包含授权码 * @param authorization 授权对象 * @return 如果包含授权码返回true,否则返回false */ private static boolean isCode(OAuth2Authorization authorization) { OAuth2Authorization.Token authorizationCode = authorization .getToken(OAuth2AuthorizationCode.class); return Objects.nonNull(authorizationCode); } /** * 判断授权是否包含刷新令牌 * @param authorization 授权信息 * @return 如果包含刷新令牌返回true,否则返回false */ private static boolean isRefreshToken(OAuth2Authorization authorization) { return Objects.nonNull(authorization.getRefreshToken()); } /** * 判断授权对象是否包含访问令牌 * @param authorization 授权对象 * @return 如果包含访问令牌返回true,否则返回false */ private static boolean isAccessToken(OAuth2Authorization authorization) { return Objects.nonNull(authorization.getAccessToken()); } } ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/service/PigRemoteRegisteredClientRepository.java ================================================ package com.pig4cloud.pig.common.security.service; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.StrUtil; import com.pig4cloud.pig.admin.api.entity.SysOauthClientDetails; import com.pig4cloud.pig.admin.api.feign.RemoteClientDetailsService; import com.pig4cloud.pig.common.core.constant.CacheConstants; import com.pig4cloud.pig.common.core.constant.SecurityConstants; import com.pig4cloud.pig.common.core.util.RetOps; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import org.springframework.cache.annotation.Cacheable; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; import java.time.Duration; import java.util.Arrays; import java.util.Optional; /** * 查询客户端相关信息实现类,支持Redis缓存 * * @author lengleng * @date 2025/05/31 */ @RequiredArgsConstructor public class PigRemoteRegisteredClientRepository implements RegisteredClientRepository { /** * 刷新令牌有效期默认 30 天 */ private final static int refreshTokenValiditySeconds = 60 * 60 * 24 * 30; /** * 请求令牌有效期默认 12 小时 */ private final static int accessTokenValiditySeconds = 60 * 60 * 12; /** * 远程客户端详情服务 */ private final RemoteClientDetailsService clientDetailsService; /** * 保存注册的客户端 * *

* 重要提示:敏感信息应在实现外部进行编码,例如 {@link RegisteredClient#getClientSecret()} *

* @param registeredClient 要保存的注册客户端 */ @Override public void save(RegisteredClient registeredClient) { } /** * 根据ID查找已注册的客户端 * @param id 注册标识符 * @return 找到的{@link RegisteredClient},未找到则返回{@code null} */ @Override public RegisteredClient findById(String id) { throw new UnsupportedOperationException(); } /** * 根据客户端ID查询注册客户端信息,支持Redis缓存 * @param clientId 客户端ID * @return 注册客户端信息 * @throws OAuth2AuthorizationCodeRequestAuthenticationException 客户端查询异常时抛出 */ @Override @SneakyThrows @Cacheable(value = CacheConstants.CLIENT_DETAILS_KEY, key = "#clientId", unless = "#result == null") public RegisteredClient findByClientId(String clientId) { SysOauthClientDetails clientDetails = RetOps.of(clientDetailsService.getClientDetailsById(clientId)) .getData() .orElseThrow(() -> new OAuth2AuthorizationCodeRequestAuthenticationException( new OAuth2Error("客户端查询异常,请检查数据库链接"), null)); RegisteredClient.Builder builder = RegisteredClient.withId(clientDetails.getClientId()) .clientId(clientDetails.getClientId()) .clientSecret(SecurityConstants.NOOP + clientDetails.getClientSecret()) .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC); for (String authorizedGrantType : clientDetails.getAuthorizedGrantTypes()) { builder.authorizationGrantType(new AuthorizationGrantType(authorizedGrantType)); } // 回调地址 Optional.ofNullable(clientDetails.getWebServerRedirectUri()) .ifPresent(redirectUri -> Arrays.stream(redirectUri.split(StrUtil.COMMA)) .filter(StrUtil::isNotBlank) .forEach(builder::redirectUri)); // scope Optional.ofNullable(clientDetails.getScope()) .ifPresent(scope -> Arrays.stream(scope.split(StrUtil.COMMA)) .filter(StrUtil::isNotBlank) .forEach(builder::scope)); return builder .tokenSettings(TokenSettings.builder() .accessTokenFormat(OAuth2TokenFormat.REFERENCE) .accessTokenTimeToLive(Duration.ofSeconds( Optional.ofNullable(clientDetails.getAccessTokenValidity()).orElse(accessTokenValiditySeconds))) .refreshTokenTimeToLive(Duration.ofSeconds(Optional.ofNullable(clientDetails.getRefreshTokenValidity()) .orElse(refreshTokenValiditySeconds))) .build()) .clientSettings(ClientSettings.builder() .requireAuthorizationConsent(!BooleanUtil.toBoolean(clientDetails.getAutoapprove())) .build()) .build(); } } ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/service/PigUser.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.security.service; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import lombok.Getter; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.SpringSecurityCoreVersion; import org.springframework.security.core.userdetails.User; import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import java.io.Serial; import java.util.Collection; import java.util.HashMap; import java.util.Map; /** * 扩展用户信息类,继承自User并实现OAuth2AuthenticatedPrincipal接口 * * @author lengleng * @date 2025/05/31 */ public class PigUser extends User implements OAuth2AuthenticatedPrincipal { @Serial private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; /** * 扩展属性,方便存放oauth 上下文相关信息 */ private final Map attributes = new HashMap<>(); /** * 用户ID */ @Getter @JsonSerialize(using = ToStringSerializer.class) private final Long id; /** * 部门ID */ @Getter @JsonSerialize(using = ToStringSerializer.class) private final Long deptId; /** * 手机号 */ @Getter private final String phone; public PigUser(Long id, Long deptId, String username, String password, String phone, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection authorities) { super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); this.id = id; this.deptId = deptId; this.phone = phone; } /** * 获取OAuth 2.0令牌属性 * @return OAuth 2.0令牌属性Map */ @Override public Map getAttributes() { return this.attributes; } /** * 获取用户名称 * @return 用户名称 */ @Override public String getName() { return this.getUsername(); } } ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/service/PigUserDetailsService.java ================================================ package com.pig4cloud.pig.common.security.service; import cn.hutool.core.util.StrUtil; import com.pig4cloud.pig.admin.api.dto.UserInfo; import com.pig4cloud.pig.common.core.constant.CommonConstants; import com.pig4cloud.pig.common.core.constant.SecurityConstants; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.core.util.RetOps; import org.springframework.core.Ordered; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import java.util.Collection; import java.util.HashSet; import java.util.Set; /** * 用户详情服务接口,扩展了Spring Security的UserDetailsService和Ordered接口 提供用户详情加载、客户端支持校验及排序功能 * * @author lengleng * @date 2025/05/31 */ public interface PigUserDetailsService extends UserDetailsService, Ordered { /** * 是否支持此客户端校验 * @param clientId 目标客户端 * @return true/false */ default boolean support(String clientId, String grantType) { return true; } /** * 排序值 默认取最大的 * @return 排序值 */ default int getOrder() { return 0; } /** * 根据用户信息构建UserDetails对象 * @param result 包含用户信息的R对象 * @return 构建好的UserDetails对象 * @throws UsernameNotFoundException 当用户信息不存在时抛出异常 */ default UserDetails getUserDetails(R result) { UserInfo info = RetOps.of(result).getData().orElseThrow(() -> new UsernameNotFoundException("用户不存在")); Set dbAuthsSet = new HashSet<>(); // 维护角色列表 info.getRoleList().forEach(role -> dbAuthsSet.add(SecurityConstants.ROLE + role.getRoleId())); // 维护权限列表 dbAuthsSet.addAll(info.getPermissions()); Collection authorities = AuthorityUtils .createAuthorityList(dbAuthsSet.toArray(new String[0])); // 构造security用户 return new PigUser(info.getUserId(), info.getDept().getDeptId(), info.getUsername(), SecurityConstants.BCRYPT + info.getPassword(), info.getPhone(), true, true, true, StrUtil.equals(info.getLockFlag(), CommonConstants.STATUS_NORMAL), authorities); } /** * 通过用户实体查询用户详情 * @param pigUser 用户实体对象 * @return 用户详情信息 */ default UserDetails loadUserByUser(PigUser pigUser) { return this.loadUserByUsername(pigUser.getUsername()); } } ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/service/PigUserDetailsServiceImpl.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.security.service; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.context.annotation.Primary; import org.springframework.security.core.userdetails.UserDetails; import com.pig4cloud.pig.admin.api.dto.UserDTO; import com.pig4cloud.pig.admin.api.dto.UserInfo; import com.pig4cloud.pig.admin.api.feign.RemoteUserService; import com.pig4cloud.pig.common.core.constant.CacheConstants; import com.pig4cloud.pig.common.core.util.R; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; /** * 用户详情服务实现类,提供基于用户名加载用户详情功能 * * @author lengleng * @author hccake * @date 2025/05/31 */ @Primary @RequiredArgsConstructor public class PigUserDetailsServiceImpl implements PigUserDetailsService { private final RemoteUserService remoteUserService; private final CacheManager cacheManager; /** * 根据用户名加载用户详情 * @param username 用户名 * @return 用户详情信息 * @throws Exception 获取用户信息过程中可能抛出的异常 */ @Override @SneakyThrows public UserDetails loadUserByUsername(String username) { Cache cache = cacheManager.getCache(CacheConstants.USER_DETAILS); if (cache != null && cache.get(username) != null) { return (PigUser) cache.get(username).get(); } UserDTO userDTO = new UserDTO(); userDTO.setUsername(username); R result = remoteUserService.info(userDTO); UserDetails userDetails = getUserDetails(result); if (cache != null) { cache.put(username, userDetails); } return userDetails; } @Override public int getOrder() { return Integer.MIN_VALUE; } } ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/util/OAuth2EndpointUtils.java ================================================ package com.pig4cloud.pig.common.security.util; import cn.hutool.core.map.MapUtil; import jakarta.servlet.http.HttpServletRequest; import lombok.experimental.UtilityClass; import org.springframework.security.oauth2.core.*; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import java.time.temporal.ChronoUnit; import java.util.Map; /** * OAuth2 端点工具类 * *

* 提供OAuth2相关端点操作的实用方法 *

* * @author lengleng * @author jumuning * @date 2025/05/31 */ @UtilityClass public class OAuth2EndpointUtils { public final String ACCESS_TOKEN_REQUEST_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2"; /** * 从HttpServletRequest中获取参数并转换为MultiValueMap * @param request HttpServletRequest对象 * @return 包含所有参数的MultiValueMap,key为参数名,value为参数值列表 */ public MultiValueMap getParameters(HttpServletRequest request) { Map parameterMap = request.getParameterMap(); MultiValueMap parameters = new LinkedMultiValueMap<>(parameterMap.size()); parameterMap.forEach((key, values) -> { for (String value : values) { parameters.add(key, value); } }); return parameters; } /** * 检查请求是否符合PKCE令牌请求规范 * @param request HTTP请求对象 * @return 如果请求是授权码模式且包含code和code_verifier参数则返回true,否则返回false */ public boolean matchesPkceTokenRequest(HttpServletRequest request) { return AuthorizationGrantType.AUTHORIZATION_CODE.getValue() .equals(request.getParameter(OAuth2ParameterNames.GRANT_TYPE)) && request.getParameter(OAuth2ParameterNames.CODE) != null && request.getParameter(PkceParameterNames.CODE_VERIFIER) != null; } /** * 抛出OAuth2认证异常 * @param errorCode 错误码 * @param parameterName 参数名称 * @param errorUri 错误URI * @throws OAuth2AuthenticationException OAuth2认证异常 */ public void throwError(String errorCode, String parameterName, String errorUri) { OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, errorUri); throw new OAuth2AuthenticationException(error); } /** * 发送OAuth2访问令牌响应 * @param authentication 用户认证信息 * @param claims 扩展信息 * @return OAuth2访问令牌响应 */ public OAuth2AccessTokenResponse sendAccessTokenResponse(OAuth2Authorization authentication, Map claims) { OAuth2AccessToken accessToken = authentication.getAccessToken().getToken(); OAuth2RefreshToken refreshToken = authentication.getRefreshToken().getToken(); OAuth2AccessTokenResponse.Builder builder = OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue()) .tokenType(accessToken.getTokenType()) .scopes(accessToken.getScopes()); if (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) { builder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt())); } if (refreshToken != null) { builder.refreshToken(refreshToken.getTokenValue()); } if (MapUtil.isNotEmpty(claims)) { builder.additionalParameters(claims); } return builder.build(); } } ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/util/OAuth2ErrorCodesExpand.java ================================================ package com.pig4cloud.pig.common.security.util; /** * OAuth2 异常信息常量接口 * * @author lengleng * @date 2025/05/31 */ public interface OAuth2ErrorCodesExpand { /** 用户名未找到 */ String USERNAME_NOT_FOUND = "username_not_found"; /** 错误凭证 */ String BAD_CREDENTIALS = "bad_credentials"; /** 用户被锁 */ String USER_LOCKED = "user_locked"; /** 用户禁用 */ String USER_DISABLE = "user_disable"; /** 用户过期 */ String USER_EXPIRED = "user_expired"; /** 证书过期 */ String CREDENTIALS_EXPIRED = "credentials_expired"; /** scope 为空异常 */ String SCOPE_IS_EMPTY = "scope_is_empty"; /** * 令牌不存在 */ String TOKEN_MISSING = "token_missing"; /** 未知的登录异常 */ String UN_KNOW_LOGIN_ERROR = "un_know_login_error"; /** * 不合法的Token */ String INVALID_BEARER_TOKEN = "invalid_bearer_token"; } ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/util/OAuthClientException.java ================================================ package com.pig4cloud.pig.common.security.util; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2Error; import java.io.Serial; /** * OAuth客户端异常类 * * @author lengleng * @date 2025/05/31 */ public class OAuthClientException extends OAuth2AuthenticationException { @Serial private static final long serialVersionUID = 1L; /** * 使用指定消息构造OAuthClientException * @param msg 详细消息 */ public OAuthClientException(String msg) { super(new OAuth2Error(msg), msg); } /** * 构造一个带有指定消息和根原因的OAuthClientException * @param msg 详细消息 * @param cause 根原因 */ public OAuthClientException(String msg, Throwable cause) { super(new OAuth2Error(msg), cause); } } ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/util/ScopeException.java ================================================ package com.pig4cloud.pig.common.security.util; import java.io.Serial; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2Error; /** * ScopeException 异常类,用于处理OAuth2认证过程中的作用域异常 * * @author lengleng * @date 2025/05/31 */ public class ScopeException extends OAuth2AuthenticationException { @Serial private static final long serialVersionUID = 1L; /** * 使用指定消息构造ScopeException * @param msg 详细消息 */ public ScopeException(String msg) { super(new OAuth2Error(msg), msg); } /** * 使用指定的错误信息和根异常构造ScopeException * @param msg 错误详细信息 * @param cause 根异常 */ public ScopeException(String msg, Throwable cause) { super(new OAuth2Error(msg), cause); } } ================================================ FILE: pig-common/pig-common-security/src/main/java/com/pig4cloud/pig/common/security/util/SecurityUtils.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.security.util; import cn.hutool.core.util.StrUtil; import com.pig4cloud.pig.common.core.constant.SecurityConstants; import com.pig4cloud.pig.common.security.service.PigUser; import lombok.experimental.UtilityClass; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * 安全工具类 * * @author lengleng * @date 2025/05/31 */ @UtilityClass public class SecurityUtils { /** * 获取当前安全上下文的认证信息 * @return 当前认证信息对象 */ public Authentication getAuthentication() { return SecurityContextHolder.getContext().getAuthentication(); } /** * 获取当前认证用户 * @param authentication 认证信息 * @return 用户对象,如果认证主体不是PigUser类型则返回null */ public PigUser getUser(Authentication authentication) { Object principal = authentication.getPrincipal(); if (principal instanceof PigUser) { return (PigUser) principal; } return null; } /** * 获取当前认证用户 * @return 当前认证用户对象,未认证时返回null */ public PigUser getUser() { Authentication authentication = getAuthentication(); if (authentication == null) { return null; } return getUser(authentication); } /** * 获取用户角色信息 * @return 角色集合 */ public List getRoles() { Authentication authentication = getAuthentication(); Collection authorities = authentication.getAuthorities(); List roleIds = new ArrayList<>(); authorities.stream() .filter(granted -> StrUtil.startWith(granted.getAuthority(), SecurityConstants.ROLE)) .forEach(granted -> { String id = StrUtil.removePrefix(granted.getAuthority(), SecurityConstants.ROLE); roleIds.add(Long.parseLong(id)); }); return roleIds; } } ================================================ FILE: pig-common/pig-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ================================================ com.pig4cloud.pig.common.security.service.PigUserDetailsServiceImpl com.pig4cloud.pig.common.security.service.PigAppUserDetailsServiceImpl com.pig4cloud.pig.common.security.service.PigRedisOAuth2AuthorizationService com.pig4cloud.pig.common.security.service.PigRedisOAuth2AuthorizationConsentService com.pig4cloud.pig.common.security.component.PigSecurityInnerAspect com.pig4cloud.pig.common.security.component.PigSecurityMessageSourceConfiguration com.pig4cloud.pig.common.security.component.PigBootCorsProperties com.pig4cloud.pig.common.security.service.PigRemoteRegisteredClientRepository ================================================ FILE: pig-common/pig-common-security/src/main/resources/i18n/errors/messages_zh_CN.properties ================================================ AbstractAccessDecisionManager.accessDenied=\u4E0D\u5141\u8BB8\u8BBF\u95EE AbstractLdapAuthenticationProvider.emptyPassword=\u7528\u6237\u540D\u6216\u5BC6\u7801\u9519\u8BEF AbstractSecurityInterceptor.authenticationNotFound=\u672A\u5728SecurityContext\u4E2D\u67E5\u627E\u5230\u8BA4\u8BC1\u5BF9\u8C61 OAuth2ResourceOwnerBaseAuthenticationProvider.tokenExpired=\u8BF7\u6C42\u4EE4\u724C\u5DF2\u8FC7\u671F AbstractUserDetailsAuthenticationProvider.smsBadCredentials=\u624B\u673A\u53F7\u4E0D\u5B58\u5728\u767B\u5F55\u5931\u8D25 AbstractUserDetailsAuthenticationProvider.badCredentials=\u7528\u6237\u540D\u6216\u5BC6\u7801\u9519\u8BEF AbstractUserDetailsAuthenticationProvider.credentialsExpired=\u7528\u6237\u51ED\u8BC1\u5DF2\u8FC7\u671F AbstractUserDetailsAuthenticationProvider.disabled=\u7528\u6237\u5DF2\u5931\u6548 AbstractUserDetailsAuthenticationProvider.expired=\u7528\u6237\u5E10\u53F7\u5DF2\u8FC7\u671F AbstractUserDetailsAuthenticationProvider.locked=\u7528\u6237\u5E10\u53F7\u5DF2\u88AB\u9501\u5B9A AbstractUserDetailsAuthenticationProvider.onlySupports=\u4EC5\u4EC5\u652F\u6301UsernamePasswordAuthenticationToken AccountStatusUserDetailsChecker.credentialsExpired=\u7528\u6237\u51ED\u8BC1\u5DF2\u8FC7\u671F AccountStatusUserDetailsChecker.disabled=\u7528\u6237\u5DF2\u5931\u6548 AccountStatusUserDetailsChecker.expired=\u7528\u6237\u5E10\u53F7\u5DF2\u8FC7\u671F AccountStatusUserDetailsChecker.locked=\u7528\u6237\u5E10\u53F7\u5DF2\u88AB\u9501\u5B9A AclEntryAfterInvocationProvider.noPermission=\u7ED9\u5B9A\u7684Authentication\u5BF9\u8C61({0})\u6839\u672C\u65E0\u6743\u64CD\u63A7\u9886\u57DF\u5BF9\u8C61({1}) AnonymousAuthenticationProvider.incorrectKey=\u5C55\u793A\u7684AnonymousAuthenticationToken\u4E0D\u542B\u6709\u9884\u671F\u7684key BindAuthenticator.badCredentials=\u7528\u6237\u540D\u6216\u5BC6\u7801\u9519\u8BEF BindAuthenticator.emptyPassword=\u7528\u6237\u540D\u6216\u5BC6\u7801\u9519\u8BEF CasAuthenticationProvider.incorrectKey=\u5C55\u793A\u7684CasAuthenticationToken\u4E0D\u542B\u6709\u9884\u671F\u7684key CasAuthenticationProvider.noServiceTicket=\u672A\u80FD\u591F\u6B63\u786E\u63D0\u4F9B\u5F85\u9A8C\u8BC1\u7684CAS\u670D\u52A1\u7968\u6839 ConcurrentSessionControlAuthenticationStrategy.exceededAllowed=\u5DF2\u7ECF\u8D85\u8FC7\u4E86\u5F53\u524D\u4E3B\u4F53({0})\u88AB\u5141\u8BB8\u7684\u6700\u5927\u4F1A\u8BDD\u6570\u91CF DigestAuthenticationFilter.incorrectRealm=\u54CD\u5E94\u7ED3\u679C\u4E2D\u7684Realm\u540D\u5B57({0})\u540C\u7CFB\u7EDF\u6307\u5B9A\u7684Realm\u540D\u5B57({1})\u4E0D\u543B\u5408 DigestAuthenticationFilter.incorrectResponse=\u9519\u8BEF\u7684\u54CD\u5E94\u7ED3\u679C DigestAuthenticationFilter.missingAuth=\u9057\u6F0F\u4E86\u9488\u5BF9'auth' QOP\u7684\u3001\u5FC5\u987B\u7ED9\u5B9A\u7684\u6458\u8981\u53D6\u503C; \u63A5\u6536\u5230\u7684\u5934\u4FE1\u606F\u4E3A{0} DigestAuthenticationFilter.missingMandatory=\u9057\u6F0F\u4E86\u5FC5\u987B\u7ED9\u5B9A\u7684\u6458\u8981\u53D6\u503C; \u63A5\u6536\u5230\u7684\u5934\u4FE1\u606F\u4E3A{0} DigestAuthenticationFilter.nonceCompromised=Nonce\u4EE4\u724C\u5DF2\u7ECF\u5B58\u5728\u95EE\u9898\u4E86\uFF0C{0} DigestAuthenticationFilter.nonceEncoding=Nonce\u672A\u7ECF\u8FC7Base64\u7F16\u7801; \u76F8\u5E94\u7684nonce\u53D6\u503C\u4E3A {0} DigestAuthenticationFilter.nonceExpired=Nonce\u5DF2\u7ECF\u8FC7\u671F/\u8D85\u65F6 DigestAuthenticationFilter.nonceNotNumeric=Nonce\u4EE4\u724C\u7684\u7B2C1\u90E8\u5206\u5E94\u8BE5\u662F\u6570\u5B57\uFF0C\u4F46\u7ED3\u679C\u5374\u662F{0} DigestAuthenticationFilter.nonceNotTwoTokens=Nonce\u5E94\u8BE5\u7531\u4E24\u90E8\u5206\u53D6\u503C\u6784\u6210\uFF0C\u4F46\u7ED3\u679C\u5374\u662F{0} DigestAuthenticationFilter.usernameNotFound=\u7528\u6237\u540D{0}\u672A\u627E\u5230 ExceptionTranslationFilter.insufficientAuthentication=\u8BBF\u95EE\u6B64\u8D44\u6E90\u9700\u8981\u5B8C\u5168\u8EAB\u4EFD\u9A8C\u8BC1 JdbcDaoImpl.noAuthority=\u6CA1\u6709\u4E3A\u7528\u6237{0}\u6307\u5B9A\u89D2\u8272 JdbcDaoImpl.notFound=\u672A\u627E\u5230\u7528\u6237{0} LdapAuthenticationProvider.badCredentials=\u7528\u6237\u540D\u6216\u5BC6\u7801\u9519\u8BEF LdapAuthenticationProvider.credentialsExpired=\u7528\u6237\u51ED\u8BC1\u5DF2\u8FC7\u671F LdapAuthenticationProvider.disabled=\u7528\u6237\u5DF2\u5931\u6548 LdapAuthenticationProvider.expired=\u7528\u6237\u5E10\u53F7\u5DF2\u8FC7\u671F LdapAuthenticationProvider.locked=\u7528\u6237\u5E10\u53F7\u5DF2\u88AB\u9501\u5B9A LdapAuthenticationProvider.emptyUsername=\u7528\u6237\u540D\u4E0D\u5141\u8BB8\u4E3A\u7A7A LdapAuthenticationProvider.onlySupports=\u4EC5\u4EC5\u652F\u6301UsernamePasswordAuthenticationToken PasswordComparisonAuthenticator.badCredentials=\u7528\u6237\u540D\u6216\u5BC6\u7801\u9519\u8BEF #PersistentTokenBasedRememberMeServices.cookieStolen=Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack. ProviderManager.providerNotFound=\u672A\u67E5\u627E\u5230\u9488\u5BF9{0}\u7684AuthenticationProvider RememberMeAuthenticationProvider.incorrectKey=\u5C55\u793ARememberMeAuthenticationToken\u4E0D\u542B\u6709\u9884\u671F\u7684key RunAsImplAuthenticationProvider.incorrectKey=\u5C55\u793A\u7684RunAsUserToken\u4E0D\u542B\u6709\u9884\u671F\u7684key SubjectDnX509PrincipalExtractor.noMatching=\u672A\u5728subjectDN\: {0}\u4E2D\u627E\u5230\u5339\u914D\u7684\u6A21\u5F0F SwitchUserFilter.noCurrentUser=\u4E0D\u5B58\u5728\u5F53\u524D\u7528\u6237 SwitchUserFilter.noOriginalAuthentication=\u4E0D\u80FD\u591F\u67E5\u627E\u5230\u539F\u5148\u7684\u5DF2\u8BA4\u8BC1\u5BF9\u8C61 ================================================ FILE: pig-common/pig-common-swagger/pom.xml ================================================ 4.0.0 com.pig4cloud pig-common ${revision} pig-common-swagger jar pig 接口文档 org.springdoc springdoc-openapi-starter-webmvc-api org.springframework spring-webflux provided org.springframework.cloud spring-cloud-gateway-server provided org.springframework.cloud spring-cloud-commons provided org.springframework spring-webmvc provided com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery provided com.pig4cloud pig-common-core ================================================ FILE: pig-common/pig-common-swagger/src/main/java/com/pig4cloud/pig/common/swagger/annotation/EnablePigDoc.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.swagger.annotation; import com.pig4cloud.pig.common.core.factory.YamlPropertySourceFactory; import com.pig4cloud.pig.common.swagger.config.OpenAPIDefinitionImportSelector; import com.pig4cloud.pig.common.swagger.support.SwaggerProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.PropertySource; import java.lang.annotation.*; /** * 启用Pig框架的Spring文档支持 * * @author lengleng * @date 2025/05/31 */ @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @EnableConfigurationProperties(SwaggerProperties.class) @Import(OpenAPIDefinitionImportSelector.class) @PropertySource(value = "classpath:openapi-config.yaml", factory = YamlPropertySourceFactory.class) public @interface EnablePigDoc { /** * 网关路由前缀 * @return String */ String value(); /** * 是否是微服务架构 * @return true */ boolean isMicro() default true; } ================================================ FILE: pig-common/pig-common-swagger/src/main/java/com/pig4cloud/pig/common/swagger/config/OpenAPIDefinition.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.common.swagger.config; import com.pig4cloud.pig.common.swagger.support.SwaggerProperties; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.security.OAuthFlow; import io.swagger.v3.oas.models.security.OAuthFlows; import io.swagger.v3.oas.models.security.Scopes; import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.servers.Server; import lombok.RequiredArgsConstructor; import lombok.Setter; import org.springdoc.core.utils.SpringDocUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.http.HttpHeaders; import java.util.ArrayList; import java.util.List; /** * Swagger配置类,用于配置OpenAPI定义 *

* 支持通过配置控制Swagger的启用状态,并提供OAuth2安全认证配置 *

* * @author lengleng * @date 2025/05/31 */ @RequiredArgsConstructor @ConditionalOnProperty(name = "swagger.enabled", matchIfMissing = true) public class OpenAPIDefinition extends OpenAPI implements InitializingBean, ApplicationContextAware { @Setter private String path; /** * 应用上下文 */ private ApplicationContext applicationContext; /** * 创建并配置OAuth2安全方案 * @param swaggerProperties Swagger配置属性 * @return 配置好的SecurityScheme对象 */ private SecurityScheme securityScheme(SwaggerProperties swaggerProperties) { OAuthFlow clientCredential = new OAuthFlow(); clientCredential.setTokenUrl(swaggerProperties.getTokenUrl()); clientCredential.setScopes(new Scopes().addString(swaggerProperties.getScope(), swaggerProperties.getScope())); OAuthFlows oauthFlows = new OAuthFlows(); oauthFlows.password(clientCredential); SecurityScheme securityScheme = new SecurityScheme(); securityScheme.setType(SecurityScheme.Type.OAUTH2); securityScheme.setFlows(oauthFlows); return securityScheme; } /** * 初始化Swagger配置 * @throws Exception 初始化过程中可能抛出的异常 */ @Override public void afterPropertiesSet() throws Exception { SwaggerProperties swaggerProperties = applicationContext.getBean(SwaggerProperties.class); this.info(new Info().title(swaggerProperties.getTitle())); // oauth2.0 password this.schemaRequirement(HttpHeaders.AUTHORIZATION, this.securityScheme(swaggerProperties)); // servers List serverList = new ArrayList<>(); serverList.add(new Server().url(swaggerProperties.getGateway() + "/" + path)); this.servers(serverList); // 支持参数平铺 SpringDocUtils.getConfig().addSimpleTypesForParameterObject(Class.class); } /** * 设置应用上下文 * @param applicationContext 应用上下文对象 * @throws BeansException 如果设置上下文时发生错误 */ @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } } ================================================ FILE: pig-common/pig-common-swagger/src/main/java/com/pig4cloud/pig/common/swagger/config/OpenAPIDefinitionImportSelector.java ================================================ package com.pig4cloud.pig.common.swagger.config; import com.pig4cloud.pig.common.swagger.annotation.EnablePigDoc; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.type.AnnotationMetadata; import java.util.Map; import java.util.Objects; /** * OpenAPI 配置类,用于动态注册 OpenAPI 相关 Bean 定义 * * @author lengleng * @date 2025/05/31 */ public class OpenAPIDefinitionImportSelector implements ImportBeanDefinitionRegistrar { /** * 注册Bean定义,根据注解元数据配置OpenAPI相关Bean * @param metadata 注解元数据 * @param registry Bean定义注册器 */ @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { Map annotationAttributes = metadata.getAnnotationAttributes(EnablePigDoc.class.getName(), true); Object value = annotationAttributes.get("value"); if (Objects.isNull(value)) { return; } BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(OpenAPIDefinition.class); definition.addPropertyValue("path", value); definition.setPrimary(true); registry.registerBeanDefinition("openAPIDefinition", definition.getBeanDefinition()); // 如果是微服务架构则,引入了服务发现声明相关的元数据配置 Object isMicro = annotationAttributes.getOrDefault("isMicro", true); if (isMicro.equals(false)) { return; } BeanDefinitionBuilder openAPIMetadata = BeanDefinitionBuilder .genericBeanDefinition(OpenAPIMetadataConfiguration.class); openAPIMetadata.addPropertyValue("path", value); registry.registerBeanDefinition("openAPIMetadata", openAPIMetadata.getBeanDefinition()); } } ================================================ FILE: pig-common/pig-common-swagger/src/main/java/com/pig4cloud/pig/common/swagger/config/OpenAPIMetadataConfiguration.java ================================================ package com.pig4cloud.pig.common.swagger.config; import lombok.Setter; import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; import org.springframework.cloud.client.ServiceInstance; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; /** * OpenAPI 元数据配置类,用于配置并注册OpenAPI相关元数据 * * @author lengleng * @date 2025/05/31 */ public class OpenAPIMetadataConfiguration implements InitializingBean, ApplicationContextAware { /** * 应用上下文 */ private ApplicationContext applicationContext; @Setter private String path; /** * 在属性设置完成后执行,将spring-doc路径信息注册到ServiceInstance的元数据中 * @throws Exception 如果执行过程中发生错误 */ @Override public void afterPropertiesSet() throws Exception { String[] beanNamesForType = applicationContext.getBeanNamesForType(ServiceInstance.class); if (beanNamesForType.length == 0) { return; } ServiceInstance serviceInstance = applicationContext.getBean(ServiceInstance.class); serviceInstance.getMetadata().put("spring-doc", path); } /** * 设置应用上下文 * @param applicationContext 应用上下文对象 * @throws BeansException 如果设置上下文时发生错误 */ @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } } ================================================ FILE: pig-common/pig-common-swagger/src/main/java/com/pig4cloud/pig/common/swagger/support/SwaggerProperties.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.common.swagger.support; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * Swagger配置属性类 * * @author lengleng * @date 2025/05/31 */ @Data @ConfigurationProperties("swagger") public class SwaggerProperties { /** * 是否开启swagger */ private Boolean enabled = true; /** * swagger会解析的包路径 **/ private String basePackage = ""; /** * swagger会解析的url规则 **/ private List basePath = new ArrayList<>(); /** * 在basePath基础上需要排除的url规则 **/ private List excludePath = new ArrayList<>(); /** * 需要排除的服务 */ private List ignoreProviders = new ArrayList<>(); /** * 标题 **/ private String title = ""; /** * 网关 */ private String gateway; /** * 获取token */ private String tokenUrl; /** * 作用域 */ private String scope; /** * 服务转发配置 */ private Map services; } ================================================ FILE: pig-common/pig-common-swagger/src/main/resources/openapi-config.yaml ================================================ # swagger 配置 swagger: enabled: true title: Pig Swagger API gateway: http://${GATEWAY-HOST:127.0.0.1}:${GATEWAY-PORT:9999} token-url: ${swagger.gateway}/auth/oauth2/token scope: server ================================================ FILE: pig-common/pig-common-websocket/pom.xml ================================================ 4.0.0 com.pig4cloud pig-common ${revision} pig-common-websocket jar pig websocket 链接模块 org.springframework.boot spring-boot-starter-websocket org.springframework.data spring-data-redis cn.hutool hutool-json com.pig4cloud pig-common-security ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/config/LocalMessageDistributorConfiguration.java ================================================ package com.pig4cloud.pig.common.websocket.config; import com.pig4cloud.pig.common.websocket.distribute.LocalMessageDistributor; import com.pig4cloud.pig.common.websocket.distribute.MessageDistributor; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * 本地的消息分发器配置 * * @author hccake */ @ConditionalOnProperty(prefix = WebSocketProperties.PREFIX, name = "message-distributor", havingValue = MessageDistributorTypeConstants.LOCAL) @Configuration(proxyBeanMethods = false) public class LocalMessageDistributorConfiguration { /** * 配置本地消息分发器,使用本地内存实现,不支持集群环境。 * @return 返回一个 {@link LocalMessageDistributor} 实例,用于处理本地消息分发。 */ @Bean @ConditionalOnMissingBean(MessageDistributor.class) public LocalMessageDistributor messageDistributor() { return new LocalMessageDistributor(); } } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/config/MessageDistributorTypeConstants.java ================================================ package com.pig4cloud.pig.common.websocket.config; /** * 消息分发器类型常量 *

* 定义了不同消息分发策略的常量标识符。 *

* * @author hccake */ public final class MessageDistributorTypeConstants { private MessageDistributorTypeConstants() { } /** * 本地消息分发 *

* 适用于单机环境,消息在当前服务实例内部分发。 *

*/ public static final String LOCAL = "local"; /** * 基于 Redis PUB/SUB 的消息分发 *

* 适用于集群环境,通过 Redis 的发布/订阅机制实现跨服务实例的消息分发。 *

*/ public static final String REDIS = "redis"; /** * 自定义消息分发 *

* 用户可以实现自定义的消息分发逻辑。 *

*/ public static final String CUSTOM = "custom"; } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/config/RedisMessageDistributorConfiguration.java ================================================ package com.pig4cloud.pig.common.websocket.config; import com.pig4cloud.pig.common.websocket.distribute.MessageDistributor; import com.pig4cloud.pig.common.websocket.distribute.RedisMessageDistributor; import com.pig4cloud.pig.common.websocket.distribute.RedisWebsocketMessageListener; import lombok.RequiredArgsConstructor; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.listener.PatternTopic; import org.springframework.data.redis.listener.RedisMessageListenerContainer; import jakarta.annotation.PostConstruct; /** * 基于 Redis Pub/Sub 的消息分发器配置 *

* 当 WebSocket 配置为使用 Redis 进行消息分发时,此配置类生效。 它提供了基于 Redis 的 {@link MessageDistributor} * 实现,适用于集群环境。 *

* * @author hccake */ @ConditionalOnClass(StringRedisTemplate.class) @ConditionalOnProperty(prefix = WebSocketProperties.PREFIX, name = "message-distributor", havingValue = MessageDistributorTypeConstants.REDIS, matchIfMissing = true) @Configuration(proxyBeanMethods = false) public class RedisMessageDistributorConfiguration { /** * 创建一个基于 Redis 的消息分发器。 * @param stringRedisTemplate Spring Data Redis 提供的 Redis 操作模板。 * @return 返回一个 {@link RedisMessageDistributor} 实例。 */ @Bean @ConditionalOnMissingBean(MessageDistributor.class) public RedisMessageDistributor messageDistributor(StringRedisTemplate stringRedisTemplate) { return new RedisMessageDistributor(stringRedisTemplate); } /** * 创建 Redis WebSocket 消息监听器。 * @param stringRedisTemplate Spring Data Redis 提供的 Redis 操作模板。 * @return 返回一个 {@link RedisWebsocketMessageListener} 实例。 */ @Bean @ConditionalOnBean(RedisMessageDistributor.class) @ConditionalOnMissingBean public RedisWebsocketMessageListener redisWebsocketMessageListener(StringRedisTemplate stringRedisTemplate) { return new RedisWebsocketMessageListener(stringRedisTemplate); } /** * 创建 Redis 消息监听器容器,用于管理消息监听器。 * @param connectionFactory Redis 连接工厂。 * @return 返回一个 {@link RedisMessageListenerContainer} 实例。 */ @Bean @ConditionalOnBean(RedisMessageDistributor.class) @ConditionalOnMissingBean public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(connectionFactory); return container; } /** * Redis 消息监听器注册配置 *

* 在 Spring 初始化后,将 {@link RedisWebsocketMessageListener} 注册到 Redis 消息监听器容器中。 *

*/ @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(MessageDistributor.class) @RequiredArgsConstructor static class RedisMessageListenerRegisterConfiguration { private final RedisMessageListenerContainer redisMessageListenerContainer; private final RedisWebsocketMessageListener redisWebsocketMessageListener; /** * 将 WebSocket 消息监听器添加到 Redis 监听容器中,监听指定频道的消息。 */ @PostConstruct public void addMessageListener() { redisMessageListenerContainer.addMessageListener(redisWebsocketMessageListener, new PatternTopic(RedisWebsocketMessageListener.CHANNEL)); } } } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/config/WebSocketAutoConfiguration.java ================================================ package com.pig4cloud.pig.common.websocket.config; import com.pig4cloud.pig.common.websocket.handler.JsonMessageHandler; import com.pig4cloud.pig.common.websocket.holder.JsonMessageHandlerHolder; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.server.HandshakeInterceptor; import java.util.List; /** * WebSocket 自动配置类 *

* 负责初始化和配置 WebSocket 功能,包括处理器、拦截器和消息分发机制。 *

* * @author Yakir */ @Import(WebSocketHandlerConfig.class) @EnableWebSocket @RequiredArgsConstructor public class WebSocketAutoConfiguration { private final WebSocketProperties webSocketProperties; private final List jsonMessageHandlerList; /** * 配置 WebSocket 连接处理器。 * @param handshakeInterceptor 握手拦截器列表,用于在 WebSocket 握手阶段执行自定义逻辑。 * @param webSocketHandler WebSocket 处理器,负责处理 WebSocket 连接和消息。 * @return 返回一个 {@link WebSocketConfigurer} 实例,用于注册 WebSocket 处理器和拦截器。 */ @Bean @ConditionalOnMissingBean public WebSocketConfigurer webSocketConfigurer(List handshakeInterceptor, WebSocketHandler webSocketHandler) { return registry -> registry.addHandler(webSocketHandler, webSocketProperties.getPath()) .setAllowedOrigins(webSocketProperties.getAllowOrigins()) .addInterceptors(handshakeInterceptor.toArray(new HandshakeInterceptor[0])); } /** * 初始化 JSON 消息处理器持有者。 *

* 在应用启动时,将所有实现了 {@link JsonMessageHandler} 接口的处理器注册到 {@link JsonMessageHandlerHolder} * 中, 以便后续根据消息类型快速查找和调用。 *

*/ @PostConstruct public void initJsonMessageHandlerHolder() { for (JsonMessageHandler jsonMessageHandler : jsonMessageHandlerList) { JsonMessageHandlerHolder.addHandler(jsonMessageHandler); } } } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/config/WebSocketHandlerConfig.java ================================================ package com.pig4cloud.pig.common.websocket.config; import com.pig4cloud.pig.common.websocket.custom.PigxSessionKeyGenerator; import com.pig4cloud.pig.common.websocket.custom.UserAttributeHandshakeInterceptor; import com.pig4cloud.pig.common.websocket.handler.CustomPlanTextMessageHandler; import com.pig4cloud.pig.common.websocket.handler.CustomWebSocketHandler; import com.pig4cloud.pig.common.websocket.handler.PingJsonMessageHandler; import com.pig4cloud.pig.common.websocket.handler.PlanTextMessageHandler; import com.pig4cloud.pig.common.websocket.holder.MapSessionWebSocketHandlerDecorator; import com.pig4cloud.pig.common.websocket.holder.SessionKeyGenerator; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.handler.TextWebSocketHandler; import org.springframework.web.socket.server.HandshakeInterceptor; /** * WebSocket 处理器配置类 *

* 负责配置和创建 WebSocket 相关的处理器、拦截器和会话管理组件。 *

* * @author Hccake 2021/1/5 * @version 1.0 */ @RequiredArgsConstructor @EnableConfigurationProperties(WebSocketProperties.class) public class WebSocketHandlerConfig { private final WebSocketProperties webSocketProperties; /** * 创建会话密钥生成器,用于生成 WebSocket 会话的唯一标识。 * @return 返回一个 {@link SessionKeyGenerator} 实例。 */ @Bean @ConditionalOnMissingBean(SessionKeyGenerator.class) public SessionKeyGenerator sessionKeyGenerator() { return new PigxSessionKeyGenerator(); } /** * 创建握手拦截器,用于在 WebSocket 握手阶段添加用户属性。 * @return 返回一个 {@link HandshakeInterceptor} 实例。 */ @Bean public HandshakeInterceptor handshakeInterceptor() { return new UserAttributeHandshakeInterceptor(); } /** * 创建默认的纯文本消息处理器。 * @return 返回一个 {@link PlanTextMessageHandler} 实例。 */ @Bean @ConditionalOnMissingBean(PlanTextMessageHandler.class) public PlanTextMessageHandler planTextMessageHandler() { return new CustomPlanTextMessageHandler(); } /** * 创建 WebSocket 处理器,当没有纯文本消息处理器时使用。 * @param sessionKeyGenerator 会话密钥生成器。 * @return 返回一个 {@link WebSocketHandler} 实例。 */ @Bean @ConditionalOnMissingBean({ TextWebSocketHandler.class, PlanTextMessageHandler.class }) public WebSocketHandler webSocketHandler1(@Autowired(required = false) SessionKeyGenerator sessionKeyGenerator) { CustomWebSocketHandler customWebSocketHandler = new CustomWebSocketHandler(); if (webSocketProperties.isMapSession()) { return new MapSessionWebSocketHandlerDecorator(customWebSocketHandler, sessionKeyGenerator, webSocketProperties); } return customWebSocketHandler; } /** * 创建 WebSocket 处理器,当存在纯文本消息处理器时使用。 * @param sessionKeyGenerator 会话密钥生成器。 * @param planTextMessageHandler 纯文本消息处理器。 * @return 返回一个 {@link WebSocketHandler} 实例。 */ @Bean @ConditionalOnBean(PlanTextMessageHandler.class) @ConditionalOnMissingBean(TextWebSocketHandler.class) public WebSocketHandler webSocketHandler2(@Autowired(required = false) SessionKeyGenerator sessionKeyGenerator, PlanTextMessageHandler planTextMessageHandler) { CustomWebSocketHandler customWebSocketHandler = new CustomWebSocketHandler(planTextMessageHandler); if (webSocketProperties.isMapSession()) { return new MapSessionWebSocketHandlerDecorator(customWebSocketHandler, sessionKeyGenerator, webSocketProperties); } return customWebSocketHandler; } /** * 创建 Ping 消息处理器,用于处理心跳检测。 * @return 返回一个 {@link PingJsonMessageHandler} 实例。 */ @Bean @ConditionalOnProperty(prefix = WebSocketProperties.PREFIX, name = "heartbeat", havingValue = "true", matchIfMissing = true) public PingJsonMessageHandler pingJsonMessageHandler() { return new PingJsonMessageHandler(); } } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/config/WebSocketMessageSender.java ================================================ package com.pig4cloud.pig.common.websocket.config; import cn.hutool.json.JSONUtil; import com.pig4cloud.pig.common.websocket.holder.WebSocketSessionHolder; import com.pig4cloud.pig.common.websocket.message.JsonWebSocketMessage; import lombok.extern.slf4j.Slf4j; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import java.io.IOException; import java.util.Collection; /** * WebSocket 消息发送器 *

* 提供向 WebSocket 客户端发送消息的静态方法,支持广播和单点发送。 *

* * @author Hccake 2021/1/4 * @version 1.0 */ @Slf4j public class WebSocketMessageSender { /** * 向所有在线的 WebSocket 会话广播消息。 * @param message 要发送的消息文本。 */ public static void broadcast(String message) { Collection sessions = WebSocketSessionHolder.getSessions(); for (WebSocketSession session : sessions) { send(session, message); } } /** * 向指定会话标识的客户端发送消息。 * @param sessionKey 会话的唯一标识。 * @param message 要发送的消息文本。 * @return 如果找到会话并成功发送,返回 {@code true};否则返回 {@code false}。 */ public static boolean send(Object sessionKey, String message) { WebSocketSession session = WebSocketSessionHolder.getSession(sessionKey); if (session == null) { log.info("[send] 当前 sessionKey:{} 对应 session 不在本服务中", sessionKey); return false; } else { return send(session, message); } } /** * 向指定会话发送 JSON 格式的 WebSocket 消息。 * @param session WebSocket 会话。 * @param message 要发送的 JSON 消息对象。 */ public static void send(WebSocketSession session, JsonWebSocketMessage message) { send(session, JSONUtil.toJsonStr(message)); } /** * 向指定会话发送文本消息。 * @param session WebSocket 会话。 * @param message 要发送的消息文本。 * @return 如果发送成功,返回 {@code true};否则返回 {@code false}。 */ public static boolean send(WebSocketSession session, String message) { if (session == null) { log.error("[send] session 为 null"); return false; } if (!session.isOpen()) { log.error("[send] session 已经关闭"); return false; } try { session.sendMessage(new TextMessage(message)); } catch (IOException e) { log.error("[send] session({}) 发送消息({}) 异常", session, message, e); return false; } return true; } } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/config/WebSocketProperties.java ================================================ package com.pig4cloud.pig.common.websocket.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; /** * WebSocket 属性配置类 *

* 用于配置 WebSocket 的各种行为,如路径、心跳、消息分发等。 *

* * @author Yakir */ @Data @ConfigurationProperties(WebSocketProperties.PREFIX) public class WebSocketProperties { /** * WebSocket 配置属性的前缀。 */ public static final String PREFIX = "pigx.websocket"; /** * WebSocket 连接路径。 *

* 支持路径参数,例如:/ws/{param} 或 /ws/{param1}/{param2}。 同时支持查询参数,例如:/ws?uid=1&name=test。 *

*/ private String path = "/ws/info"; /** * 允许的跨域来源,默认为 "*",表示允许所有来源。 */ private String allowOrigins = "*"; /** * 是否支持部分消息传输,默认为 {@code false}。 */ private boolean supportPartialMessages = false; /** * 是否启用心跳处理,默认为 {@code true}。 */ private boolean heartbeat = true; /** * 是否开启会话映射记录,默认为 {@code true}。 *

* 开启后,会话将与唯一标识符关联,方便进行单点消息发送。 *

*/ private boolean mapSession = true; /** * 消息分发器类型,默认为 "local"。 *

* 可选值为 "local"(本地内存)或 "redis"(Redis Pub/Sub)。 也可配置为其他值以使用自定义分发器。 *

*/ private String messageDistributor = MessageDistributorTypeConstants.LOCAL; /** * 消息发送时间限制(毫秒),默认为 10000。 */ private Integer sendTimeLimit = 10000; /** * 消息发送缓冲区大小限制(字节),默认为 64000。 */ private Integer sendBufferSizeLimit = 64000; } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/custom/PigxSessionKeyGenerator.java ================================================ package com.pig4cloud.pig.common.websocket.custom; import com.pig4cloud.pig.common.security.service.PigUser; import com.pig4cloud.pig.common.websocket.holder.SessionKeyGenerator; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.WebSocketSession; /** * WebSocket Session 标识生成器 *

* 默认的 WebSocket 会话密钥生成器实现,用于根据用户信息生成唯一的会话标识。 *

* * @author lengleng * @date 2021/10/4 */ @Configuration @RequiredArgsConstructor public class PigxSessionKeyGenerator implements SessionKeyGenerator { /** * 根据 WebSocket 会话中的用户信息生成会话的唯一标识。 *

* 此实现从会话属性中获取 {@link PigUser} 对象,并使用其 ID 作为唯一标识。 *

* @param webSocketSession 当前的 WebSocket 会话。 * @return 返回会话的唯一标识,如果无法确定用户,则返回 {@code null}。 */ @Override public Object sessionKey(WebSocketSession webSocketSession) { Object obj = webSocketSession.getAttributes().get("USER_KEY_ATTR_NAME"); if (obj instanceof PigUser user) { // userId 作为唯一区分 return String.valueOf(user.getId()); } return null; } } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/custom/UserAttributeHandshakeInterceptor.java ================================================ package com.pig4cloud.pig.common.websocket.custom; import com.pig4cloud.pig.common.security.service.PigUser; import com.pig4cloud.pig.common.security.util.SecurityUtils; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.server.HandshakeInterceptor; import java.util.Map; /** * 用户属性握手拦截器 *

* 在 WebSocket 握手阶段,将当前通过 Spring Security 认证的用户信息存入 WebSocket 会话属性中, 以便后续在 WebSocket * 处理流程中访问用户信息。 *

* * @author lengleng * @date 2021/10/4 */ public class UserAttributeHandshakeInterceptor implements HandshakeInterceptor { /** * 在 WebSocket 握手前调用,用于将用户信息添加到会话属性中。 *

* 由于 WebSocket 握手是基于 HTTP 的,此时可以通过 {@link SecurityUtils} 获取已认证的用户信息。 *

* @param request 当前的服务器请求。 * @param response 当前的服务器响应。 * @param wsHandler 目标 WebSocket 处理器。 * @param attributes 用于存储 WebSocket 会话属性的映射。 * @return 始终返回 {@code true},允许握手继续。 * @throws Exception 如果在处理过程中发生错误。 */ @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception { // 由于 WebSocket 握手是由 http 升级的,携带 token 已经被 Security 拦截验证了,所以可以直接获取到用户 PigUser user = SecurityUtils.getUser(); attributes.put("USER_KEY_ATTR_NAME", user); return true; } /** * 在 WebSocket 握手完成后调用。 *

* 此方法在此实现中为空,没有执行任何操作。 *

* @param request 当前的服务器请求。 * @param response 当前的服务器响应。 * @param wsHandler 目标 WebSocket 处理器。 * @param exception 握手过程中抛出的异常,如果没有异常则为 {@code null}。 */ @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { } } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/distribute/LocalMessageDistributor.java ================================================ package com.pig4cloud.pig.common.websocket.distribute; /** * 本地消息分发器 *

* 在单机环境下,直接将消息发送给本地的 WebSocket 会话,不涉及跨服务通信。 *

* * @author Hccake 2021/1/12 * @version 1.0 */ public class LocalMessageDistributor implements MessageDistributor, MessageSender { /** * 分发消息,对于本地分发器,直接调用发送逻辑。 * @param messageDO 待发送的消息对象,包含消息内容和目标会话信息。 */ @Override public void distribute(MessageDO messageDO) { doSend(messageDO); } } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/distribute/MessageDO.java ================================================ package com.pig4cloud.pig.common.websocket.distribute; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; import java.util.List; /** * 消息数据对象(Data Object) *

* 用于在消息分发过程中传递消息内容和元数据,如是否广播、目标会话等。 *

* * @author Hccake 2021/1/12 * @version 1.0 */ @Data @Builder @AllArgsConstructor @NoArgsConstructor @Accessors(chain = true) public class MessageDO { /** * 是否需要广播消息。 *

* 如果为 {@code true},则消息将发送给所有在线的 WebSocket 会话。 *

*/ private Boolean needBroadcast; /** * 目标会话的唯一标识列表。 *

* 当 {@code needBroadcast} 为 {@code false} 或 {@code null} 时,消息将发送给此列表中的所有会话。 *

*/ private List sessionKeys; /** * 需要发送的消息文本内容。 */ private String messageText; /** * 构建一个需要广播的消息对象。 * @param text 要广播的消息文本。 * @return 返回一个配置为广播模式的 {@link MessageDO} 实例。 * @author lingting 2021-03-25 17:28 */ public static MessageDO broadcastMessage(String text) { return new MessageDO().setMessageText(text).setNeedBroadcast(true); } } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/distribute/MessageDistributor.java ================================================ package com.pig4cloud.pig.common.websocket.distribute; /** * 消息分发器接口 *

* 定义了消息分发的契约,负责将 WebSocket 消息路由到适当的目标, 支持本地内存分发和基于消息中间件(如 Redis)的分布式分发。 *

* * @author Hccake 2021/1/12 * @version 1.0 */ public interface MessageDistributor { /** * 分发消息。 *

* 根据实现类的不同,此方法可以将消息发送到本地会话或发布到消息队列中, 以便在集群环境中进行广播或单点发送。 *

* @param messageDO 待发送的消息对象,包含消息内容和目标会话信息。 */ void distribute(MessageDO messageDO); } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/distribute/MessageSender.java ================================================ package com.pig4cloud.pig.common.websocket.distribute; import cn.hutool.core.collection.CollectionUtil; import com.pig4cloud.pig.common.websocket.config.WebSocketMessageSender; import java.util.List; /** * 消息发送者接口 *

* 提供了一个默认方法 {@code doSend},用于实际执行 WebSocket 消息的发送操作。 此接口可由不同的消息分发策略实现,以统一发送逻辑。 *

* * @author Hccake 2021/1/12 * @version 1.0 */ public interface MessageSender { /** * 执行消息发送。 *

* 此方法会检查消息是否为广播,如果是,则向所有会话发送; 否则,向指定列表的会话发送。 *

* @param messageDO 待发送的消息对象,包含消息内容和目标会话信息。 */ default void doSend(MessageDO messageDO) { Boolean needBroadcast = messageDO.getNeedBroadcast(); String messageText = messageDO.getMessageText(); List sessionKeys = messageDO.getSessionKeys(); if (needBroadcast != null && needBroadcast) { // 广播信息 WebSocketMessageSender.broadcast(messageText); } else if (CollectionUtil.isNotEmpty(sessionKeys)) { // 指定用户发送 for (Object sessionKey : sessionKeys) { WebSocketMessageSender.send(sessionKey, messageText); } } } } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/distribute/RedisMessageDistributor.java ================================================ package com.pig4cloud.pig.common.websocket.distribute; import cn.hutool.json.JSONUtil; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.StringRedisTemplate; import java.util.ArrayList; import java.util.List; /** * 基于 Redis 的消息分发器 *

* 在集群环境下,通过 Redis 的发布/订阅机制,将消息发布到指定频道, 由所有订阅该频道的服务实例进行消费和处理,从而实现跨服务的消息分发。 *

* * @author Hccake 2021/1/12 * @version 1.0 */ @RequiredArgsConstructor public class RedisMessageDistributor implements MessageDistributor { private final StringRedisTemplate stringRedisTemplate; /** * 将消息发布到 Redis 频道。 *

* 此方法首先将消息对象序列化为 JSON 字符串,然后通过 Redis 发布/订阅机制 将其发送到 * {@link RedisWebsocketMessageListener#CHANNEL} 频道。 *

* @param messageDO 待发送的消息对象,包含消息内容和目标会话信息。 */ @Override public void distribute(MessageDO messageDO) { // 包装 sessionKey 适配分布式多环境 List sessionKeyList = new ArrayList<>(messageDO.getSessionKeys()); messageDO.setSessionKeys(sessionKeyList); String str = JSONUtil.toJsonStr(messageDO); stringRedisTemplate.convertAndSend(RedisWebsocketMessageListener.CHANNEL, str); } } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/distribute/RedisWebsocketMessageListener.java ================================================ package com.pig4cloud.pig.common.websocket.distribute; import cn.hutool.json.JSONUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.serializer.RedisSerializer; /** * Redis WebSocket 消息监听器 *

* 监听 Redis 的特定频道,接收并处理来自其他服务实例的 WebSocket 消息, 从而实现集群环境下的消息同步和分发。 *

* * @author Hccake 2021/1/12 * @version 1.0 */ @Slf4j @RequiredArgsConstructor public class RedisWebsocketMessageListener implements MessageListener, MessageSender { /** * WebSocket 消息在 Redis 发布/订阅中的频道名称。 */ public static final String CHANNEL = "websocket-send"; private final StringRedisTemplate stringRedisTemplate; /** * 当从 Redis 频道接收到消息时调用此方法。 *

* 此方法会反序列化消息内容,并调用 {@link #doSend(MessageDO)} 方法 将消息发送给当前服务实例中的目标 WebSocket 会话。 *

* @param message 接收到的 Redis 消息。 * @param bytes 订阅的频道模式(未使用)。 */ @Override public void onMessage(Message message, byte[] bytes) { log.info("redis channel Listener message send {}", message); byte[] channelBytes = message.getChannel(); RedisSerializer stringSerializer = stringRedisTemplate.getStringSerializer(); String channel = stringSerializer.deserialize(channelBytes); // 这里没有使用通配符,所以一定是true if (CHANNEL.equals(channel)) { byte[] bodyBytes = message.getBody(); String body = stringSerializer.deserialize(bodyBytes); MessageDO messageDO = JSONUtil.toBean(body, MessageDO.class); doSend(messageDO); } } } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/handler/CustomPlanTextMessageHandler.java ================================================ package com.pig4cloud.pig.common.websocket.handler; import lombok.extern.slf4j.Slf4j; import org.springframework.web.socket.WebSocketSession; /** * 默认的纯文本消息处理器 *

* 当 WebSocket 消息不符合预定义的 JSON 格式时,此处理器将作为默认实现, 仅记录接收到的消息内容和会话 ID,不执行其他业务逻辑。 *

* * @author lengleng * @date 2021/11/4 */ @Slf4j public class CustomPlanTextMessageHandler implements PlanTextMessageHandler { /** * 处理纯文本 WebSocket 消息。 *

* 默认实现仅将接收到的消息内容和会话 ID 记录到日志中。 *

* @param session 当前接收消息的 WebSocket 会话。 * @param message 接收到的文本消息。 */ @Override public void handle(WebSocketSession session, String message) { log.info("sessionId {} ,msg {}", session.getId(), message); } } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/handler/CustomWebSocketHandler.java ================================================ package com.pig4cloud.pig.common.websocket.handler; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.json.JsonReadFeature; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.pig4cloud.pig.common.websocket.holder.JsonMessageHandlerHolder; import com.pig4cloud.pig.common.websocket.message.AbstractJsonWebSocketMessage; import com.pig4cloud.pig.common.websocket.message.JsonWebSocketMessage; import lombok.extern.slf4j.Slf4j; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; /** * 自定义 WebSocket 处理器 *

* 继承自 {@link TextWebSocketHandler},负责处理接收到的文本 WebSocket 消息。 它能够根据消息内容(JSON * 格式或纯文本)分发给不同的消息处理器。 *

* * @author Hccake 2020/12/31 * @version 1.0 */ @Slf4j public class CustomWebSocketHandler extends TextWebSocketHandler { private static final ObjectMapper MAPPER = new ObjectMapper(); static { // 有特殊需要转义字符, 不报错 MAPPER.enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature()); } private PlanTextMessageHandler planTextMessageHandler; /** * 构造函数,创建一个不带纯文本消息处理器的 {@code CustomWebSocketHandler} 实例。 */ public CustomWebSocketHandler() { } /** * 构造函数,创建一个带纯文本消息处理器的 {@code CustomWebSocketHandler} 实例。 * @param planTextMessageHandler 用于处理非 JSON 格式的纯文本消息。 */ public CustomWebSocketHandler(PlanTextMessageHandler planTextMessageHandler) { this.planTextMessageHandler = planTextMessageHandler; } /** * 处理接收到的文本 WebSocket 消息。 *

* 如果消息是 JSON 格式且包含 'type' 字段,则根据 'type' 查找并调用对应的 {@link JsonMessageHandler}。 * 如果消息是纯文本或不包含 'type' 字段,则尝试使用 {@link PlanTextMessageHandler} 处理。 *

* @param session 当前的 WebSocket 会话。 * @param message 接收到的文本消息。 * @throws JsonProcessingException 如果 JSON 消息处理失败。 */ @Override public void handleTextMessage(WebSocketSession session, TextMessage message) throws JsonProcessingException { // 空消息不处理 if (message.getPayloadLength() == 0) { return; } // 消息类型必有一属性type,先解析,获取该属性 String payload = message.getPayload(); JsonNode jsonNode = MAPPER.readTree(payload); JsonNode typeNode = jsonNode.get(AbstractJsonWebSocketMessage.TYPE_FIELD); if (typeNode == null) { if (planTextMessageHandler != null) { planTextMessageHandler.handle(session, payload); } else { log.error("[handleTextMessage] 普通文本消息({})没有对应的消息处理器", payload); } } else { String messageType = typeNode.asText(); // 获得对应的消息处理器 JsonMessageHandler jsonMessageHandler = JsonMessageHandlerHolder.getHandler(messageType); if (jsonMessageHandler == null) { log.error("[handleTextMessage] 消息类型({})不存在对应的消息处理器", messageType); return; } // 消息处理 Class messageClass = jsonMessageHandler.getMessageClass(); JsonWebSocketMessage websocketMessageJson = MAPPER.treeToValue(jsonNode, messageClass); jsonMessageHandler.handle(session, websocketMessageJson); } } } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/handler/JsonMessageHandler.java ================================================ package com.pig4cloud.pig.common.websocket.handler; import com.pig4cloud.pig.common.websocket.message.JsonWebSocketMessage; import org.springframework.web.socket.WebSocketSession; /** * JSON 消息处理器接口 *

* 定义了处理特定类型 JSON 格式 WebSocket 消息的契约。 实现此接口的类负责解析和处理对应 {@link JsonWebSocketMessage} 类型的消息。 *

* * @author Hccake 2021/1/4 * @version 1.0 */ public interface JsonMessageHandler { /** * 处理 JSON 格式的 WebSocket 消息。 * @param session 当前接收消息的 WebSocket 会话。 * @param message 接收到的 JSON 消息对象,其类型由泛型 {@code T} 指定。 */ void handle(WebSocketSession session, T message); /** * 获取当前处理器能够处理的消息类型标识。 *

* 此类型标识用于在 {@link JsonMessageHandlerHolder} 中查找对应的处理器。 *

* @return 消息类型字符串。 */ String type(); /** * 获取当前处理器对应的消息类的 Class 对象。 *

* 此 Class 对象用于 JSON 消息的反序列化。 *

* @return 消息类的 Class 对象。 */ Class getMessageClass(); } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/handler/PingJsonMessageHandler.java ================================================ package com.pig4cloud.pig.common.websocket.handler; import com.pig4cloud.pig.common.websocket.config.WebSocketMessageSender; import com.pig4cloud.pig.common.websocket.message.JsonWebSocketMessage; import com.pig4cloud.pig.common.websocket.message.PingJsonWebSocketMessage; import com.pig4cloud.pig.common.websocket.message.PongJsonWebSocketMessage; import com.pig4cloud.pig.common.websocket.message.WebSocketMessageTypeEnum; import org.springframework.web.socket.WebSocketSession; /** * Ping 消息处理器 *

* 负责处理客户端发送的 Ping 消息,并立即回复一个 Pong 消息,用于维持 WebSocket 连接的心跳。 *

* * @author Hccake 2021/1/4 * @version 1.0 */ public class PingJsonMessageHandler implements JsonMessageHandler { /** * 处理接收到的 Ping 消息。 *

* 当接收到 Ping 消息时,会构建一个 Pong 消息并通过 {@link WebSocketMessageSender} 发送回客户端。 *

* @param session 当前的 WebSocket 会话。 * @param message 接收到的 Ping 消息对象。 */ @Override public void handle(WebSocketSession session, PingJsonWebSocketMessage message) { JsonWebSocketMessage pongJsonWebSocketMessage = new PongJsonWebSocketMessage(); WebSocketMessageSender.send(session, pongJsonWebSocketMessage); } /** * 获取此处理器处理的消息类型。 * @return 返回 {@link WebSocketMessageTypeEnum#PING} 的值。 */ @Override public String type() { return WebSocketMessageTypeEnum.PING.getValue(); } /** * 获取此处理器对应的消息 Class。 * @return 返回 {@link PingJsonWebSocketMessage} 的 Class 对象。 */ @Override public Class getMessageClass() { return PingJsonWebSocketMessage.class; } } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/handler/PlanTextMessageHandler.java ================================================ package com.pig4cloud.pig.common.websocket.handler; import org.springframework.web.socket.WebSocketSession; /** * 纯文本消息处理器接口 *

* 定义了处理非 JSON 格式的纯文本 WebSocket 消息的契约。 当接收到的消息不符合预定义的 JSON 消息结构时,将由实现此接口的处理器进行处理。 *

* * @see com.pig4cloud.pig.common.websocket.message.JsonWebSocketMessage * @author Hccake 2021/1/5 * @version 1.0 */ public interface PlanTextMessageHandler { /** * 处理纯文本 WebSocket 消息。 * @param session 当前接收消息的 WebSocket 会话。 * @param message 接收到的文本消息内容。 */ void handle(WebSocketSession session, String message); } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/holder/JsonMessageHandlerHolder.java ================================================ package com.pig4cloud.pig.common.websocket.holder; import com.pig4cloud.pig.common.websocket.handler.JsonMessageHandler; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * JSON 消息处理器持有者 *

* 负责管理和存储所有注册的 {@link JsonMessageHandler} 实例。 通过消息类型({@code type})可以快速查找对应的处理器。 *

* * @author Hccake 2021/1/4 * @version 1.0 */ public final class JsonMessageHandlerHolder { private JsonMessageHandlerHolder() { } private static final Map MESSAGE_HANDLER_MAP = new ConcurrentHashMap<>(); /** * 根据消息类型获取对应的 JSON 消息处理器。 * @param type 消息类型字符串。 * @return 对应的 {@link JsonMessageHandler} 实例,如果不存在则返回 {@code null}。 */ public static JsonMessageHandler getHandler(String type) { return MESSAGE_HANDLER_MAP.get(type); } /** * 添加一个 JSON 消息处理器到持有者中。 *

* 处理器会根据其 {@link JsonMessageHandler#type()} 方法返回的类型进行注册。 *

* @param jsonMessageHandler 待添加的 JSON 消息处理器实例。 */ public static void addHandler(JsonMessageHandler jsonMessageHandler) { MESSAGE_HANDLER_MAP.put(jsonMessageHandler.type(), jsonMessageHandler); } } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/holder/MapSessionWebSocketHandlerDecorator.java ================================================ package com.pig4cloud.pig.common.websocket.holder; import com.pig4cloud.pig.common.websocket.config.WebSocketProperties; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator; import org.springframework.web.socket.handler.WebSocketHandlerDecorator; /** * WebSocketHandler 装饰器 *

* 此装饰器主要用于在 WebSocket 连接建立和关闭时,对会话进行映射存储和释放。 它确保了会话的生命周期管理,并支持并发消息发送的控制。 *

* * @author Hccake 2020/12/31 * @version 1.0 */ public class MapSessionWebSocketHandlerDecorator extends WebSocketHandlerDecorator { private final SessionKeyGenerator sessionKeyGenerator; private final WebSocketProperties webSocketProperties; /** * 构造一个新的 {@code MapSessionWebSocketHandlerDecorator} 实例。 * @param delegate 被装饰的原始 {@link WebSocketHandler}。 * @param sessionKeyGenerator 会话密钥生成器,用于生成会话的唯一标识。 * @param webSocketProperties WebSocket 配置属性,包含发送时间限制和缓冲区大小限制。 */ public MapSessionWebSocketHandlerDecorator(WebSocketHandler delegate, SessionKeyGenerator sessionKeyGenerator, WebSocketProperties webSocketProperties) { super(delegate); this.sessionKeyGenerator = sessionKeyGenerator; this.webSocketProperties = webSocketProperties; } /** * 在 WebSocket 连接建立后执行的动作。 *

* 此方法会生成会话的唯一标识,并将带有并发控制的 {@link WebSocketSession} 存储到 {@link WebSocketSessionHolder} * 中。 {@link ConcurrentWebSocketSessionDecorator} * 确保一次只有一个线程可以发送消息,并根据配置的缓冲区大小和发送时间限制管理会话。 *

* @param session 建立的 WebSocket 会话对象。 * @throws Exception 如果在处理过程中发生错误。 */ @Override public void afterConnectionEstablished(final WebSocketSession session) throws Exception { Object sessionKey = sessionKeyGenerator.sessionKey(session); WebSocketSessionHolder.addSession(sessionKey, new ConcurrentWebSocketSessionDecorator(session, webSocketProperties.getSendTimeLimit(), webSocketProperties.getSendBufferSizeLimit())); } /** * 在 WebSocket 连接关闭后执行的动作。 *

* 此方法会根据会话的唯一标识从 {@link WebSocketSessionHolder} 中移除对应的会话。 *

* @param session 关闭的 WebSocket 会话对象。 * @param closeStatus 关闭状态对象,包含关闭原因和状态码。 * @throws Exception 如果在处理过程中发生错误。 */ @Override public void afterConnectionClosed(final WebSocketSession session, CloseStatus closeStatus) throws Exception { Object sessionKey = sessionKeyGenerator.sessionKey(session); WebSocketSessionHolder.removeSession(sessionKey); } } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/holder/SessionKeyGenerator.java ================================================ package com.pig4cloud.pig.common.websocket.holder; import org.springframework.web.socket.WebSocketSession; /** * WebSocketSession 唯一标识生成器接口 *

* 定义了生成 WebSocket 会话唯一标识的契约。 不同的实现可以根据业务需求(如用户ID、会话ID等)生成唯一的会话标识。 *

* * @author Hccake 2021/1/5 * @version 1.0 */ public interface SessionKeyGenerator { /** * 获取当前 WebSocket 会话的唯一标识。 * @param webSocketSession 当前的 WebSocket 会话对象。 * @return 返回会话的唯一标识对象。 */ Object sessionKey(WebSocketSession webSocketSession); } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/holder/WebSocketSessionHolder.java ================================================ package com.pig4cloud.pig.common.websocket.holder; import org.springframework.web.socket.WebSocketSession; import java.util.Collection; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * WebSocketSession 持有者 *

* 负责管理和存储所有在线的 WebSocket 会话信息。 提供添加、删除、获取会话以及获取所有会话和会话键的方法。 *

* * @author Hccake 2021/1/4 * @version 1.0 */ public final class WebSocketSessionHolder { private WebSocketSessionHolder() { } private static final Map USER_SESSION_MAP = new ConcurrentHashMap<>(); /** * 添加一个 WebSocket 会话。 * @param sessionKey 会话的唯一标识。 * @param session 待添加的 WebSocketSession 对象。 */ public static void addSession(Object sessionKey, WebSocketSession session) { USER_SESSION_MAP.put(sessionKey.toString(), session); } /** * 删除一个 WebSocket 会话。 * @param sessionKey 会话的唯一标识。 */ public static void removeSession(Object sessionKey) { USER_SESSION_MAP.remove(sessionKey.toString()); } /** * 获取指定标识的 WebSocket 会话。 * @param sessionKey 会话的唯一标识。 * @return 对应的 WebSocketSession 对象,如果不存在则返回 {@code null}。 */ public static WebSocketSession getSession(Object sessionKey) { return USER_SESSION_MAP.get(sessionKey.toString()); } /** * 获取当前所有在线的 WebSocket 会话集合。 * @return 所有 WebSocketSession 对象的集合。 */ public static Collection getSessions() { return USER_SESSION_MAP.values(); } /** * 获取所有在线 WebSocket 会话的唯一标识集合。 * @return 所有会话唯一标识(字符串形式)的集合。 */ public static Set getSessionKeys() { return USER_SESSION_MAP.keySet(); } } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/message/AbstractJsonWebSocketMessage.java ================================================ package com.pig4cloud.pig.common.websocket.message; /** * 抽象的 JSON WebSocket 消息基类 *

* 提供了 JSON 格式 WebSocket 消息的通用结构,包含一个类型字段 {@code type}, 用于标识消息的具体类型,以便于消息分发和处理。 *

* * @author Hccake 2021/1/4 * @version 1.0 */ public abstract class AbstractJsonWebSocketMessage implements JsonWebSocketMessage { /** * 消息类型字段的名称。 */ public static final String TYPE_FIELD = "type"; private final String type; /** * 构造函数,用于创建抽象 JSON WebSocket 消息实例。 * @param type 消息的类型标识符。 */ protected AbstractJsonWebSocketMessage(String type) { this.type = type; } /** * 获取消息的类型。 * @return 消息类型字符串。 */ @Override public String getType() { return type; } } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/message/JsonWebSocketMessage.java ================================================ package com.pig4cloud.pig.common.websocket.message; /** * JSON WebSocket 消息接口 *

* 定义了所有 JSON 格式 WebSocket 消息应遵循的契约, 强制实现类提供一个消息类型标识符。 *

* * @author Hccake 2021/1/4 * @version 1.0 */ public interface JsonWebSocketMessage { /** * 获取消息类型。 *

* 消息类型主要用于匹配对应的消息处理器,实现消息的路由和分发。 *

* @return 当前消息的类型字符串。 */ String getType(); } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/message/PingJsonWebSocketMessage.java ================================================ package com.pig4cloud.pig.common.websocket.message; /** * Ping JSON WebSocket 消息 *

* 表示一个 Ping 类型的 WebSocket 消息,通常用于客户端向服务器发送心跳请求。 *

* * @author Hccake 2021/1/4 * @version 1.0 */ public class PingJsonWebSocketMessage extends AbstractJsonWebSocketMessage { /** * 构造函数,创建一个 Ping 类型的 JSON WebSocket 消息。 */ public PingJsonWebSocketMessage() { super(WebSocketMessageTypeEnum.PING.getValue()); } } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/message/PongJsonWebSocketMessage.java ================================================ package com.pig4cloud.pig.common.websocket.message; /** * Pong JSON WebSocket 消息 *

* 表示一个 Pong 类型的 WebSocket 消息,通常用于服务器响应客户端的 Ping 请求,作为心跳机制的一部分。 *

* * @author Hccake 2021/1/4 * @version 1.0 */ public class PongJsonWebSocketMessage extends AbstractJsonWebSocketMessage { /** * 构造函数,创建一个 Pong 类型的 JSON WebSocket 消息。 */ public PongJsonWebSocketMessage() { super(WebSocketMessageTypeEnum.PONG.getValue()); } } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/message/WebSocketMessageTypeEnum.java ================================================ package com.pig4cloud.pig.common.websocket.message; import lombok.Getter; import lombok.RequiredArgsConstructor; /** * WebSocket 消息类型枚举 *

* 定义了 WebSocket 消息的常见类型,例如 Ping 和 Pong,用于心跳机制。 *

* * @author Hccake 2021/1/4 * @version 1.0 */ @Getter @RequiredArgsConstructor public enum WebSocketMessageTypeEnum { /** * Ping 消息类型,通常由客户端发送,用于心跳检测。 */ PING("ping"), /** * Pong 消息类型,通常由服务器响应 Ping 消息发送,用于心跳检测。 */ PONG("pong"); private final String value; } ================================================ FILE: pig-common/pig-common-websocket/src/main/java/com/pig4cloud/pig/common/websocket/package-info.java ================================================ /** * 此包内代码 来源至 BallCat 关于BallCat: 组织旨在为项目快速开发提供一系列的基础能力,方便使用者根据项目需求快速进行功能拓展。 * https://github.com/ballcat-projects/ballcat */ package com.pig4cloud.pig.common.websocket; ================================================ FILE: pig-common/pig-common-websocket/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ================================================ com.pig4cloud.pig.common.websocket.config.WebSocketAutoConfiguration com.pig4cloud.pig.common.websocket.config.RedisMessageDistributorConfiguration com.pig4cloud.pig.common.websocket.config.LocalMessageDistributorConfiguration ================================================ FILE: pig-common/pig-common-xss/pom.xml ================================================ 4.0.0 com.pig4cloud pig-common ${revision} pig-common-xss jar pigx xss 安全过滤插件 基于 JSOUP 1.18.3 com.pig4cloud pig-common-core org.jsoup jsoup ${jsoup.version} org.springframework spring-webmvc com.fasterxml.jackson.core jackson-databind jakarta.servlet jakarta.servlet-api provided org.springframework.cloud spring-cloud-context ================================================ FILE: pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/PigXssAutoConfiguration.java ================================================ /* * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net). *

* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* http://www.gnu.org/licenses/lgpl.html *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.xss; import com.pig4cloud.pig.common.xss.config.PigXssProperties; import com.pig4cloud.pig.common.xss.core.*; import lombok.RequiredArgsConstructor; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.core.Ordered; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; /** * Jackson XSS 自动配置类 * * @author lengleng * @date 2025/05/31 */ @AutoConfiguration @RequiredArgsConstructor @EnableConfigurationProperties(PigXssProperties.class) @ConditionalOnProperty(prefix = PigXssProperties.PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) public class PigXssAutoConfiguration implements WebMvcConfigurer { private final PigXssProperties xssProperties; /** * 创建XSS清理器Bean * @param properties XSS配置属性 * @return XSS清理器实例 * @see DefaultXssCleaner */ @Bean @ConditionalOnMissingBean public XssCleaner xssCleaner(PigXssProperties properties) { return new DefaultXssCleaner(properties); } /** * 创建FormXssClean实例 * @param properties PigXss配置属性 * @param xssCleaner XSS清理器 * @return FormXssClean实例 */ @Bean public FormXssClean formXssClean(PigXssProperties properties, XssCleaner xssCleaner) { return new FormXssClean(properties, xssCleaner); } /** * 创建Jackson2ObjectMapperBuilderCustomizer Bean,用于XSS防护 * @param properties XSS配置属性 * @param xssCleaner XSS清理器 * @return 自定义的Jackson2ObjectMapperBuilder */ @Bean public Jackson2ObjectMapperBuilderCustomizer xssJacksonCustomizer(PigXssProperties properties, XssCleaner xssCleaner) { return builder -> builder.deserializerByType(String.class, new JacksonXssClean(properties, xssCleaner)); } /** * 添加XSS拦截器 * @param registry 拦截器注册器 */ @Override public void addInterceptors(InterceptorRegistry registry) { List patterns = xssProperties.getPathPatterns(); if (patterns.isEmpty()) { patterns.add("/**"); } XssCleanInterceptor interceptor = new XssCleanInterceptor(xssProperties); registry.addInterceptor(interceptor) .addPathPatterns(patterns) .excludePathPatterns(xssProperties.getPathExcludePatterns()) .order(Ordered.LOWEST_PRECEDENCE); } } ================================================ FILE: pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/config/PigXssProperties.java ================================================ /* * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net). *

* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* http://www.gnu.org/licenses/lgpl.html *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.xss.config; import lombok.Getter; import lombok.Setter; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.context.config.annotation.RefreshScope; import java.util.ArrayList; import java.util.List; /** * XSS 防护配置属性类 * * @author lengleng * @date 2025/05/31 */ @Getter @Setter @RefreshScope @ConfigurationProperties(PigXssProperties.PREFIX) public class PigXssProperties { public static final String PREFIX = "security.xss"; /** * 开启xss */ private boolean enabled = true; /** * 全局:对文件进行首尾 trim */ private boolean trimText = true; /** * 模式:clear 清理(默认),escape 转义 */ private Mode mode = Mode.clear; /** * [clear 专用] prettyPrint,默认关闭: 保留换行 */ private boolean prettyPrint = false; /** * [clear 专用] 使用转义,默认关闭 */ private boolean enableEscape = false; /** * 拦截的路由,默认为空 */ private List pathPatterns = new ArrayList<>(); /** * 放行的路由,默认为空 */ private List pathExcludePatterns = new ArrayList<>(); public enum Mode { /** * 清理 */ clear, /** * 转义 */ escape, /** * 校验,抛出异常 */ validate; } } ================================================ FILE: pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/config/package-info.java ================================================ /* * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net). *

* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* http://www.gnu.org/licenses/lgpl.html *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.xss.config; ================================================ FILE: pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/core/DefaultXssCleaner.java ================================================ /* * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net). *

* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* http://www.gnu.org/licenses/lgpl.html *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.xss.core; import cn.hutool.core.util.CharsetUtil; import com.pig4cloud.pig.common.xss.config.PigXssProperties; import com.pig4cloud.pig.common.xss.utils.XssUtil; import org.jsoup.Jsoup; import org.jsoup.internal.StringUtil; import org.jsoup.nodes.Document; import org.jsoup.nodes.Entities; import org.springframework.web.util.HtmlUtils; /** * 默认的XSS清理器实现类,提供HTML内容的安全清理功能 * * @author lengleng * @date 2025/05/31 */ public class DefaultXssCleaner implements XssCleaner { private final PigXssProperties properties; public DefaultXssCleaner(PigXssProperties properties) { this.properties = properties; } /** * 获取文档输出设置 * @param properties PigXss配置属性 * @return 文档输出设置对象 */ private static Document.OutputSettings getOutputSettings(PigXssProperties properties) { return new Document.OutputSettings() // 2. 转义,没找到关闭的方法,目前这个规则最少 .escapeMode(Entities.EscapeMode.xhtml) // 3. 保留换行 .prettyPrint(properties.isPrettyPrint()); } /** * 清理HTML内容,根据XSS类型和模式进行处理 * @param bodyHtml 待清理的HTML内容 * @param type XSS处理类型 * @return 清理后的HTML内容 * @throws XssException 当模式为validate且内容不合法时抛出异常 */ @Override public String clean(String bodyHtml, XssType type) { // 1. 为空直接返回 if (StringUtil.isBlank(bodyHtml)) { return bodyHtml; } PigXssProperties.Mode mode = properties.getMode(); if (PigXssProperties.Mode.escape == mode) { // html 转义 return HtmlUtils.htmlEscape(bodyHtml, CharsetUtil.UTF_8); } else if (PigXssProperties.Mode.validate == mode) { // 校验 if (Jsoup.isValid(bodyHtml, XssUtil.WHITE_LIST)) { return bodyHtml; } throw type.getXssException(bodyHtml, "Xss validate fail, input value:" + bodyHtml); } else { // 4. 清理后的 html String escapedHtml = Jsoup.clean(bodyHtml, "", XssUtil.WHITE_LIST, getOutputSettings(properties)); if (properties.isEnableEscape()) { return escapedHtml; } // 5. 反转义 return Entities.unescape(escapedHtml); } } } ================================================ FILE: pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/core/FormXssClean.java ================================================ /* * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net). *

* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* http://www.gnu.org/licenses/lgpl.html *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.xss.core; import cn.hutool.core.util.StrUtil; import com.pig4cloud.pig.common.xss.config.PigXssProperties; import com.pig4cloud.pig.common.xss.utils.XssUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.InitBinder; import java.beans.PropertyEditorSupport; /** * 表单XSS清理控制器 *

* 用于处理前端表单提交的XSS清理工作 * * @author lengleng * @date 2025/05/31 */ @ControllerAdvice @ConditionalOnProperty(prefix = PigXssProperties.PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true) @RequiredArgsConstructor public class FormXssClean { private final PigXssProperties properties; private final XssCleaner xssCleaner; /** * 初始化数据绑定器,注册自定义属性编辑器 * @param binder 数据绑定器 */ @InitBinder public void initBinder(WebDataBinder binder) { // 处理前端传来的表单字符串 binder.registerCustomEditor(String.class, new StringPropertiesEditor(xssCleaner, properties)); } /** * 字符串属性编辑器,用于处理XSS防护的字符串属性编辑 * * @author lengleng * @date 2025/05/31 */ @Slf4j @RequiredArgsConstructor public static class StringPropertiesEditor extends PropertyEditorSupport { private final XssCleaner xssCleaner; private final PigXssProperties properties; /** * 获取属性值的文本表示 * @return 属性值的字符串表示,如果值为null则返回空字符串 */ @Override public String getAsText() { Object value = getValue(); return value != null ? value.toString() : StrUtil.EMPTY; } /** * 设置文本值,根据XSS防护状态进行相应处理 * @param text 要设置的文本 * @throws IllegalArgumentException 如果参数不合法时抛出异常 */ @Override public void setAsText(String text) throws IllegalArgumentException { if (text == null) { setValue(null); } else if (XssHolder.isEnabled()) { String value = xssCleaner.clean(XssUtil.trim(text, properties.isTrimText())); setValue(value); log.debug("Request parameter value:{} cleaned up by mica-xss, current value is:{}.", text, value); } else { setValue(XssUtil.trim(text, properties.isTrimText())); } } } } ================================================ FILE: pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/core/FromXssException.java ================================================ /* * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net). *

* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* http://www.gnu.org/licenses/lgpl.html *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.xss.core; import java.io.Serial; import lombok.Getter; /** * XSS 表单异常类,用于处理表单相关的 XSS 异常情况 * * @author lengleng * @date 2025/05/31 * @see IllegalStateException * @see XssException */ @Getter public class FromXssException extends IllegalStateException implements XssException { @Serial private static final long serialVersionUID = 1L; private final String input; /** * 构造FromXssException异常 * @param input 引发异常的输入内容 * @param message 异常信息 */ public FromXssException(String input, String message) { super(message); this.input = input; } } ================================================ FILE: pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/core/JacksonXssClean.java ================================================ /* * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net). *

* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* http://www.gnu.org/licenses/lgpl.html *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.xss.core; import cn.hutool.core.util.ArrayUtil; import com.pig4cloud.pig.common.xss.config.PigXssProperties; import com.pig4cloud.pig.common.xss.utils.XssUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import java.io.IOException; import java.util.Objects; /** * Jackson XSS 处理类,用于清理JSON数据中的XSS风险内容 * * @author lengleng * @date 2025/05/31 */ @Slf4j @RequiredArgsConstructor public class JacksonXssClean extends XssCleanDeserializerBase { private final PigXssProperties properties; private final XssCleaner xssCleaner; /** * 清理文本内容,根据XSS防护设置进行处理 * @param name 属性名称 * @param text 待清理的文本 * @return 清理后的文本 * @throws IOException 如果清理过程中发生IO异常 */ @Override public String clean(String name, String text) throws IOException { if (XssHolder.isEnabled() && Objects.isNull(XssHolder.getXssCleanIgnore())) { String value = xssCleaner.clean(XssUtil.trim(text, properties.isTrimText())); log.debug("Json property value:{} cleaned up by mica-xss, current value is:{}.", text, value); return value; } else if (XssHolder.isEnabled() && Objects.nonNull(XssHolder.getXssCleanIgnore())) { XssCleanIgnore xssCleanIgnore = XssHolder.getXssCleanIgnore(); if (ArrayUtil.contains(xssCleanIgnore.value(), name)) { return XssUtil.trim(text, properties.isTrimText()); } String value = xssCleaner.clean(XssUtil.trim(text, properties.isTrimText())); log.debug("Json property value:{} cleaned up by mica-xss, current value is:{}.", text, value); return value; } else { return XssUtil.trim(text, properties.isTrimText()); } } } ================================================ FILE: pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/core/JacksonXssException.java ================================================ /* * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net). *

* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* http://www.gnu.org/licenses/lgpl.html *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.xss.core; import lombok.Getter; import java.io.IOException; import java.io.Serial; /** * Jackson XSS 异常类,用于处理 JSON 序列化/反序列化过程中的 XSS 安全问题 * * @author lengleng * @date 2025/05/31 */ @Getter public class JacksonXssException extends IOException implements XssException { @Serial private static final long serialVersionUID = 1L; private final String input; /** * 构造JacksonXssException异常 * @param input 引发异常的输入内容 * @param message 异常信息 */ public JacksonXssException(String input, String message) { super(message); this.input = input; } } ================================================ FILE: pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/core/XssCleanDeserializer.java ================================================ /* * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net). *

* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* http://www.gnu.org/licenses/lgpl.html *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.xss.core; import com.pig4cloud.pig.common.core.util.SpringContextHolder; import com.pig4cloud.pig.common.xss.config.PigXssProperties; import com.pig4cloud.pig.common.xss.utils.XssUtil; import lombok.extern.slf4j.Slf4j; import java.io.IOException; /** * Jackson XSS 处理反序列化器 * * @author lengleng * @date 2025/05/31 */ @Slf4j public class XssCleanDeserializer extends XssCleanDeserializerBase { /** * 清理文本中的XSS内容 * @param name 名称 * @param text 待清理的文本 * @return 清理后的文本 * @throws IOException IO异常 */ @Override public String clean(String name, String text) throws IOException { // 读取 xss 配置 PigXssProperties properties = SpringContextHolder.getBean(PigXssProperties.class); // 读取 XssCleaner bean XssCleaner xssCleaner = SpringContextHolder.getBean(XssCleaner.class); if (xssCleaner != null) { String value = xssCleaner.clean(XssUtil.trim(text, properties.isTrimText())); log.debug("Json property value:{} cleaned up by mica-xss, current value is:{}.", text, value); return value; } else { return XssUtil.trim(text, properties.isTrimText()); } } } ================================================ FILE: pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/core/XssCleanDeserializerBase.java ================================================ /* * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net). *

* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* http://www.gnu.org/licenses/lgpl.html *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.xss.core; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.exc.MismatchedInputException; import java.io.IOException; /** * Jackson XSS 处理基类 * * @author lengleng * @date 2025/05/31 */ public abstract class XssCleanDeserializerBase extends JsonDeserializer { /** * 反序列化方法,用于处理JSON字符串的反序列化并进行XSS清洗 * @param p JSON解析器 * @param ctx 反序列化上下文 * @return 经过XSS清洗后的字符串,如果输入为null则返回null * @throws IOException 当反序列化过程中发生I/O错误时抛出 * @throws MismatchedInputException 当当前JSON令牌不是VALUE_STRING时抛出 */ @Override public String deserialize(JsonParser p, DeserializationContext ctx) throws IOException { JsonToken jsonToken = p.getCurrentToken(); if (JsonToken.VALUE_STRING != jsonToken) { throw MismatchedInputException.from(p, String.class, "mica-xss: can't deserialize value of type java.lang.String from " + jsonToken); } // 解析字符串 String text = p.getValueAsString(); if (text == null) { return null; } // xss 配置 return this.clean(p.getParsingContext().getCurrentName(), text); } /** * 清理文本中的XSS攻击内容 * @param name 文本名称标识 * @param text 待清理的文本内容 * @return 清理后的安全文本 * @throws IOException 清理过程中发生IO异常时抛出 */ public abstract String clean(String name, String text) throws IOException; } ================================================ FILE: pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/core/XssCleanIgnore.java ================================================ /* * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net). *

* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* http://www.gnu.org/licenses/lgpl.html *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.xss.core; import java.lang.annotation.*; /** * 忽略 xss * * @author L.cm */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface XssCleanIgnore { /** * @return 需要跳过的字段列表 */ String[] value() default {}; } ================================================ FILE: pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/core/XssCleanInterceptor.java ================================================ /* * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net). *

* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* http://www.gnu.org/licenses/lgpl.html *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.xss.core; import cn.hutool.core.util.ArrayUtil; import com.pig4cloud.pig.common.xss.config.PigXssProperties; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.AsyncHandlerInterceptor; /** * xss 处理拦截器 * * @author L.cm */ @RequiredArgsConstructor public class XssCleanInterceptor implements AsyncHandlerInterceptor { /** * XSS防护配置属性 */ private final PigXssProperties xssProperties; /** * 预处理XSS过滤 * @param request HTTP请求 * @param response HTTP响应 * @param handler 处理器对象 * @return 总是返回true * @throws Exception 处理过程中可能抛出的异常 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1. 非控制器请求直接跳出 if (!(handler instanceof HandlerMethod)) { return true; } // 2. 没有开启 if (!xssProperties.isEnabled()) { return true; } // 3. 处理 XssIgnore 注解 HandlerMethod handlerMethod = (HandlerMethod) handler; XssCleanIgnore xssCleanIgnore = AnnotationUtils.getAnnotation(handlerMethod.getMethod(), XssCleanIgnore.class); if (xssCleanIgnore == null) { XssHolder.setEnable(); } else if (ArrayUtil.isNotEmpty(xssCleanIgnore.value())) { XssHolder.setEnable(); XssHolder.setXssCleanIgnore(xssCleanIgnore); } return true; } /** * 请求完成后清理XSS过滤器持有的线程局部变量 * @param request HTTP请求对象 * @param response HTTP响应对象 * @param handler 处理请求的处理器 * @param ex 处理过程中抛出的异常 */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { XssHolder.remove(); } /** * 在并发处理开始后清除XSSHolder中的内容 * @param request HTTP请求对象 * @param response HTTP响应对象 * @param handler 处理器对象 * @throws Exception 可能抛出的异常 */ @Override public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { XssHolder.remove(); } } ================================================ FILE: pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/core/XssCleaner.java ================================================ /* * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net). *

* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* http://www.gnu.org/licenses/lgpl.html *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.xss.core; import com.pig4cloud.pig.common.xss.utils.XssUtil; import org.jsoup.Jsoup; /** * xss 清理器 * * @author L.cm */ public interface XssCleaner { /** * 清理 html * @param html html * @return 清理后的数据 */ default String clean(String html) { return clean(html, XssType.FORM); } /** * 清理 html * @param html html * @param type XssType * @return 清理后的数据 */ String clean(String html, XssType type); /** * 判断输入是否安全 * @param html html * @return 是否安全 */ default boolean isValid(String html) { return Jsoup.isValid(html, XssUtil.WHITE_LIST); } } ================================================ FILE: pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/core/XssException.java ================================================ /* * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net). *

* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* http://www.gnu.org/licenses/lgpl.html *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.xss.core; /** * xss 异常,校验模式抛出 * * @author L.cm */ public interface XssException { /** * 输入的数据 * @return 数据 */ String getInput(); /** * 获取异常的消息 * @return 消息 */ String getMessage(); } ================================================ FILE: pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/core/XssHolder.java ================================================ /* * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net). *

* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* http://www.gnu.org/licenses/lgpl.html *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.xss.core; /** * 利用 ThreadLocal 缓存线程间的数据 * * @author L.cm */ public class XssHolder { private static final ThreadLocal TL = new ThreadLocal<>(); private static final ThreadLocal TL_IGNORE = new ThreadLocal<>(); /** * 是否开启 * @return boolean */ public static boolean isEnabled() { return Boolean.TRUE.equals(TL.get()); } /** * 标记为开启 */ static void setEnable() { TL.set(Boolean.TRUE); } /** * 保存接口上的 XssCleanIgnore * @param xssCleanIgnore XssCleanIgnore */ public static void setXssCleanIgnore(XssCleanIgnore xssCleanIgnore) { TL_IGNORE.set(xssCleanIgnore); } /** * 获取接口上的 XssCleanIgnore * @return XssCleanIgnore */ public static XssCleanIgnore getXssCleanIgnore() { return TL_IGNORE.get(); } /** * 关闭 xss 清理 */ public static void remove() { TL.remove(); TL_IGNORE.remove(); } } ================================================ FILE: pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/core/XssType.java ================================================ /* * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net). *

* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* http://www.gnu.org/licenses/lgpl.html *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.xss.core; /** * xss 数据处理类型 */ public enum XssType { /** * 表单 */ FORM() { @Override public RuntimeException getXssException(String input, String message) { return new FromXssException(input, message); } }, /** * body json */ JACKSON() { @Override public RuntimeException getXssException(String input, String message) { return new RuntimeException(message); } }; /** * 获取 xss 异常 * @param input input * @param message message * @return XssException */ public abstract RuntimeException getXssException(String input, String message); } ================================================ FILE: pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/package-info.java ================================================ /** * 此包代码来源至: https://gitee.com/596392912/mica/tree/master/mica-xss */ package com.pig4cloud.pig.common.xss; ================================================ FILE: pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/utils/XssUtil.java ================================================ /* * Copyright (c) 2016-2020, Michael Yang 杨福海 (fuhai999@gmail.com). *

* Licensed under the GNU Lesser General Public License (LGPL) ,Version 3.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* http://www.gnu.org/licenses/lgpl-3.0.txt *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.xss.utils; import org.jsoup.Jsoup; import org.jsoup.nodes.Attribute; import org.jsoup.nodes.Element; import org.springframework.util.StringUtils; import cn.hutool.core.util.StrUtil; /** * xss clean * *

* 参考自 jpress:https://gitee.com/fuhai/jpress *

* * @author L.cm * @author michael */ public class XssUtil { public static final HtmlSafeList WHITE_LIST = HtmlSafeList.INSTANCE; /** * trim 字符串 * @param text text * @return 清理后的 text */ public static String trim(String text, boolean trim) { return trim ? StrUtil.trim(text) : text; } /** * xss 清理 * @param html html * @return 清理后的 html */ public static String clean(String html) { if (StringUtils.hasText(html)) { return Jsoup.clean(html, WHITE_LIST); } return html; } /** * 做自己的白名单,允许base64的图片通过等 * * @author michael */ public static class HtmlSafeList extends org.jsoup.safety.Safelist { public static final HtmlSafeList INSTANCE = new HtmlSafeList(); public HtmlSafeList() { addTags("a", "b", "blockquote", "br", "caption", "cite", "code", "col", "colgroup", "dd", "div", "span", "embed", "object", "dl", "dt", "em", "h1", "h2", "h3", "h4", "h5", "h6", "i", "img", "li", "ol", "p", "pre", "q", "small", "strike", "strong", "sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "u", "ul"); addAttributes("a", "href", "title", "target"); addAttributes("blockquote", "cite"); addAttributes("col", "span"); addAttributes("colgroup", "span"); addAttributes("img", "align", "alt", "src", "title"); addAttributes("ol", "start"); addAttributes("q", "cite"); addAttributes("table", "summary"); addAttributes("td", "abbr", "axis", "colspan", "rowspan", "width"); addAttributes("th", "abbr", "axis", "colspan", "rowspan", "scope", "width"); addAttributes("video", "src", "autoplay", "controls", "loop", "muted", "poster", "preload"); addAttributes("object", "width", "height", "classid", "codebase"); addAttributes("param", "name", "value"); addAttributes("embed", "src", "quality", "width", "height", "allowFullScreen", "allowScriptAccess", "flashvars", "name", "type", "pluginspage"); addAttributes(":all", "class", "style", "height", "width", "type", "id", "name"); addProtocols("blockquote", "cite", "http", "https"); addProtocols("cite", "cite", "http", "https"); addProtocols("q", "cite", "http", "https"); // 如果添加以下的协议,那么href 必须是http、 https 等开头,相对路径则被过滤掉了 // addProtocols("a", "href", "ftp", "http", "https", "mailto", "tel"); // 如果添加以下的协议,那么src必须是http 或者 https 开头,相对路径则被过滤掉了, // 所以必须注释掉,允许相对路径的图片资源 // addProtocols("img", "src", "http", "https"); } @Override public boolean isSafeAttribute(String tagName, Element el, Attribute attr) { // 不允许 javascript 开头的 src 和 href if ("src".equalsIgnoreCase(attr.getKey()) || "href".equalsIgnoreCase(attr.getKey())) { String value = attr.getValue(); if (StringUtils.hasText(value) && value.toLowerCase().startsWith("javascript")) { return false; } } // 允许 base64 的图片内容 if ("img".equals(tagName) && "src".equals(attr.getKey()) && attr.getValue().startsWith("data:;base64")) { return true; } return super.isSafeAttribute(tagName, el, attr); } } } ================================================ FILE: pig-common/pig-common-xss/src/main/java/com/pig4cloud/pig/common/xss/utils/package-info.java ================================================ /* * Copyright (c) 2019-2029, Dreamlu 卢春梦 (596392912@qq.com & www.dreamlu.net). *

* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* http://www.gnu.org/licenses/lgpl.html *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.common.xss.utils; ================================================ FILE: pig-common/pig-common-xss/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports ================================================ com.pig4cloud.pig.common.xss.PigXssAutoConfiguration ================================================ FILE: pig-common/pig-common-xss/src/main/resources/META-INF/spring-configuration-metadata.json ================================================ { "groups": [ { "name": "security.xss", "type": "com.pig4cloud.pig.common.xss.config.PigXssProperties", "sourceType": "com.pig4cloud.pig.common.xss.config.PigXssProperties" } ], "properties": [ { "name": "security.xss.enable-escape", "type": "java.lang.Boolean", "description": "[clear 专用] 使用转义,默认关闭", "sourceType": "com.pig4cloud.pig.common.xss.config.PigXssProperties", "defaultValue": false }, { "name": "security.xss.enabled", "type": "java.lang.Boolean", "description": "开启xss", "sourceType": "com.pig4cloud.pig.common.xss.config.PigXssProperties", "defaultValue": true }, { "name": "security.xss.mode", "type": "com.pig4cloud.pig.common.xss.config.PigXssProperties$Mode", "description": "模式:clear 清理(默认),escape 转义", "sourceType": "com.pig4cloud.pig.common.xss.config.PigXssProperties" }, { "name": "security.xss.path-exclude-patterns", "type": "java.util.List", "description": "放行的路由,默认为空", "sourceType": "com.pig4cloud.pig.common.xss.config.PigXssProperties" }, { "name": "security.xss.path-patterns", "type": "java.util.List", "description": "拦截的路由,默认为空", "sourceType": "com.pig4cloud.pig.common.xss.config.PigXssProperties" }, { "name": "security.xss.pretty-print", "type": "java.lang.Boolean", "description": "[clear 专用] prettyPrint,默认关闭: 保留换行", "sourceType": "com.pig4cloud.pig.common.xss.config.PigXssProperties", "defaultValue": false }, { "name": "security.xss.trim-text", "type": "java.lang.Boolean", "description": "全局:对文件进行首尾 trim", "sourceType": "com.pig4cloud.pig.common.xss.config.PigXssProperties", "defaultValue": true } ], "hints": [] } ================================================ FILE: pig-common/pom.xml ================================================ 4.0.0 com.pig4cloud pig ${revision} pig-common pom pig 公共聚合模块 pig-common-bom pig-common-core pig-common-datasource pig-common-log pig-common-mybatis pig-common-oss pig-common-seata pig-common-security pig-common-feign pig-common-swagger pig-common-websocket pig-common-xss pig-common-excel ================================================ FILE: pig-gateway/Dockerfile ================================================ FROM registry.cn-hangzhou.aliyuncs.com/dockerhub_mirror/java:21-anolis WORKDIR /pig-gateway ARG JAR_FILE=target/pig-gateway.jar COPY ${JAR_FILE} app.jar EXPOSE 9999 ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms128m -Xmx256m -Djava.security.egd=file:/dev/./urandom" CMD sleep 60; java $JAVA_OPTS -jar app.jar ================================================ FILE: pig-gateway/pom.xml ================================================ 4.0.0 com.pig4cloud pig ${revision} pig-gateway jar pig 服务网关,基于 spring cloud gateway org.springframework.cloud spring-cloud-starter-gateway-server-webflux org.springframework.boot spring-boot-starter-data-redis-reactive com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config org.springframework.cloud spring-cloud-starter-loadbalancer com.github.ben-manes.caffeine caffeine com.pig4cloud pig-common-core org.springdoc springdoc-openapi-starter-webflux-ui com.github.xiaoymin knife4j-openapi3-ui cn.hutool hutool-crypto org.springframework.boot spring-boot-maven-plugin io.fabric8 docker-maven-plugin ================================================ FILE: pig-gateway/src/main/java/com/pig4cloud/pig/gateway/PigGatewayApplication.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.gateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; /** * 网关应用 * * @author lengleng * @date 2025/05/30 */ @EnableDiscoveryClient @SpringBootApplication public class PigGatewayApplication { public static void main(String[] args) { SpringApplication.run(PigGatewayApplication.class, args); } } ================================================ FILE: pig-gateway/src/main/java/com/pig4cloud/pig/gateway/config/GatewayConfiguration.java ================================================ package com.pig4cloud.pig.gateway.config; import com.fasterxml.jackson.databind.ObjectMapper; import com.pig4cloud.pig.gateway.filter.PigRequestGlobalFilter; import com.pig4cloud.pig.gateway.handler.GlobalExceptionHandler; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * 网关配置类 * * @author lengleng * @date 2025/05/30 */ @Configuration(proxyBeanMethods = false) public class GatewayConfiguration { /** * 创建PigRequest全局过滤器 * @return PigRequest全局过滤器 */ @Bean public PigRequestGlobalFilter pigRequestGlobalFilter() { return new PigRequestGlobalFilter(); } /** * 创建全局异常处理程序 * @param objectMapper 对象映射器 * @return 全局异常处理程序 */ @Bean public GlobalExceptionHandler globalExceptionHandler(ObjectMapper objectMapper) { return new GlobalExceptionHandler(objectMapper); } } ================================================ FILE: pig-gateway/src/main/java/com/pig4cloud/pig/gateway/config/RateLimiterConfiguration.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.gateway.config; import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import reactor.core.publisher.Mono; import java.util.Objects; /** * 路由限流配置类 * * @author lengleng * @date 2019/2/1 */ @Configuration(proxyBeanMethods = false) public class RateLimiterConfiguration { /** * 创建基于远程地址的KeyResolver实例 * @return 根据请求的远程地址生成限流key的KeyResolver * @see Spring * Cloud Gateway文档 */ @Bean public KeyResolver remoteAddrKeyResolver() { return exchange -> Mono .just(Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress()); } } ================================================ FILE: pig-gateway/src/main/java/com/pig4cloud/pig/gateway/config/SpringDocConfiguration.java ================================================ package com.pig4cloud.pig.gateway.config; import com.alibaba.nacos.client.naming.event.InstancesChangeEvent; import com.alibaba.nacos.common.notify.Event; import com.alibaba.nacos.common.notify.NotifyCenter; import com.alibaba.nacos.common.notify.listener.Subscriber; import com.alibaba.nacos.common.utils.StringUtils; import lombok.RequiredArgsConstructor; import org.springdoc.core.properties.AbstractSwaggerUiConfigProperties; import org.springdoc.core.properties.SwaggerUiConfigProperties; import org.springframework.beans.factory.InitializingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.context.annotation.Configuration; import java.util.Set; import java.util.stream.Collectors; /** * SpringDoc配置类,实现InitializingBean接口,用于Swagger 3.0文档展示 * * @author lengleng * @date 2025/05/30 */ @RequiredArgsConstructor @Configuration(proxyBeanMethods = false) @ConditionalOnProperty(value = "springdoc.api-docs.enabled", matchIfMissing = true) public class SpringDocConfiguration implements InitializingBean { private final SwaggerUiConfigProperties swaggerUiConfigProperties; private final DiscoveryClient discoveryClient; /** * 在初始化后调用的方法,用于注册SwaggerDocRegister订阅器 */ @Override public void afterPropertiesSet() { NotifyCenter.registerSubscriber(new SwaggerDocRegister(swaggerUiConfigProperties, discoveryClient)); } } /** * Swagger文档注册器,用于处理服务实例变更事件并更新Swagger UI配置 * * @author lengleng * @date 2025/05/30 */ @RequiredArgsConstructor class SwaggerDocRegister extends Subscriber { private final SwaggerUiConfigProperties swaggerUiConfigProperties; private final DiscoveryClient discoveryClient; /** * 处理服务实例变更事件 * @param event 服务实例变更事件对象 */ @Override public void onEvent(InstancesChangeEvent event) { Set swaggerUrlSet = discoveryClient.getServices() .stream() .flatMap(serviceId -> discoveryClient.getInstances(serviceId).stream()) .filter(instance -> StringUtils.isNotBlank(instance.getMetadata().get("spring-doc"))) .map(instance -> { AbstractSwaggerUiConfigProperties.SwaggerUrl swaggerUrl = new AbstractSwaggerUiConfigProperties.SwaggerUrl(); swaggerUrl.setName(instance.getServiceId()); swaggerUrl.setUrl(String.format("/%s/v3/api-docs", instance.getMetadata().get("spring-doc"))); return swaggerUrl; }) .collect(Collectors.toSet()); swaggerUiConfigProperties.setUrls(swaggerUrlSet); } /** * 订阅类型方法,返回订阅的事件类型 * @return 订阅的事件类型 */ @Override public Class subscribeType() { return InstancesChangeEvent.class; } } ================================================ FILE: pig-gateway/src/main/java/com/pig4cloud/pig/gateway/filter/PigRequestGlobalFilter.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.gateway.filter; import com.pig4cloud.pig.common.core.constant.CommonConstants; import com.pig4cloud.pig.common.core.constant.SecurityConstants; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.util.StringUtils; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.util.Arrays; import java.util.Collections; import java.util.stream.Collectors; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl; /** * 全局拦截器,作用于所有微服务 *

* 1. 清洗请求头中的from参数 2. 重写StripPrefix = 1,支持全局路由 * * @author lengleng * @date 2025/05/30 */ public class PigRequestGlobalFilter implements GlobalFilter, Ordered { /** * 处理Web请求并(可选地)通过给定的网关过滤器链委托给下一个过滤器 * @param exchange 当前服务器交换对象 * @param chain 提供委托给下一个过滤器的方式 * @return {@code Mono} 表示请求处理完成 */ @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 1. 清洗请求头中from 参数 ServerHttpRequest request = exchange.getRequest().mutate().headers(httpHeaders -> { httpHeaders.remove(SecurityConstants.FROM); // 设置请求时间 httpHeaders.put(CommonConstants.REQUEST_START_TIME, Collections.singletonList(String.valueOf(System.currentTimeMillis()))); }).build(); // 2. 重写StripPrefix addOriginalRequestUrl(exchange, request.getURI()); String rawPath = request.getURI().getRawPath(); String newPath = "/" + Arrays.stream(StringUtils.tokenizeToStringArray(rawPath, "/")) .skip(1L) .collect(Collectors.joining("/")); ServerHttpRequest newRequest = request.mutate().path(newPath).build(); exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, newRequest.getURI()); return chain.filter(exchange.mutate().request(newRequest.mutate().build()).build()); } @Override public int getOrder() { return 10; } } ================================================ FILE: pig-gateway/src/main/java/com/pig4cloud/pig/gateway/handler/GlobalExceptionHandler.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.gateway.handler; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.pig4cloud.pig.common.core.util.R; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler; import org.springframework.core.annotation.Order; import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.http.MediaType; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * 网关异常通用处理器,作用于WebFlux环境,优先级低于ResponseStatusExceptionHandler * * @author lengleng * @date 2025/05/30 */ @Slf4j @Order(-1) @RequiredArgsConstructor public class GlobalExceptionHandler implements ErrorWebExceptionHandler { /** * 对象映射器,用于JSON序列化与反序列化 */ private final ObjectMapper objectMapper; /** * @param exchange 服务器网络交换对象 * @param ex 抛出的异常 * @return Mono 异步处理结果 */ @Override public Mono handle(ServerWebExchange exchange, Throwable ex) { ServerHttpResponse response = exchange.getResponse(); if (response.isCommitted()) { return Mono.error(ex); } // header set response.getHeaders().setContentType(MediaType.APPLICATION_JSON); if (ex instanceof ResponseStatusException) { response.setStatusCode(((ResponseStatusException) ex).getStatusCode()); } return response.writeWith(Mono.fromSupplier(() -> { DataBufferFactory bufferFactory = response.bufferFactory(); try { log.debug("Error Spring Cloud Gateway : {} {}", exchange.getRequest().getPath(), ex.getMessage()); return bufferFactory.wrap(objectMapper.writeValueAsBytes(R.failed(ex.getMessage()))); } catch (JsonProcessingException e) { log.error("Error writing response", ex); return bufferFactory.wrap(new byte[0]); } })); } } ================================================ FILE: pig-gateway/src/main/resources/application.yml ================================================ server: port: 9999 spring: application: name: @artifactId@ cloud: nacos: username: @nacos.username@ password: @nacos.password@ discovery: server-addr: ${NACOS_HOST:127.0.0.1}:${NACOS_PORT:8848} watch: enabled: true watch-delay: 1000 config: server-addr: ${spring.cloud.nacos.discovery.server-addr} config: import: - optional:nacos:application-@profiles.active@.yml - optional:nacos:${spring.application.name}-@profiles.active@.yml ================================================ FILE: pig-gateway/src/main/resources/logback-spring.xml ================================================ ${CONSOLE_LOG_PATTERN} ${log.path}/debug.log ${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz 50MB 30 ${FILE_LOG_PATTERN} ${log.path}/error.log ${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz 50MB 30 ${FILE_LOG_PATTERN} ERROR ================================================ FILE: pig-register/Dockerfile ================================================ FROM registry.cn-hangzhou.aliyuncs.com/dockerhub_mirror/java:21-anolis WORKDIR /pig-register ARG JAR_FILE=target/pig-register.jar COPY ${JAR_FILE} app.jar EXPOSE 8848 9848 8080 ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms128m -Xmx256m -Djava.security.egd=file:/dev/./urandom" CMD sleep 30; java $JAVA_OPTS -jar app.jar ================================================ FILE: pig-register/pom.xml ================================================ 4.0.0 com.pig4cloud pig ${revision} pig-register jar pig-register nacos 注册配置中心 3.1.0-bugfix io.github.pig-mesh.nacos nacos-console ${nacos.version} io.github.pig-mesh.nacos nacos-server ${nacos.version} org.springframework.boot spring-boot-maven-plugin repackage io.fabric8 docker-maven-plugin src/main/resources true **/*.woff **/*.woff2 **/*.ttf **/*.eot src/main/resources false **/*.woff **/*.woff2 **/*.ttf **/*.eot ================================================ FILE: pig-register/src/main/java/com/alibaba/nacos/bootstrap/PigNacosApplication.java ================================================ /* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.nacos.bootstrap; import static org.springframework.boot.context.logging.LoggingApplicationListener.CONFIG_PROPERTY; import static org.springframework.core.io.ResourceLoader.CLASSPATH_URL_PREFIX; import org.springframework.boot.WebApplicationType; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.context.ConfigurableApplicationContext; import com.alibaba.nacos.NacosServerBasicApplication; import com.alibaba.nacos.NacosServerWebApplication; import com.alibaba.nacos.console.NacosConsole; import com.alibaba.nacos.core.listener.startup.NacosStartUp; import com.alibaba.nacos.core.listener.startup.NacosStartUpManager; import com.alibaba.nacos.sys.env.Constants; import com.alibaba.nacos.sys.env.DeploymentType; import com.alibaba.nacos.sys.env.EnvUtil; /** * @author nacos *

* nacos console 源码运行,方便开发 生产从官网下载zip最新版集群配置运行 */ public class PigNacosApplication { /** * 独立模式系统属性名称 */ private static String STANDALONE_MODE = "nacos.standalone"; public static void main(String[] args) { System.setProperty(STANDALONE_MODE, "true"); System.setProperty(CONFIG_PROPERTY, CLASSPATH_URL_PREFIX + "logback-spring.xml"); String type = System.getProperty(Constants.NACOS_DEPLOYMENT_TYPE, Constants.NACOS_DEPLOYMENT_TYPE_MERGED); DeploymentType deploymentType = DeploymentType.getType(type); EnvUtil.setDeploymentType(deploymentType); // Start Core Context NacosStartUpManager.start(NacosStartUp.CORE_START_UP_PHASE); ConfigurableApplicationContext coreContext = new SpringApplicationBuilder(NacosServerBasicApplication.class) .web(WebApplicationType.NONE) .run(args); // Start Server Web Context NacosStartUpManager.start(NacosStartUp.WEB_START_UP_PHASE); ConfigurableApplicationContext serverWebContext = new SpringApplicationBuilder(NacosServerWebApplication.class) .parent(coreContext) .run(args); // Start Console Context NacosStartUpManager.start(NacosStartUp.CONSOLE_START_UP_PHASE); ConfigurableApplicationContext consoleContext = new SpringApplicationBuilder(NacosConsole.class) .parent(coreContext) .run(args); } } ================================================ FILE: pig-register/src/main/resources/application.properties ================================================ # Nacos \u63A7\u5236\u53F0\u7AEF\u53E3\uFF0C\u8BF7\u6CE8\u610F\u8BBF\u95EE\u7684 IP:8080 nacos.console.port=8080 # Nacos \u670D\u52A1\u7AEF\u4E3B\u7AEF\u53E3 nacos.server.main.port=8848 # \u6570\u636E\u5E93\u8BBE\u7F6E spring.sql.init.platform=mysql db.num=1 db.url.0=jdbc:mysql://${MYSQL_HOST:127.0.0.1}:${MYSQL_PORT:3306}/${MYSQL_DB:pig_config}?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true db.user=root db.password=root # nacos \u4F1A\u81EA\u52A8\u88C5\u914D\u6570\u636E\u6E90\uFF0C\u6240\u4EE5\u6392\u9664 spring.datasource \u81EA\u52A8\u914D\u7F6E spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration #*************** \u76D1\u63A7\u76F8\u5173\u914D\u7F6E ***************# # Prometheus \u76D1\u63A7\u914D\u7F6E #management.endpoints.web.exposure.include=prometheus # ElasticSearch \u76D1\u63A7\u914D\u7F6E - \u5DF2\u7981\u7528 management.elastic.metrics.export.enabled=false # InfluxDB \u76D1\u63A7\u914D\u7F6E - \u5DF2\u7981\u7528 management.influx.metrics.export.enabled=false #*************** \u914D\u7F6E\u4E2D\u5FC3\u76F8\u5173 ***************# # Nacos \u914D\u7F6E\u63A8\u9001\u6700\u5927\u91CD\u8BD5\u6B21\u6570 nacos.config.push.maxRetryTime=50 #*************** \u547D\u540D\u670D\u52A1\u76F8\u5173 ***************# # \u7A7A\u670D\u52A1\u81EA\u52A8\u6E05\u7406 nacos.naming.empty-service.auto-clean=true nacos.naming.empty-service.clean.initial-delay-ms=50000 nacos.naming.empty-service.clean.period-time-ms=30000 #*************** Nacos Web \u76F8\u5173\u914D\u7F6E ***************# # Nacos \u670D\u52A1\u7AEF\u4E0A\u4E0B\u6587\u8DEF\u5F84 nacos.server.contextPath=/nacos #*************** \u65E5\u5FD7\u76F8\u5173\u914D\u7F6E ***************# # \u5F00\u542F\u8BBF\u95EE\u65E5\u5FD7 server.tomcat.accesslog.enabled=true # \u8BBF\u95EE\u65E5\u5FD7\u4FDD\u7559\u5929\u6570\u914D\u7F6E server.tomcat.accesslog.max-days=30 # \u8BBF\u95EE\u65E5\u5FD7\u683C\u5F0F server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b %D %{User-Agent}i %{Request-Source}i # \u65E5\u5FD7\u57FA\u7840\u76EE\u5F55 server.tomcat.basedir=file:. #*************** API \u9519\u8BEF\u5904\u7406 ***************# # \u9519\u8BEF\u4FE1\u606F\u663E\u793A\u8BBE\u7F6E server.error.include-message=ALWAYS #*************** Nacos \u63A7\u5236\u53F0\u914D\u7F6E ***************# # Nacos \u63A7\u5236\u53F0\u4E0A\u4E0B\u6587\u8DEF\u5F84 nacos.console.contextPath= # Nacos \u63A7\u5236\u53F0\u8FDC\u7A0B\u670D\u52A1\u4E0A\u4E0B\u6587\u8DEF\u5F84 nacos.console.remote.server.context-path=/nacos #*************** \u5B89\u5168\u76F8\u5173\u914D\u7F6E ***************# # \u5B89\u5168\u5FFD\u7565 URL nacos.security.ignore.urls=/,/error,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-ui/public/**,/v1/auth/**,/v1/console/health/**,/actuator/**,/v1/console/server/** # \u8BA4\u8BC1\u7CFB\u7EDF\u7C7B\u578B nacos.core.auth.system.type=nacos # \u5F00\u542F\u8BA4\u8BC1 nacos.core.auth.enabled=true # \u542F\u7528 API \u8BBF\u95EE\u6743\u9650 nacos.core.auth.admin.enabled=true # \u542F\u7528\u63A7\u5236\u53F0 API \u8BA4\u8BC1 nacos.core.auth.console.enabled=true # \u5F00\u542F\u8BA4\u8BC1\u7F13\u5B58 nacos.core.auth.caching.enabled=true # \u670D\u52A1\u5668\u8EAB\u4EFD\u8BA4\u8BC1 (\u5F53 nacos.core.auth.enabled=true \u65F6\u751F\u6548) nacos.core.auth.server.identity.key=VGhpc0lzTXlDdXN0b21TZWNyZXRLZXkwMTIzNDU2Nzg= nacos.core.auth.server.identity.value=VGhpc0lzTXlDdXN0b21TZWNyZXRLZXkwMTIzNDU2Nzg= # Nacos \u4EE4\u724C\u76F8\u5173\u914D\u7F6E nacos.core.auth.plugin.nacos.token.cache.enable=false nacos.core.auth.plugin.nacos.token.expire.seconds=18000 # \u8BA4\u8BC1\u5BC6\u94A5 (Base64 \u7F16\u7801) nacos.core.auth.plugin.nacos.token.secret.key=VGhpc0lzTXlDdXN0b21TZWNyZXRLZXkwMTIzNDU2Nzg= #*************** Istio \u670D\u52A1\u7F51\u683C\u914D\u7F6E ***************# # MCP \u670D\u52A1\u652F\u6301 - \u5DF2\u7981\u7528 nacos.istio.mcp.server.enabled=false #*************** K8s \u76F8\u5173\u914D\u7F6E ***************# # K8s \u540C\u6B65\u652F\u6301 - \u5DF2\u7981\u7528 nacos.k8s.sync.enabled=false #*************** \u90E8\u7F72\u6A21\u5F0F\u914D\u7F6E ***************# # \u90E8\u7F72\u6A21\u5F0F: 'merged' \u6DF7\u5408\u6A21\u5F0F, 'server' \u670D\u52A1\u7AEF\u6A21\u5F0F, 'console' \u63A7\u5236\u53F0\u6A21\u5F0F nacos.deployment.type=merged #*************** \u6A21\u7CCA\u5339\u914D\u914D\u7F6E ***************# # \u914D\u7F6E\u4E2D\u5FC3\u6A21\u7CCA\u5339\u914D\u6700\u5927\u503C nacos.config.fuzzy.watch.max.pattern.count=20 nacos.config.fuzzy.watch.max.pattern.match.config.count=500 # \u670D\u52A1\u53D1\u73B0\u6A21\u7CCA\u5339\u914D\u6700\u5927\u503C nacos.naming.fuzzy.watch.max.pattern.count=20 nacos.naming.fuzzy.watch.max.pattern.match.service.count=500 ================================================ FILE: pig-register/src/main/resources/logback-spring.xml ================================================ ${CONSOLE_LOG_PATTERN} ${log.path}/debug.log ${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz 50MB 30 %date [%thread] %-5level [%logger{50}] %file:%line - %msg%n ${log.path}/error.log ${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz 50MB 30 %date [%thread] %-5level [%logger{50}] %file:%line - %msg%n ERROR ================================================ FILE: pig-upms/pig-upms-api/pom.xml ================================================ 4.0.0 com.pig4cloud pig-upms ${revision} pig-upms-api jar pig 通用用户权限管理系统公共api模块 com.pig4cloud pig-common-core com.pig4cloud pig-common-feign com.pig4cloud pig-common-mybatis com.pig4cloud pig-common-excel ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/dto/RegisterUserDTO.java ================================================ package com.pig4cloud.pig.admin.api.dto; import lombok.Data; /** * 注册用户 DTO * * @author lengleng * @date 2024/12/23 */ @Data public class RegisterUserDTO { /** * 用户名 */ private String username; /** * 新密码 */ private String password; /** * 电话 */ private String phone; } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/dto/SysLogDTO.java ================================================ package com.pig4cloud.pig.admin.api.dto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Data; import java.time.LocalDateTime; /** * @author lengleng * @date 2020/10/9 *

* 日志查询传输对象 */ @Data @Schema(description = "日志查询对象") public class SysLogDTO { /** * 编号 */ private Long id; /** * 日志类型 */ @NotBlank(message = "日志类型不能为空") private String logType; /** * 日志标题 */ @NotBlank(message = "日志标题不能为空") private String title; /** * 创建者 */ private String createBy; /** * 更新时间 */ private LocalDateTime updateTime; /** * 操作IP地址 */ private String remoteAddr; /** * 用户代理 */ private String userAgent; /** * 请求URI */ private String requestUri; /** * 操作方式 */ private String method; /** * 操作提交的数据 */ private String params; /** * 执行时间 */ private Long time; /** * 异常信息 */ private String exception; /** * 服务ID */ private String serviceId; /** * 创建时间区间 [开始时间,结束时间] */ private LocalDateTime[] createTime; } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/dto/UserDTO.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.api.dto; import java.io.Serial; import java.util.List; import com.pig4cloud.pig.admin.api.entity.SysUser; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; /** * @author lengleng * @date 2017/11/5 */ @Data @Schema(description = "系统用户传输对象") @EqualsAndHashCode(callSuper = true) public class UserDTO extends SysUser { @Serial private static final long serialVersionUID = 1L; /** * 角色ID */ @Schema(description = "角色id集合") private List role; /** * 部门id */ @Schema(description = "部门id") private Long deptId; /** * 岗位ID */ private List post; /** * 新密码 */ @Schema(description = "新密码") private String newpassword1; } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/dto/UserInfo.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.api.dto; import com.fasterxml.jackson.annotation.JsonIgnore; import com.pig4cloud.pig.admin.api.vo.UserVO; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import java.io.Serial; import java.io.Serializable; import java.util.ArrayList; import java.util.List; /** * 用户信息实体类,继承自UserVO并实现Serializable接口 , spring security * * @author lengleng * @date 2025/06/28 */ @Data @Schema(description = "spring security 用户信息") @EqualsAndHashCode(callSuper = true) public class UserInfo extends UserVO implements Serializable { @Serial private static final long serialVersionUID = 1L; /** * 密码 */ @JsonIgnore(value = false) private String password; /** * 随机盐 */ @JsonIgnore(value = false) private String salt; /** * 权限标识集合 */ @Schema(description = "权限标识集合") private List permissions = new ArrayList<>(); } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysDept.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.api.entity; import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.extension.activerecord.Model; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.FieldNameConstants; import java.time.LocalDateTime; /** *

* 部门管理 *

* * @author lengleng * @since 2018-01-22 */ @Data @Schema(description = "部门") @FieldNameConstants @EqualsAndHashCode(callSuper = true) public class SysDept extends Model { private static final long serialVersionUID = 1L; @TableId(value = "dept_id", type = IdType.ASSIGN_ID) @Schema(description = "部门id") private Long deptId; /** * 部门名称 */ @NotBlank(message = "部门名称不能为空") @Schema(description = "部门名称") private String name; /** * 排序 */ @NotNull(message = "排序值不能为空") @Schema(description = "排序值") private Integer sortOrder; /** * 创建人 */ @TableField(fill = FieldFill.INSERT) @Schema(description = "创建人") private String createBy; /** * 修改人 */ @TableField(fill = FieldFill.UPDATE) @Schema(description = "修改人") private String updateBy; /** * 创建时间 */ @Schema(description = "创建时间") @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; /** * 修改时间 */ @Schema(description = "修改时间") @TableField(fill = FieldFill.UPDATE) private LocalDateTime updateTime; /** * 父级部门id */ @Schema(description = "父级部门id") private Long parentId; /** * 是否删除 1:已删除 0:正常 */ @TableLogic @Schema(description = "删除标记,1:已删除,0:正常") @TableField(fill = FieldFill.INSERT) private String delFlag; } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysDeptRelation.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.api.entity; import com.baomidou.mybatisplus.extension.activerecord.Model; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; /** *

* 部门关系表 *

* * @author lengleng * @since 2018-01-22 */ @Data @Schema(description = "部门关系") @EqualsAndHashCode(callSuper = true) public class SysDeptRelation extends Model { private static final long serialVersionUID = 1L; /** * 祖先节点 */ @Schema(description = "祖先节点") private Long ancestor; /** * 后代节点 */ @Schema(description = "后代节点") private Long descendant; } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysDict.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.api.entity; import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.extension.activerecord.Model; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import java.time.LocalDateTime; /** * 字典表 * * @author lengleng * @date 2019/03/19 */ @Data @Schema(description = "字典类型") @EqualsAndHashCode(callSuper = true) public class SysDict extends Model { private static final long serialVersionUID = 1L; /** * 编号 */ @TableId(type = IdType.ASSIGN_ID) @Schema(description = "字典编号") private Long id; /** * 类型 */ @Schema(description = "字典类型") private String dictType; /** * 描述 */ @Schema(description = "字典描述") private String description; /** * 创建时间 */ @Schema(description = "创建时间") @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; /** * 更新时间 */ @Schema(description = "更新时间") @TableField(fill = FieldFill.UPDATE) private LocalDateTime updateTime; /** * 是否是系统内置 */ @Schema(description = "是否系统内置") private String systemFlag; /** * 备注信息 */ @Schema(description = "备注信息") private String remarks; /** * 创建人 */ @TableField(fill = FieldFill.INSERT) @Schema(description = "创建人") private String createBy; /** * 修改人 */ @TableField(fill = FieldFill.UPDATE) @Schema(description = "修改人") private String updateBy; /** * 删除标记 */ @TableLogic @TableField(fill = FieldFill.INSERT) @Schema(description = "删除标记,1:已删除,0:正常") private String delFlag; } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysDictItem.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.api.entity; import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.extension.activerecord.Model; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import java.time.LocalDateTime; /** * 字典项 * * @author lengleng * @date 2019/03/19 */ @Data @Schema(description = "字典项") @EqualsAndHashCode(callSuper = true) public class SysDictItem extends Model { private static final long serialVersionUID = 1L; /** * 编号 */ @TableId(type = IdType.ASSIGN_ID) @Schema(description = "字典项id") private Long id; /** * 所属字典类id */ @Schema(description = "所属字典类id") private Long dictId; /** * 数据值 */ @Schema(description = "数据值") @JsonProperty(value = "value") private String itemValue; /** * 标签名 */ @Schema(description = "标签名") private String label; /** * 类型 */ @Schema(description = "类型") private String dictType; /** * 描述 */ @Schema(description = "描述") private String description; /** * 排序(升序) */ @Schema(description = "排序值,默认升序") private Integer sortOrder; /** * 创建人 */ @TableField(fill = FieldFill.INSERT) @Schema(description = "创建人") private String createBy; /** * 修改人 */ @TableField(fill = FieldFill.UPDATE) @Schema(description = "修改人") private String updateBy; /** * 创建时间 */ @TableField(fill = FieldFill.INSERT) @Schema(description = "创建时间") private LocalDateTime createTime; /** * 更新时间 */ @TableField(fill = FieldFill.UPDATE) @Schema(description = "更新时间") private LocalDateTime updateTime; /** * 备注信息 */ @Schema(description = "备注信息") private String remarks; /** * 删除标记 */ @TableLogic @TableField(fill = FieldFill.INSERT) @Schema(description = "删除标记,1:已删除,0:正常") private String delFlag; } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysFile.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.api.entity; import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.extension.activerecord.Model; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.FieldNameConstants; import java.io.Serial; import java.time.LocalDateTime; /** * 文件管理实体类 * * @author lengleng * @date 2025/07/03 */ @Data @FieldNameConstants @Schema(description = "文件") @EqualsAndHashCode(callSuper = true) public class SysFile extends Model { @Serial private static final long serialVersionUID = 1L; /** * 编号 */ @TableId(type = IdType.ASSIGN_ID) @Schema(description = "文件编号") private Long id; /** * 文件名 */ @Schema(description = "文件名") private String fileName; /** * 原文件名 */ @Schema(description = "原始文件名") private String original; /** * 容器名称 */ @Schema(description = "存储桶名称") private String bucketName; /** * 文件类型 */ @Schema(description = "文件类型") private String type; /** * 文件大小 */ @Schema(description = "文件大小") private Long fileSize; /** * 上传人 */ @TableField(fill = FieldFill.INSERT) @Schema(description = "创建者") private String createBy; /** * 上传时间 */ @TableField(fill = FieldFill.INSERT) @Schema(description = "创建时间") private LocalDateTime createTime; /** * 更新人 */ @TableField(fill = FieldFill.INSERT) @Schema(description = "更新者") private String updateBy; /** * 更新时间 */ @TableField(fill = FieldFill.UPDATE) @Schema(description = "更新时间") private LocalDateTime updateTime; /** * 删除标识:1-删除,0-正常 */ @TableLogic @TableField(fill = FieldFill.INSERT) @Schema(description = "删除标记,1:已删除,0:正常") private String delFlag; } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysLog.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.api.entity; import cn.idev.excel.annotation.ExcelIgnore; import cn.idev.excel.annotation.ExcelProperty; import com.baomidou.mybatisplus.annotation.*; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Data; import java.io.Serializable; import java.time.LocalDateTime; /** *

* 日志表 *

* * @author lengleng * @since 2017-11-20 */ @Data @Schema(description = "日志") public class SysLog implements Serializable { private static final long serialVersionUID = 1L; /** * 编号 */ @TableId(type = IdType.ASSIGN_ID) @ExcelProperty("日志编号") @Schema(description = "日志编号") private Long id; /** * 日志类型 */ @NotBlank(message = "日志类型不能为空") @ExcelProperty("日志类型(0-正常 9-错误)") @Schema(description = "日志类型") private String logType; /** * 日志标题 */ @NotBlank(message = "日志标题不能为空") @ExcelProperty("日志标题") @Schema(description = "日志标题") private String title; /** * 创建者 */ @ExcelProperty("创建人") @TableField(fill = FieldFill.INSERT) @Schema(description = "创建人") private String createBy; /** * 创建时间 */ @ExcelProperty("创建时间") @TableField(fill = FieldFill.INSERT) @Schema(description = "创建时间") private LocalDateTime createTime; /** * 更新时间 */ @ExcelIgnore @TableField(fill = FieldFill.UPDATE) @Schema(description = "更新时间") private LocalDateTime updateTime; /** * 操作IP地址 */ @ExcelProperty("操作ip地址") @Schema(description = "操作ip地址") private String remoteAddr; /** * 用户代理 */ @Schema(description = "用户代理") private String userAgent; /** * 请求URI */ @ExcelProperty("浏览器") @Schema(description = "请求uri") private String requestUri; /** * 操作方式 */ @ExcelProperty("操作方式") @Schema(description = "操作方式") private String method; /** * 操作提交的数据 */ @ExcelProperty("提交数据") @Schema(description = "提交数据") private String params; /** * 执行时间 */ @ExcelProperty("执行时间") @Schema(description = "方法执行时间") private Long time; /** * 异常信息 */ @ExcelProperty("异常信息") @Schema(description = "异常信息") private String exception; /** * 服务ID */ @ExcelProperty("应用标识") @Schema(description = "应用标识") private String serviceId; /** * 删除标记 */ @TableLogic @ExcelIgnore @TableField(fill = FieldFill.INSERT) @Schema(description = "删除标记,1:已删除,0:正常") private String delFlag; } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysMenu.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.api.entity; import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.extension.activerecord.Model; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.FieldNameConstants; import java.time.LocalDateTime; /** *

* 菜单权限表 *

* * @author lengleng * @since 2017-11-08 */ @Data @Schema(description = "菜单") @FieldNameConstants @EqualsAndHashCode(callSuper = true) public class SysMenu extends Model { private static final long serialVersionUID = 1L; /** * 菜单ID */ @TableId(value = "menu_id", type = IdType.ASSIGN_ID) @Schema(description = "菜单id") private Long menuId; /** * 菜单名称 */ @NotBlank(message = "菜单名称不能为空") @Schema(description = "菜单名称") private String name; /** * 菜单名称 */ @Schema(description = "菜单名称") private String enName; /** * 菜单权限标识 */ @Schema(description = "菜单权限标识") private String permission; /** * 父菜单ID */ @NotNull(message = "菜单父ID不能为空") @Schema(description = "菜单父id") private Long parentId; /** * 图标 */ @Schema(description = "菜单图标") private String icon; /** * 前端路由标识路径,默认和 comment 保持一致 过期 */ @Schema(description = "前端路由标识路径") private String path; /** * 菜单显示隐藏控制 */ @Schema(description = "菜单是否显示") private String visible; /** * 排序值 */ @Schema(description = "排序值") private Integer sortOrder; /** * 菜单类型 (0菜单 1按钮) */ @NotNull(message = "菜单类型不能为空") @Schema(description = "菜单类型,0:菜单 1:按钮") private String menuType; /** * 路由缓冲 */ @Schema(description = "路由缓冲") private String keepAlive; @Schema(description = "菜单是否内嵌") private String embedded; /** * 创建人 */ @TableField(fill = FieldFill.INSERT) @Schema(description = "创建人") private String createBy; /** * 修改人 */ @TableField(fill = FieldFill.UPDATE) @Schema(description = "修改人") private String updateBy; /** * 创建时间 */ @TableField(fill = FieldFill.INSERT) @Schema(description = "创建时间") private LocalDateTime createTime; /** * 更新时间 */ @TableField(fill = FieldFill.UPDATE) @Schema(description = "更新时间") private LocalDateTime updateTime; /** * 0--正常 1--删除 */ @TableLogic @TableField(fill = FieldFill.INSERT) @Schema(description = "删除标记,1:已删除,0:正常") private String delFlag; } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysOauthClientDetails.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.api.entity; import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.extension.activerecord.Model; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Data; import lombok.EqualsAndHashCode; import java.time.LocalDateTime; /** *

* 客户端信息 *

* * @author lengleng * @since 2018-05-15 */ @Data @Schema(description = "客户端信息") @EqualsAndHashCode(callSuper = true) public class SysOauthClientDetails extends Model { private static final long serialVersionUID = 1L; @TableId(value = "id", type = IdType.ASSIGN_ID) @Schema(description = "id") private Long id; /** * 客户端ID */ @NotBlank(message = "client_id 不能为空") @Schema(description = "客户端id") private String clientId; /** * 客户端密钥 */ @NotBlank(message = "client_secret 不能为空") @Schema(description = "客户端密钥") private String clientSecret; /** * 资源ID */ @Schema(description = "资源id列表") private String resourceIds; /** * 作用域 */ @NotBlank(message = "scope 不能为空") @Schema(description = "作用域") private String scope; /** * 授权方式[A,B,C] */ @Schema(description = "授权方式") private String[] authorizedGrantTypes; /** * 回调地址 */ @Schema(description = "回调地址") private String webServerRedirectUri; /** * 权限 */ @Schema(description = "权限列表") private String authorities; /** * 请求令牌有效时间 */ @Schema(description = "请求令牌有效时间") private Integer accessTokenValidity; /** * 刷新令牌有效时间 */ @Schema(description = "刷新令牌有效时间") private Integer refreshTokenValidity; /** * 扩展信息 */ @Schema(description = "扩展信息") private String additionalInformation; /** * 是否自动放行 */ @Schema(description = "是否自动放行") private String autoapprove; /** * 删除标记 */ @TableLogic @TableField(fill = FieldFill.INSERT) @Schema(description = "删除标记,1:已删除,0:正常") private String delFlag; /** * 创建人 */ @TableField(fill = FieldFill.INSERT) @Schema(description = "创建人") private String createBy; /** * 修改人 */ @TableField(fill = FieldFill.UPDATE) @Schema(description = "修改人") private String updateBy; /** * 创建时间 */ @TableField(fill = FieldFill.INSERT) @Schema(description = "创建时间") private LocalDateTime createTime; /** * 更新时间 */ @TableField(fill = FieldFill.UPDATE) @Schema(description = "更新时间") private LocalDateTime updateTime; } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysPost.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.api.entity; import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.extension.activerecord.Model; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Data; import lombok.EqualsAndHashCode; import java.time.LocalDateTime; /** * 岗位信息表 * * @author fxz * @date 2022-03-26 12:50:43 */ @Data @TableName("sys_post") @EqualsAndHashCode(callSuper = true) @Schema(description = "岗位信息表") public class SysPost extends Model { private static final long serialVersionUID = 1L; /** * 岗位ID */ @TableId(value = "post_id", type = IdType.ASSIGN_ID) @Schema(description = "岗位ID") private Long postId; /** * 岗位编码 */ @NotBlank(message = "岗位编码不能为空") @Schema(description = "岗位编码") private String postCode; /** * 岗位名称 */ @NotBlank(message = "岗位名称不能为空") @Schema(description = "岗位名称") private String postName; /** * 岗位排序 */ @NotNull(message = "排序值不能为空") @Schema(description = "岗位排序") private Integer postSort; /** * 岗位描述 */ @Schema(description = "岗位描述") private String remark; /** * 创建人 */ @TableField(fill = FieldFill.INSERT) @Schema(description = "创建人") private String createBy; /** * 修改人 */ @TableField(fill = FieldFill.UPDATE) @Schema(description = "修改人") private String updateBy; /** * 是否删除 -1:已删除 0:正常 */ @TableLogic @TableField(fill = FieldFill.INSERT) @Schema(description = "是否删除 -1:已删除 0:正常") private String delFlag; /** * 创建时间 */ @Schema(description = "创建时间") @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; /** * 更新时间 */ @Schema(description = "更新时间") @TableField(fill = FieldFill.UPDATE) private LocalDateTime updateTime; } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysPublicParam.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.api.entity; import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.extension.activerecord.Model; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import java.time.LocalDateTime; /** * 公共参数配置 * * @author Lucky * @date 2019-04-29 */ @Data @Schema(description = "公共参数") @EqualsAndHashCode(callSuper = true) public class SysPublicParam extends Model { private static final long serialVersionUID = 1L; /** * 编号 */ @TableId(type = IdType.ASSIGN_ID) @Schema(description = "公共参数编号") private Long publicId; /** * 公共参数名称 */ @Schema(description = "公共参数名称", required = true, example = "公共参数名称") private String publicName; /** * 公共参数地址值,英文大写+下划线 */ @Schema(description = "键[英文大写+下划线]", required = true, example = "PIGX_PUBLIC_KEY") private String publicKey; /** * 值 */ @Schema(description = "值", required = true, example = "999") private String publicValue; /** * 状态(1有效;2无效;) */ @Schema(description = "标识[1有效;2无效]", example = "1") private String status; /** * 公共参数编码 */ @Schema(description = "编码", example = "^(PIG|PIGX)$") private String validateCode; /** * 是否是系统内置 */ @Schema(description = "是否是系统内置") private String systemFlag; /** * 配置类型:0-默认;1-检索;2-原文;3-报表;4-安全;5-文档;6-消息;9-其他 */ @Schema(description = "类型[1-检索;2-原文...]", example = "1") private String publicType; /** * 创建人 */ @TableField(fill = FieldFill.INSERT) @Schema(description = "创建人") private String createBy; /** * 修改人 */ @TableField(fill = FieldFill.UPDATE) @Schema(description = "修改人") private String updateBy; /** * 删除标记 */ @TableLogic @TableField(fill = FieldFill.INSERT) @Schema(description = "删除标记,1:已删除,0:正常") private String delFlag; /** * 创建时间 */ @TableField(fill = FieldFill.INSERT) @Schema(description = "创建时间") private LocalDateTime createTime; /** * 更新时间 */ @TableField(fill = FieldFill.UPDATE) @Schema(description = "更新时间") private LocalDateTime updateTime; } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysRole.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.api.entity; import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.extension.activerecord.Model; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Data; import lombok.EqualsAndHashCode; import java.time.LocalDateTime; /** *

* 角色表 *

* * @author lengleng * @since 2017-10-29 */ @Data @Schema(description = "角色") @EqualsAndHashCode(callSuper = true) public class SysRole extends Model { private static final long serialVersionUID = 1L; @TableId(value = "role_id", type = IdType.ASSIGN_ID) @Schema(description = "角色编号") private Long roleId; @NotBlank(message = "角色名称不能为空") @Schema(description = "角色名称") private String roleName; @NotBlank(message = "角色标识不能为空") @Schema(description = "角色标识") private String roleCode; @Schema(description = "角色描述") private String roleDesc; /** * 创建人 */ @TableField(fill = FieldFill.INSERT) @Schema(description = "创建人") private String createBy; /** * 修改人 */ @TableField(fill = FieldFill.UPDATE) @Schema(description = "修改人") private String updateBy; /** * 创建时间 */ @Schema(description = "创建时间") @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; /** * 修改时间 */ @Schema(description = "修改时间") @TableField(fill = FieldFill.UPDATE) private LocalDateTime updateTime; /** * 删除标识(0-正常,1-删除) */ @TableLogic @TableField(fill = FieldFill.INSERT) @Schema(description = "删除标记,1:已删除,0:正常") private String delFlag; } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysRoleMenu.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.api.entity; import com.baomidou.mybatisplus.extension.activerecord.Model; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; /** *

* 角色菜单表 *

* * @author lengleng * @since 2017-10-29 */ @Data @Schema(description = "角色菜单") @EqualsAndHashCode(callSuper = true) public class SysRoleMenu extends Model { private static final long serialVersionUID = 1L; /** * 角色ID */ @Schema(description = "角色id") private Long roleId; /** * 菜单ID */ @Schema(description = "菜单id") private Long menuId; } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysUser.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.api.entity; import com.baomidou.mybatisplus.annotation.*; import com.fasterxml.jackson.annotation.JsonIgnore; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.io.Serializable; import java.time.LocalDateTime; /** *

* 用户表 *

* * @author lengleng * @since 2017-10-29 */ @Data @Schema(description = "用户") public class SysUser implements Serializable { private static final long serialVersionUID = 1L; /** * 主键ID */ @TableId(value = "user_id", type = IdType.ASSIGN_ID) @Schema(description = "主键id") private Long userId; /** * 用户名 */ @Schema(description = "用户名") private String username; /** * 密码 */ @Schema(description = "密码") private String password; /** * 随机盐 */ @JsonIgnore @Schema(description = "随机盐") private String salt; /** * 创建人 */ @TableField(fill = FieldFill.INSERT) @Schema(description = "创建人") private String createBy; /** * 修改人 */ @TableField(fill = FieldFill.UPDATE) @Schema(description = "修改人") private String updateBy; /** * 创建时间 */ @TableField(fill = FieldFill.INSERT) @Schema(description = "创建时间") private LocalDateTime createTime; /** * 修改时间 */ @TableField(fill = FieldFill.UPDATE) @Schema(description = "修改时间") private LocalDateTime updateTime; /** * 0-正常,1-删除 */ @TableLogic @TableField(fill = FieldFill.INSERT) @Schema(description = "删除标记,1:已删除,0:正常") private String delFlag; /** * 锁定标记 */ @Schema(description = "锁定标记") private String lockFlag; /** * 手机号 */ @Schema(description = "手机号") private String phone; /** * 头像 */ @Schema(description = "头像地址") private String avatar; /** * 部门ID */ @Schema(description = "用户所属部门id") private Long deptId; /** * 微信openid */ @Schema(description = "微信openid") private String wxOpenid; /** * 微信小程序openId */ @Schema(description = "微信小程序openid") private String miniOpenid; /** * QQ openid */ @Schema(description = "QQ openid") private String qqOpenid; /** * 码云唯一标识 */ @Schema(description = "码云唯一标识") private String giteeLogin; /** * 开源中国唯一标识 */ @Schema(description = "开源中国唯一标识") private String oscId; /** * 昵称 */ @Schema(description = "昵称") private String nickname; /** * 姓名 */ @Schema(description = "姓名") private String name; /** * 邮箱 */ @Schema(description = "邮箱") private String email; } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysUserPost.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.admin.api.entity; import com.baomidou.mybatisplus.extension.activerecord.Model; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; /** *

* 用户岗位表 *

* * @author fxz */ @Data @EqualsAndHashCode(callSuper = true) public class SysUserPost extends Model { private static final long serialVersionUID = 1L; /** * 用户ID */ @Schema(description = "用户id") private Long userId; /** * 岗位ID */ @Schema(description = "岗位id") private Long postId; } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/entity/SysUserRole.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.api.entity; import com.baomidou.mybatisplus.extension.activerecord.Model; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; /** *

* 用户角色表 *

* * @author lengleng * @since 2017-10-29 */ @Data @Schema(description = "用户角色") @EqualsAndHashCode(callSuper = true) public class SysUserRole extends Model { private static final long serialVersionUID = 1L; /** * 用户ID */ @Schema(description = "用户id") private Long userId; /** * 角色ID */ @Schema(description = "角色id") private Long roleId; } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/feign/RemoteClientDetailsService.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.api.feign; import com.pig4cloud.pig.admin.api.entity.SysOauthClientDetails; import com.pig4cloud.pig.common.core.constant.ServiceNameConstants; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.feign.annotation.NoToken; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; /** * 远程客户端详情服务接口 * * @author lengleng * @date 2025/05/30 */ @FeignClient(contextId = "remoteClientDetailsService", value = ServiceNameConstants.UPMS_SERVICE) public interface RemoteClientDetailsService { /** * 通过clientId 查询客户端信息 (未登录,需要无token 内部调用) * @param clientId 用户名 * @return R */ @NoToken @GetMapping("/client/getClientDetailsById/{clientId}") R getClientDetailsById(@PathVariable("clientId") String clientId); } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/feign/RemoteDictService.java ================================================ package com.pig4cloud.pig.admin.api.feign; import com.pig4cloud.pig.admin.api.entity.SysDictItem; import com.pig4cloud.pig.common.core.constant.ServiceNameConstants; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.feign.annotation.NoToken; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import java.util.List; /** * 远程字典服务接口 * * @author lengleng * @date 2025/05/30 */ @FeignClient(contextId = "remoteDictService", value = ServiceNameConstants.UPMS_SERVICE) public interface RemoteDictService { /** * 通过字典类型查找字典 * @param type 字典类型 * @return 同类型字典 */ @NoToken @GetMapping("/dict/remote/type/{type}") R> getDictByType(@PathVariable("type") String type); } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/feign/RemoteLogService.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.api.feign; import com.pig4cloud.pig.admin.api.entity.SysLog; import com.pig4cloud.pig.common.core.constant.ServiceNameConstants; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.feign.annotation.NoToken; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; /** * 远程日志服务接口 * * @author lengleng * @date 2025/05/30 */ @FeignClient(contextId = "remoteLogService", value = ServiceNameConstants.UPMS_SERVICE) public interface RemoteLogService { /** * 保存日志 (异步多线程调用,无token) * @param sysLog 日志实体 * @return succes、false */ @NoToken @PostMapping("/log/save") R saveLog(@RequestBody SysLog sysLog); } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/feign/RemoteParamService.java ================================================ package com.pig4cloud.pig.admin.api.feign; import com.pig4cloud.pig.common.core.constant.ServiceNameConstants; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.feign.annotation.NoToken; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; /** * 远程参数服务接口 *

* 通过Feign客户端调用UPMS服务获取参数配置 *

* * @author lengleng * @date 2025/05/30 * @see FeignClient */ @FeignClient(contextId = "remoteParamService", value = ServiceNameConstants.UPMS_SERVICE) public interface RemoteParamService { /** * 通过key 查询参数配置 * @param key key * @NoToken 声明成内部调用,避免MQ 等无法调用 */ @NoToken @GetMapping("/param/publicValue/{key}") R getByKey(@PathVariable("key") String key); } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/feign/RemoteTokenService.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.api.feign; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.pig4cloud.pig.common.core.constant.ServiceNameConstants; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.feign.annotation.NoToken; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.*; import java.util.Map; /** * 远程令牌服务接口 * * @author lengleng * @date 2025/05/30 */ @FeignClient(contextId = "remoteTokenService", value = ServiceNameConstants.AUTH_SERVICE) public interface RemoteTokenService { /** * 分页查询token 信息 * @param params 分页参数 * @return page */ @NoToken @PostMapping("/token/page") R getTokenPage(@RequestBody Map params); /** * 根据token删除token信息 * @param token 要删除的token * @return 删除操作结果,包含是否成功的布尔值 */ @NoToken @DeleteMapping("/token/remove/{token}") R removeTokenById(@PathVariable("token") String token); /** * 根据令牌查询用户信息 * @param token 用户令牌 * @return 包含用户信息的响应结果 */ @NoToken @GetMapping("/token/query-token") R> queryToken(@RequestParam("token") String token); } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/feign/RemoteUserService.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.api.feign; import com.pig4cloud.pig.admin.api.dto.UserDTO; import com.pig4cloud.pig.admin.api.dto.UserInfo; import com.pig4cloud.pig.common.core.constant.ServiceNameConstants; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.feign.annotation.NoToken; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.SpringQueryMap; import org.springframework.web.bind.annotation.GetMapping; /** * 远程用户服务接口:提供用户信息查询功能 * * @author lengleng * @date 2025/05/30 */ @FeignClient(contextId = "remoteUserService", value = ServiceNameConstants.UPMS_SERVICE) public interface RemoteUserService { /** * (未登录状态调用,需要加 @NoToken) 通过用户名查询用户、角色信息 * @param user 用户查询对象 * @return R */ @NoToken @GetMapping("/user/info/query") R info(@SpringQueryMap UserDTO user); } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/util/DictResolver.java ================================================ package com.pig4cloud.pig.admin.api.util; import cn.hutool.core.lang.Assert; import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.baomidou.mybatisplus.core.toolkit.ObjectUtils; import com.baomidou.mybatisplus.core.toolkit.StringPool; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.pig4cloud.pig.admin.api.entity.SysDictItem; import com.pig4cloud.pig.admin.api.feign.RemoteDictService; import com.pig4cloud.pig.common.core.util.SpringContextHolder; import lombok.experimental.UtilityClass; import java.util.List; /** * 字典解析工具类:提供字典数据的查询和解析功能 * * @author lengleng * @date 2025/05/30 */ @UtilityClass public class DictResolver { /** * 根据字典类型获取所有字典项 * @param type 字典类型 * @return 字典数据项集合 */ public List getDictItemsByType(String type) { Assert.isTrue(StringUtils.isNotBlank(type), "参数不合法"); RemoteDictService remoteDictService = SpringContextHolder.getBean(RemoteDictService.class); return remoteDictService.getDictByType(type).getData(); } /** * 根据字典类型以及字典项字典值获取字典标签 * @param type 字典类型 * @param itemValue 字典项字典值 * @return 字典项标签值 */ public String getDictItemLabel(String type, String itemValue) { Assert.isTrue(StringUtils.isNotBlank(type) && StringUtils.isNotBlank(itemValue), "参数不合法"); SysDictItem sysDictItem = getDictItemByItemValue(type, itemValue); return ObjectUtils.isNotEmpty(sysDictItem) ? sysDictItem.getLabel() : StringPool.EMPTY; } /** * 根据字典类型以及字典标签获取字典值 * @param type 字典类型 * @param itemLabel 字典数据标签 * @return 字典数据项值 */ public String getDictItemValue(String type, String itemLabel) { Assert.isTrue(StringUtils.isNotBlank(type) && StringUtils.isNotBlank(itemLabel), "参数不合法"); SysDictItem sysDictItem = getDictItemByItemLabel(type, itemLabel); return ObjectUtils.isNotEmpty(sysDictItem) ? sysDictItem.getItemValue() : StringPool.EMPTY; } /** * 根据字典类型以及字典值获取字典项 * @param type 字典类型 * @param itemValue 字典数据值 * @return 字典数据项 */ public SysDictItem getDictItemByItemValue(String type, String itemValue) { Assert.isTrue(StringUtils.isNotBlank(type) && StringUtils.isNotBlank(itemValue), "参数不合法"); List dictItemList = getDictItemsByType(type); if (CollectionUtils.isNotEmpty(dictItemList)) { return dictItemList.stream().filter(item -> itemValue.equals(item.getItemValue())).findFirst().orElse(null); } return null; } /** * 根据字典类型以及字典标签获取字典项 * @param type 字典类型 * @param itemLabel 字典数据项标签 * @return 字典数据项 */ public SysDictItem getDictItemByItemLabel(String type, String itemLabel) { Assert.isTrue(StringUtils.isNotBlank(type) && StringUtils.isNotBlank(itemLabel), "参数不合法"); List dictItemList = getDictItemsByType(type); if (CollectionUtils.isNotEmpty(dictItemList)) { return dictItemList.stream().filter(item -> itemLabel.equals(item.getLabel())).findFirst().orElse(null); } return null; } } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/util/ParamResolver.java ================================================ package com.pig4cloud.pig.admin.api.util; import cn.hutool.core.convert.Convert; import cn.hutool.core.util.StrUtil; import com.pig4cloud.pig.admin.api.feign.RemoteParamService; import com.pig4cloud.pig.common.core.util.SpringContextHolder; import lombok.experimental.UtilityClass; /** * 系统参数配置解析器工具类 * * @author lengleng * @date 2025/05/30 */ @UtilityClass public class ParamResolver { /** * 根据key 查询value 配置 * @param key key * @param defaultVal 默认值 * @return value */ public Long getLong(String key, Long... defaultVal) { return checkAndGet(key, Long.class, defaultVal); } /** * 根据key 查询value 配置 * @param key key * @param defaultVal 默认值 * @return value */ public String getStr(String key, String... defaultVal) { return checkAndGet(key, String.class, defaultVal); } /** * 根据key获取远程参数值并转换为指定类型 * @param key 参数key * @param clazz 目标类型 * @param defaultVal 默认值(可选,最多一个) * @param 泛型类型 * @return 转换后的参数值,未找到且无默认值时返回null * @throws IllegalArgumentException 参数不合法时抛出异常 */ private T checkAndGet(String key, Class clazz, T... defaultVal) { // 校验入参是否合法 if (StrUtil.isBlank(key) || defaultVal.length > 1) { throw new IllegalArgumentException("参数不合法"); } RemoteParamService remoteParamService = SpringContextHolder.getBean(RemoteParamService.class); String result = remoteParamService.getByKey(key).getData(); if (StrUtil.isNotBlank(result)) { return Convert.convert(clazz, result); } if (defaultVal.length == 1) { return Convert.convert(clazz, defaultVal.clone()[0]); } return null; } } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/vo/DeptExcelVo.java ================================================ package com.pig4cloud.pig.admin.api.vo; import cn.idev.excel.annotation.ExcelIgnore; import cn.idev.excel.annotation.ExcelProperty; import com.pig4cloud.plugin.excel.annotation.ExcelLine; import jakarta.validation.constraints.NotBlank; import lombok.Data; import java.io.Serial; import java.io.Serializable; /** * 部门导入导出 */ @Data public class DeptExcelVo implements Serializable { @Serial private static final long serialVersionUID = 1L; /** * 导入时候回显行号 */ @ExcelLine @ExcelIgnore private Long lineNum; /** * 上级部门 */ @NotBlank(message = "上级部门不能为空") @ExcelProperty("上级部门") private String parentName; /** * 部门名称 */ @NotBlank(message = "部门名称不能为空") @ExcelProperty("部门名称") private String name; /** * 排序 */ @ExcelProperty(value = "排序值") private Integer sortOrder; } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/vo/PostExcelVO.java ================================================ package com.pig4cloud.pig.admin.api.vo; import cn.idev.excel.annotation.ExcelIgnore; import cn.idev.excel.annotation.ExcelProperty; import cn.idev.excel.annotation.write.style.ColumnWidth; import com.pig4cloud.plugin.excel.annotation.ExcelLine; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Data; import java.io.Serializable; import java.time.LocalDateTime; /** * 岗位excel 对应的实体 * * @author fxz * @date 2022/3/21 */ @Data @ColumnWidth(30) public class PostExcelVO implements Serializable { private static final long serialVersionUID = 1L; /** * 导入时候回显行号 */ @ExcelLine @ExcelIgnore private Long lineNum; /** * 主键ID */ @ExcelProperty("岗位编号") private Long postId; /** * 岗位名称 */ @NotBlank(message = "岗位名称不能为空") @ExcelProperty("岗位名称") private String postName; /** * 岗位标识 */ @NotBlank(message = "岗位标识不能为空") @ExcelProperty("岗位标识") private String postCode; /** * 岗位排序 */ @NotNull(message = "岗位排序不能为空") @ExcelProperty("岗位排序") private Integer postSort; /** * 岗位描述 */ @NotBlank(message = "岗位描述不能为空") @ExcelProperty(value = "岗位描述") private String remark; /** * 创建时间 */ @ExcelProperty(value = "创建时间") private LocalDateTime createTime; } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/vo/PreLogVO.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.api.vo; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; /** * @author lengleng * @date 2018/8/27 前端日志vo */ @Data @Schema(description = "前端日志展示对象") public class PreLogVO { /** * 请求url */ @Schema(description = "请求url") private String url; /** * 请求耗时 */ @Schema(description = "请求耗时") private String time; /** * 请求用户 */ @Schema(description = "请求用户") private String user; /** * 请求结果 */ @Schema(description = "请求结果0:成功9:失败") private String type; /** * 请求传递参数 */ @Schema(description = "请求传递参数") private String message; /** * 异常信息 */ @Schema(description = "异常信息") private String stack; /** * 日志标题 */ @Schema(description = "日志标题") private String info; } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/vo/RoleExcelVO.java ================================================ package com.pig4cloud.pig.admin.api.vo; import cn.idev.excel.annotation.ExcelIgnore; import cn.idev.excel.annotation.ExcelProperty; import cn.idev.excel.annotation.write.style.ColumnWidth; import com.pig4cloud.plugin.excel.annotation.ExcelLine; import jakarta.validation.constraints.NotBlank; import lombok.Data; import java.io.Serializable; import java.time.LocalDateTime; /** * 角色excel 对应的实体 * * @author fxz * @date 2022/3/21 */ @Data @ColumnWidth(30) public class RoleExcelVO implements Serializable { private static final long serialVersionUID = 1L; /** * 导入时候回显行号 */ @ExcelLine @ExcelIgnore private Long lineNum; /** * 主键ID */ @ExcelProperty("角色编号") private Long roleId; /** * 角色名称 */ @NotBlank(message = "角色名称不能为空") @ExcelProperty("角色名称") private String roleName; /** * 角色标识 */ @NotBlank(message = "角色标识不能为空") @ExcelProperty("角色标识") private String roleCode; /** * 角色描述 */ @NotBlank(message = "角色描述不能为空") @ExcelProperty("角色描述") private String roleDesc; /** * 创建时间 */ @ExcelProperty(value = "创建时间") private LocalDateTime createTime; } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/vo/RoleVO.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.api.vo; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; /** * @author lengleng * @date 2020/2/10 */ @Data @Schema(description = "前端角色展示对象") public class RoleVO { /** * 角色id */ private Long roleId; /** * 菜单列表 */ private String menuIds; } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/vo/TokenVo.java ================================================ package com.pig4cloud.pig.admin.api.vo; import lombok.Data; /** * 前端展示令牌管理 * * @author lengleng * @date 2022/6/2 */ @Data public class TokenVo { private String id; private Long userId; private String clientId; private String username; private String accessToken; private String issuedAt; private String expiresAt; } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/vo/UserExcelVO.java ================================================ package com.pig4cloud.pig.admin.api.vo; import cn.idev.excel.annotation.ExcelIgnore; import cn.idev.excel.annotation.ExcelProperty; import cn.idev.excel.annotation.write.style.ColumnWidth; import com.pig4cloud.plugin.excel.annotation.DictTypeProperty; import com.pig4cloud.plugin.excel.annotation.ExcelLine; import jakarta.validation.constraints.NotBlank; import lombok.Data; import java.io.Serializable; import java.time.LocalDateTime; /** * 用户excel 对应的实体 * * @author lengleng * @date 2021/8/4 */ @Data @ColumnWidth(30) public class UserExcelVO implements Serializable { private static final long serialVersionUID = 1L; /** * 导入时候回显行号 */ @ExcelLine @ExcelIgnore private Long lineNum; /** * 主键ID */ @ExcelProperty("用户编号") private Long userId; /** * 用户名 */ @NotBlank(message = "用户名不能为空") @ExcelProperty("用户名") private String username; /** * 手机号 */ @NotBlank(message = "手机号不能为空") @ExcelProperty("手机号") private String phone; /** * 手机号 */ @NotBlank(message = "昵称不能为空") @ExcelProperty("昵称") private String nickname; /** * 手机号 */ @NotBlank(message = "姓名不能为空") @ExcelProperty("姓名") private String name; /** * 手机号 */ @NotBlank(message = "邮箱不能为空") @ExcelProperty("邮箱") private String email; /** * 部门名称 */ @NotBlank(message = "部门名称不能为空") @ExcelProperty("部门名称") private String deptName; /** * 角色列表 */ @NotBlank(message = "角色不能为空") @ExcelProperty("角色") private String roleNameList; /** * 角色列表 */ @NotBlank(message = "岗位不能为空") @ExcelProperty("岗位名称") private String postNameList; /** * 锁定标记 */ @ExcelProperty("锁定标记,0:正常,9:已锁定") @DictTypeProperty("lock_flag") private String lockFlag; /** * 创建时间 */ @ExcelProperty(value = "创建时间") private LocalDateTime createTime; } ================================================ FILE: pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/vo/UserVO.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.api.vo; import com.fasterxml.jackson.annotation.JsonIgnore; import com.pig4cloud.pig.admin.api.entity.SysDept; import com.pig4cloud.pig.admin.api.entity.SysPost; import com.pig4cloud.pig.admin.api.entity.SysRole; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.io.Serializable; import java.time.LocalDateTime; import java.util.List; /** * @author lengleng * @date 2017/10/29 */ @Data @Schema(description = "前端用户展示对象") public class UserVO implements Serializable { private static final long serialVersionUID = 1L; /** * 主键ID */ @Schema(description = "主键") private Long userId; /** * 用户名 */ @Schema(description = "用户名") private String username; /** * 密码 */ @JsonIgnore private String password; /** * 随机盐 */ @JsonIgnore private String salt; /** * 微信openid */ @Schema(description = "微信open id") private String wxOpenid; /** * QQ openid */ @Schema(description = "qq open id") private String qqOpenid; /** * gitee openid */ @Schema(description = "gitee open id") private String giteeOpenId; /** * 开源中国 openid */ @Schema(description = "开源中国 open id") private String oscOpenId; /** * 创建时间 */ @Schema(description = "创建时间") private LocalDateTime createTime; /** * 修改时间 */ @Schema(description = "修改时间") private LocalDateTime updateTime; /** * 0-正常,1-删除 */ @Schema(description = "删除标记,1:已删除,0:正常") private String delFlag; /** * 锁定标记 */ @Schema(description = "锁定标记,0:正常,9:已锁定") private String lockFlag; /** * 手机号 */ @Schema(description = "手机号") private String phone; /** * 头像 */ @Schema(description = "头像") private String avatar; /** * 部门名称 */ @Schema(description = "所属部门名称") private SysDept dept; /** * 角色列表 */ @Schema(description = "拥有的角色列表") private List roleList; /** * 岗位列表 */ private List postList; /** * 昵称 */ @Schema(description = "昵称") private String nickname; /** * 姓名 */ @Schema(description = "姓名") private String name; /** * 邮箱 */ @Schema(description = "邮箱") private String email; } ================================================ FILE: pig-upms/pig-upms-api/src/main/resources/META-INF/spring/org.springframework.cloud.openfeign.FeignClient.imports ================================================ com.pig4cloud.pig.admin.api.feign.RemoteClientDetailsService com.pig4cloud.pig.admin.api.feign.RemoteDictService com.pig4cloud.pig.admin.api.feign.RemoteLogService com.pig4cloud.pig.admin.api.feign.RemoteParamService com.pig4cloud.pig.admin.api.feign.RemoteTokenService com.pig4cloud.pig.admin.api.feign.RemoteUserService ================================================ FILE: pig-upms/pig-upms-biz/Dockerfile ================================================ FROM registry.cn-hangzhou.aliyuncs.com/dockerhub_mirror/java:21-anolis WORKDIR /pig-upms-biz ARG JAR_FILE=target/pig-upms-biz.jar COPY ${JAR_FILE} app.jar EXPOSE 4000 ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms128m -Xmx256m -Djava.security.egd=file:/dev/./urandom" CMD sleep 60; java $JAVA_OPTS -jar app.jar ================================================ FILE: pig-upms/pig-upms-biz/pom.xml ================================================ 4.0.0 com.pig4cloud pig-upms ${revision} pig-upms-biz jar pig 通用用户权限管理系统业务处理模块 com.pig4cloud pig-upms-api com.pig4cloud pig-common-oss com.pig4cloud pig-common-feign com.pig4cloud pig-common-security com.pig4cloud pig-common-log com.pig4cloud pig-common-swagger com.baomidou mybatis-plus-spring-boot3-starter com.mysql mysql-connector-j com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config org.dromara.sms4j sms4j-spring-boot-starter com.pig4cloud pig-common-xss org.springframework.boot spring-boot-starter-undertow boot cloud true org.springframework.boot spring-boot-maven-plugin io.fabric8 docker-maven-plugin src/main/resources true **/*.xlsx **/*.xls src/main/resources false **/*.xlsx **/*.xls ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/PigAdminApplication.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin; import com.pig4cloud.pig.common.feign.annotation.EnablePigFeignClients; import com.pig4cloud.pig.common.security.annotation.EnablePigResourceServer; import com.pig4cloud.pig.common.swagger.annotation.EnablePigDoc; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; /** * 用户统一管理系统 * * @author lengleng * @date 2025/05/30 */ @EnablePigDoc(value = "admin") @EnablePigFeignClients @EnablePigResourceServer @EnableDiscoveryClient @SpringBootApplication public class PigAdminApplication { public static void main(String[] args) { SpringApplication.run(PigAdminApplication.class, args); } } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysClientController.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.controller; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.pig4cloud.pig.admin.api.entity.SysOauthClientDetails; import com.pig4cloud.pig.admin.service.SysOauthClientDetailsService; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.log.annotation.SysLog; import com.pig4cloud.pig.common.security.annotation.HasPermission; import com.pig4cloud.pig.common.security.annotation.Inner; import com.pig4cloud.plugin.excel.annotation.ResponseExcel; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.AllArgsConstructor; import org.springdoc.core.annotations.ParameterObject; import org.springframework.http.HttpHeaders; import org.springframework.web.bind.annotation.*; import java.util.List; /** * 客户端管理模块前端控制器 * * @author lengleng * @date 2025/05/30 * @since 2018-05-15 */ @RestController @AllArgsConstructor @RequestMapping("/client") @Tag(description = "client", name = "客户端管理模块") @SecurityRequirement(name = HttpHeaders.AUTHORIZATION) public class SysClientController { private final SysOauthClientDetailsService clientDetailsService; /** * 通过客户端ID查询客户端详情 * @param clientId 客户端ID * @return 包含客户端详情的响应对象 */ @GetMapping("/{clientId}") @Operation(summary = "通过客户端ID查询客户端详情", description = "通过客户端ID查询客户端详情") public R getByClientId(@PathVariable String clientId) { SysOauthClientDetails details = clientDetailsService .getOne(Wrappers.lambdaQuery().eq(SysOauthClientDetails::getClientId, clientId)); return R.ok(details); } /** * 分页查询系统终端信息 * @param page 分页参数对象 * @param sysOauthClientDetails 系统终端查询条件 * @return 分页查询结果 */ @GetMapping("/page") @Operation(summary = "分页查询系统终端信息", description = "分页查询系统终端信息") public R getClientPage(@ParameterObject Page page, @ParameterObject SysOauthClientDetails sysOauthClientDetails) { LambdaQueryWrapper wrapper = Wrappers.lambdaQuery() .like(StrUtil.isNotBlank(sysOauthClientDetails.getClientId()), SysOauthClientDetails::getClientId, sysOauthClientDetails.getClientId()) .like(StrUtil.isNotBlank(sysOauthClientDetails.getClientSecret()), SysOauthClientDetails::getClientSecret, sysOauthClientDetails.getClientSecret()); return R.ok(clientDetailsService.page(page, wrapper)); } /** * 添加客户端终端 * @param clientDetails 客户端详情实体 * @return 操作结果,成功返回success,失败返回false */ @SysLog("添加终端") @PostMapping @HasPermission("sys_client_add") @Operation(summary = "添加客户端终端", description = "添加客户端终端") public R saveClient(@Valid @RequestBody SysOauthClientDetails clientDetails) { return R.ok(clientDetailsService.saveClient(clientDetails)); } /** * 根据ID列表批量删除终端 * @param ids 要删除的终端ID数组 * @return 操作结果,成功返回success */ @SysLog("删除终端") @DeleteMapping @HasPermission("sys_client_del") @Operation(summary = "根据ID列表批量删除终端", description = "根据ID列表批量删除终端") public R removeById(@RequestBody Long[] ids) { clientDetailsService.removeBatchByIds(CollUtil.toList(ids)); return R.ok(); } /** * 编辑终端信息 * @param clientDetails 终端实体信息 * @return 操作结果 */ @SysLog("编辑终端") @PutMapping @HasPermission("sys_client_edit") @Operation(summary = "编辑终端信息", description = "编辑终端信息") public R updateClient(@Valid @RequestBody SysOauthClientDetails clientDetails) { return R.ok(clientDetailsService.updateClientById(clientDetails)); } /** * 根据客户端ID获取客户端详情 * @param clientId 客户端ID * @return 包含客户端详情的响应结果 */ @Inner @GetMapping("/getClientDetailsById/{clientId}") @Operation(summary = "根据客户端ID获取客户端详情", description = "根据客户端ID获取客户端详情") public R getClientDetailsById(@PathVariable String clientId) { return R.ok(clientDetailsService.getOne( Wrappers.lambdaQuery().eq(SysOauthClientDetails::getClientId, clientId), false)); } /** * 同步缓存字典 * @return 操作结果 */ @SysLog("同步终端") @PutMapping("/sync") @Operation(summary = "同步缓存字典", description = "同步缓存字典") public R syncClient() { return clientDetailsService.syncClientCache(); } /** * 导出客户端信息到Excel * @param sysOauthClientDetails 客户端查询条件 * @return 符合条件的客户端列表 */ @ResponseExcel @SysLog("导出excel") @GetMapping("/export") @Operation(summary = "导出客户端信息到Excel", description = "导出客户端信息到Excel") public List exportClients(SysOauthClientDetails sysOauthClientDetails) { return clientDetailsService.list(Wrappers.query(sysOauthClientDetails)); } } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysDeptController.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.controller; import com.pig4cloud.pig.admin.api.entity.SysDept; import com.pig4cloud.pig.admin.api.vo.DeptExcelVo; import com.pig4cloud.pig.admin.service.SysDeptService; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.log.annotation.SysLog; import com.pig4cloud.pig.common.security.annotation.HasPermission; import com.pig4cloud.plugin.excel.annotation.RequestExcel; import com.pig4cloud.plugin.excel.annotation.ResponseExcel; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.AllArgsConstructor; import org.springframework.http.HttpHeaders; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; import java.time.LocalDateTime; import java.util.List; /** * 部门管理前端控制器 * * @author lengleng * @date 2025/05/30 */ @RestController @AllArgsConstructor @RequestMapping("/dept") @Tag(description = "dept", name = "部门管理模块") @SecurityRequirement(name = HttpHeaders.AUTHORIZATION) public class SysDeptController { private final SysDeptService sysDeptService; /** * 通过ID查询部门信息 * @param id 部门ID * @return 包含部门信息的响应对象 */ @GetMapping("/{id}") @Operation(summary = "通过ID查询部门信息", description = "通过ID查询部门信息") public R getById(@PathVariable Long id) { return R.ok(sysDeptService.getById(id)); } /** * 查询全部部门列表 * @return 包含全部部门列表的响应结果 */ @GetMapping("/list") @Operation(summary = "查询全部部门列表", description = "查询全部部门列表") public R listDepts() { return R.ok(sysDeptService.list()); } /** * 获取树形菜单 * @param deptName 部门名称 * @return 包含树形菜单的响应结果 */ @GetMapping(value = "/tree") @Operation(summary = "获取树形菜单", description = "获取树形菜单") public R getDeptTree(String deptName) { return R.ok(sysDeptService.getDeptTree(deptName)); } /** * 保存部门信息 * @param sysDept 部门实体 * @return 操作结果 */ @SysLog("添加部门") @PostMapping @HasPermission("sys_dept_add") @Operation(summary = "保存部门信息", description = "保存部门信息") public R saveDept(@Valid @RequestBody SysDept sysDept) { return R.ok(sysDeptService.save(sysDept)); } /** * 根据ID删除部门 * @param id 部门ID * @return 操作结果,成功返回true,失败返回false */ @SysLog("删除部门") @DeleteMapping("/{id}") @HasPermission("sys_dept_del") @Operation(summary = "根据ID删除部门", description = "根据ID删除部门") public R removeById(@PathVariable Long id) { return R.ok(sysDeptService.removeDeptById(id)); } /** * 编辑部门信息 * @param sysDept 部门实体对象 * @return 操作结果,成功返回success,失败返回false */ @SysLog("编辑部门") @PutMapping @HasPermission("sys_dept_edit") @Operation(summary = "编辑部门信息", description = "编辑部门信息") public R updateDept(@Valid @RequestBody SysDept sysDept) { sysDept.setUpdateTime(LocalDateTime.now()); return R.ok(sysDeptService.updateById(sysDept)); } /** * 获取部门子级列表 * @param deptId 部门ID * @return 包含子级部门列表的响应结果 */ @GetMapping(value = "/getDescendantList/{deptId}") @Operation(summary = "获取部门子级列表", description = "获取部门子级列表") public R getDescendantList(@PathVariable Long deptId) { return R.ok(sysDeptService.listDescendants(deptId)); } /** * 导出部门数据 * @return 部门数据列表 */ @ResponseExcel @GetMapping("/export") @Operation(summary = "导出部门数据", description = "导出部门数据") public List exportDepts() { return sysDeptService.exportDepts(); } /** * 导入部门信息 * @param excelVOList 部门Excel数据列表 * @param bindingResult 数据校验结果 * @return 导入结果 */ @PostMapping("import") @Operation(summary = "导入部门信息", description = "导入部门信息") public R importDept(@RequestExcel List excelVOList, BindingResult bindingResult) { return sysDeptService.importDept(excelVOList, bindingResult); } } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysDictController.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.controller; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.pig4cloud.pig.admin.api.entity.SysDict; import com.pig4cloud.pig.admin.api.entity.SysDictItem; import com.pig4cloud.pig.admin.service.SysDictItemService; import com.pig4cloud.pig.admin.service.SysDictService; import com.pig4cloud.pig.common.core.constant.CacheConstants; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.log.annotation.SysLog; import com.pig4cloud.pig.common.security.annotation.Inner; import com.pig4cloud.plugin.excel.annotation.ResponseExcel; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.AllArgsConstructor; import org.springdoc.core.annotations.ParameterObject; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.http.HttpHeaders; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import java.util.List; /** * 字典表前端控制器 * * @author lengleng * @date 2025/05/30 * @since 2019-03-19 */ @RestController @AllArgsConstructor @RequestMapping("/dict") @Tag(description = "dict", name = "字典管理模块") @SecurityRequirement(name = HttpHeaders.AUTHORIZATION) public class SysDictController { private final SysDictService sysDictService; private final SysDictItemService sysDictItemService; /** * 通过ID查询字典信息 * @param id 字典ID * @return 包含字典信息的响应对象 */ @GetMapping("/details/{id}") @Operation(summary = "通过ID查询字典信息", description = "通过ID查询字典信息") public R getById(@PathVariable Long id) { return R.ok(sysDictService.getById(id)); } /** * 查询字典详细信息 * @param query 字典查询条件对象 * @return 包含字典信息的响应结果 */ @GetMapping("/details") @Operation(summary = "查询字典详细信息", description = "查询字典详细信息") public R getDetails(@ParameterObject SysDict query) { return R.ok(sysDictService.getOne(Wrappers.query(query), false)); } /** * 分页查询字典信息 * @param page 分页对象 * @param sysDict 字典查询条件 * @return 包含分页结果的响应对象 */ @GetMapping("/page") @Operation(summary = "分页查询字典信息", description = "分页查询字典信息") public R getDictPage(@ParameterObject Page page, @ParameterObject SysDict sysDict) { return R.ok(sysDictService.page(page, Wrappers.lambdaQuery() .eq(StrUtil.isNotBlank(sysDict.getSystemFlag()), SysDict::getSystemFlag, sysDict.getSystemFlag()) .like(StrUtil.isNotBlank(sysDict.getDictType()), SysDict::getDictType, sysDict.getDictType()))); } /** * 保存字典信息 * @param sysDict 字典信息对象 * @return 操作结果,包含保存的字典信息 */ @SysLog("添加字典") @PostMapping @Operation(summary = "保存字典信息", description = "保存字典信息") @PreAuthorize("@pms.hasPermission('sys_dict_add')") public R saveDict(@Valid @RequestBody SysDict sysDict) { sysDictService.save(sysDict); return R.ok(sysDict); } /** * 删除字典并清除字典缓存 * @param ids 字典ID数组 * @return 操作结果 */ @SysLog("删除字典") @DeleteMapping @PreAuthorize("@pms.hasPermission('sys_dict_del')") @Operation(summary = "删除字典并清除字典缓存", description = "删除字典并清除字典缓存") @CacheEvict(value = CacheConstants.DICT_DETAILS, allEntries = true) public R removeById(@RequestBody Long[] ids) { return R.ok(sysDictService.removeDictByIds(ids)); } /** * 修改字典信息 * @param sysDict 字典信息 * @return 操作结果 success/false */ @PutMapping @SysLog("修改字典") @PreAuthorize("@pms.hasPermission('sys_dict_edit')") @Operation(summary = "修改字典信息", description = "修改字典信息") public R updateDict(@Valid @RequestBody SysDict sysDict) { return sysDictService.updateDict(sysDict); } /** * 分页查询字典列表 * @param name 字典类型名称或描述 * @return 包含字典列表的响应结果 */ @GetMapping("/list") @Operation(summary = "分页查询字典列表", description = "分页查询字典列表") public R listDicts(String name) { return R.ok(sysDictService.list(Wrappers.lambdaQuery() .like(StrUtil.isNotBlank(name), SysDict::getDictType, name) .or() .like(StrUtil.isNotBlank(name), SysDict::getDescription, name))); } /** * 分页查询字典项 * @param page 分页对象 * @param sysDictItem 字典项查询条件 * @return 分页查询结果 */ @GetMapping("/item/page") @Operation(summary = "分页查询字典项", description = "分页查询字典项") public R getDictItemPage(Page page, SysDictItem sysDictItem) { return R.ok(sysDictItemService.page(page, Wrappers.query(sysDictItem))); } /** * 通过id查询字典项详情 * @param id 字典项id * @return 包含字典项详情的响应结果 */ @GetMapping("/item/details/{id}") @Operation(summary = "通过id查询字典项详情", description = "通过id查询字典项详情") public R getDictItemById(@PathVariable("id") Long id) { return R.ok(sysDictItemService.getById(id)); } /** * 获取字典项详情 * @param query 字典项查询条件 * @return 包含字典项详情的响应结果 */ @GetMapping("/item/details") @Operation(summary = "获取字典项详情", description = "获取字典项详情") public R getDictItemDetails(SysDictItem query) { return R.ok(sysDictItemService.getOne(Wrappers.query(query), false)); } /** * 新增字典项 * @param sysDictItem 字典项对象 * @return 操作结果 */ @SysLog("新增字典项") @PostMapping("/item") @Operation(summary = "新增字典项", description = "新增字典项") @CacheEvict(value = CacheConstants.DICT_DETAILS, allEntries = true) public R saveDictItem(@RequestBody SysDictItem sysDictItem) { return R.ok(sysDictItemService.save(sysDictItem)); } /** * 修改字典项 * @param sysDictItem 要修改的字典项对象 * @return 操作结果 */ @SysLog("修改字典项") @PutMapping("/item") @Operation(summary = "修改字典项", description = "修改字典项") public R updateDictItem(@RequestBody SysDictItem sysDictItem) { return sysDictItemService.updateDictItem(sysDictItem); } /** * 通过id删除字典项 * @param id 字典项id * @return 操作结果 */ @SysLog("通过id删除字典项") @DeleteMapping("/item/{id}") @Operation(summary = "通过id删除字典项", description = "通过id删除字典项") public R removeDictItemById(@PathVariable Long id) { return sysDictItemService.removeDictItem(id); } /** * 同步字典缓存 * @return 操作结果 */ @SysLog("同步字典缓存") @PutMapping("/sync") @Operation(summary = "同步字典缓存", description = "同步字典缓存") public R syncDict() { return sysDictService.syncDictCache(); } /** * 导出字典项数据 * @param sysDictItem 字典项查询条件 * @return 符合条件的字典项列表 */ @ResponseExcel @GetMapping("/export") @Operation(summary = "导出字典项数据", description = "导出字典项数据") public List exportDictItems(SysDictItem sysDictItem) { return sysDictItemService.list(Wrappers.query(sysDictItem)); } /** * 通过字典类型查找字典 * @param type 类型 * @return 同类型字典 */ @GetMapping("/type/{type}") @Operation(summary = "通过字典类型查找字典", description = "通过字典类型查找字典") @Cacheable(value = CacheConstants.DICT_DETAILS, key = "#type", unless = "#result.data.isEmpty()") public R> getDictByType(@PathVariable String type) { return R.ok(sysDictItemService.list(Wrappers.query().lambda().eq(SysDictItem::getDictType, type))); } /** * 通过字典类型查找字典 (针对feign调用) TODO: 兼容性方案,代码重复 * @param type 类型 * @return 同类型字典 */ @Inner @GetMapping("/remote/type/{type}") @Operation(summary = "通过字典类型查找字典(针对feign调用)", description = "通过字典类型查找字典(针对feign调用)", hidden = true) @Cacheable(value = CacheConstants.DICT_DETAILS, key = "#type", unless = "#result.data.isEmpty()") public R> getRemoteDictByType(@PathVariable String type) { return R.ok(sysDictItemService.list(Wrappers.query().lambda().eq(SysDictItem::getDictType, type))); } } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysFileController.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.controller; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.pig4cloud.pig.admin.api.entity.SysFile; import com.pig4cloud.pig.admin.service.SysFileService; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.log.annotation.SysLog; import com.pig4cloud.pig.common.security.annotation.HasPermission; import com.pig4cloud.pig.common.security.annotation.Inner; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletResponse; import lombok.AllArgsConstructor; import lombok.SneakyThrows; import org.springdoc.core.annotations.ParameterObject; import org.springframework.core.io.ClassPathResource; import org.springframework.http.HttpHeaders; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; /** * 文件管理控制器 * * @author lengleng * @date 2025/05/30 */ @RestController @AllArgsConstructor @RequestMapping("/sys-file") @Tag(description = "sys-file", name = "文件管理模块") @SecurityRequirement(name = HttpHeaders.AUTHORIZATION) public class SysFileController { private final SysFileService sysFileService; /** * 分页查询文件信息 * @param page 分页参数对象 * @param sysFile 文件查询条件对象 * @return 分页查询结果 */ @GetMapping("/page") @Operation(summary = "分页查询", description = "分页查询") public R getFilePage(@ParameterObject Page page, @ParameterObject SysFile sysFile) { LambdaQueryWrapper wrapper = Wrappers.lambdaQuery() .like(StrUtil.isNotBlank(sysFile.getOriginal()), SysFile::getOriginal, sysFile.getOriginal()); return R.ok(sysFileService.page(page, wrapper)); } /** * 通过id删除文件管理 * @param ids 要删除的文件id数组 * @return 操作结果 */ @SysLog("删除文件管理") @DeleteMapping @HasPermission("sys_file_del") @Operation(summary = "通过id删除文件管理", description = "通过id删除文件管理") public R removeById(@RequestBody Long[] ids) { for (Long id : ids) { sysFileService.removeFile(id); } return R.ok(); } /** * 上传文件 * @param file 上传的文件资源 * @return 包含文件路径的R对象,格式为(/admin/bucketName/filename) */ @PostMapping(value = "/upload") @Operation(summary = "上传文件", description = "上传文件") public R upload(@RequestPart("file") MultipartFile file) { return sysFileService.uploadFile(file); } /** * 获取文件并写入响应流 * @param bucket 桶名称 * @param fileName 文件路径/名称 * @param response HTTP响应对象 */ @Inner(false) @GetMapping("/{bucket}/{fileName}") @Operation(summary = "获取文件并写入响应流", description = "获取文件并写入响应流") public void file(@PathVariable String bucket, @PathVariable String fileName, HttpServletResponse response) { sysFileService.getFile(bucket, fileName, response); } /** * 获取本地resources目录下的文件并写入响应流 * @param fileName 文件名称 * @param response HTTP响应对象,用于输出文件内容 * @throws IOException 文件操作异常 */ @SneakyThrows @GetMapping("/local/file/{fileName}") @Operation(summary = "获取本地resources目录下的文件并写入响应流", description = "获取本地resources目录下的文件并写入响应流") public void localFile(@PathVariable String fileName, HttpServletResponse response) { ClassPathResource resource = new ClassPathResource("file/" + fileName); response.setContentType("application/octet-stream; charset=UTF-8"); IoUtil.copy(resource.getInputStream(), response.getOutputStream()); } } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysLogController.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.controller; import cn.hutool.core.collection.CollUtil; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.pig4cloud.pig.admin.api.dto.SysLogDTO; import com.pig4cloud.pig.admin.api.entity.SysLog; import com.pig4cloud.pig.admin.service.SysLogService; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.security.annotation.HasPermission; import com.pig4cloud.pig.common.security.annotation.Inner; import com.pig4cloud.plugin.excel.annotation.ResponseExcel; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.AllArgsConstructor; import org.springdoc.core.annotations.ParameterObject; import org.springframework.http.HttpHeaders; import org.springframework.web.bind.annotation.*; import java.util.List; /** * 系统日志前端控制器 * * @author lengleng * @since 2017-11-20 */ @RestController @AllArgsConstructor @RequestMapping("/log") @Tag(description = "log", name = "日志管理模块") @SecurityRequirement(name = HttpHeaders.AUTHORIZATION) public class SysLogController { private final SysLogService sysLogService; /** * 分页查询系统日志 * @param page 分页参数对象 * @param sysLog 系统日志查询条件 * @return 包含分页结果的响应对象 */ @GetMapping("/page") @Operation(summary = "分页查询系统日志", description = "分页查询系统日志") public R getLogPage(@ParameterObject Page page, @ParameterObject SysLogDTO sysLog) { return R.ok(sysLogService.getLogPage(page, sysLog)); } /** * 批量删除日志 * @param ids 要删除的日志ID数组 * @return 操作结果,成功返回success,失败返回false */ @DeleteMapping @HasPermission("sys_log_del") @Operation(summary = "批量删除日志", description = "批量删除日志") public R removeByIds(@RequestBody Long[] ids) { return R.ok(sysLogService.removeBatchByIds(CollUtil.toList(ids))); } /** * 保存日志 * @param sysLog 日志实体 * @return 操作结果,成功返回success,失败返回false */ @Inner @PostMapping("/save") @Operation(summary = "保存日志", description = "保存日志") public R saveLog(@Valid @RequestBody SysLog sysLog) { return R.ok(sysLogService.saveLog(sysLog)); } /** * 导出系统日志到Excel表格 * @param sysLog 系统日志查询条件DTO * @return 符合查询条件的系统日志列表 */ @ResponseExcel @GetMapping("/export") @HasPermission("sys_log_export") @Operation(summary = "导出系统日志到Excel表格", description = "导出系统日志到Excel表格") public List exportLogs(SysLogDTO sysLog) { return sysLogService.listLogs(sysLog); } } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysMenuController.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.controller; import java.util.HashSet; import java.util.Set; import io.swagger.v3.oas.annotations.Operation; import org.springframework.http.HttpHeaders; 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.pig4cloud.pig.admin.api.entity.SysMenu; import com.pig4cloud.pig.admin.service.SysMenuService; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.log.annotation.SysLog; import com.pig4cloud.pig.common.security.annotation.HasPermission; import com.pig4cloud.pig.common.security.util.SecurityUtils; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.AllArgsConstructor; /** * 菜单管理控制器 * * @author lengleng * @date 2025/05/30 */ @RestController @AllArgsConstructor @RequestMapping("/menu") @Tag(description = "menu", name = "菜单管理模块") @SecurityRequirement(name = HttpHeaders.AUTHORIZATION) public class SysMenuController { private final SysMenuService sysMenuService; /** * 获取当前用户的树形菜单集合 * @param type 菜单类型 * @param parentId 父菜单ID * @return 包含菜单数据的响应对象 */ @GetMapping @Operation(summary = "获取当前用户的树形菜单集合", description = "获取当前用户的树形菜单集合") public R getUserMenu(String type, Long parentId) { // 获取符合条件的菜单 Set all = new HashSet<>(); SecurityUtils.getRoles().forEach(roleId -> all.addAll(sysMenuService.findMenuByRoleId(roleId))); return R.ok(sysMenuService.filterMenu(all, type, parentId)); } /** * 获取树形菜单集合 * @param parentId 父节点ID * @param menuName 菜单名称 * @param type 菜单类型 * @return 包含树形菜单的响应结果 */ @GetMapping(value = "/tree") @Operation(summary = "获取树形菜单集合", description = "获取树形菜单集合") public R getMenuTree(Long parentId, String menuName, String type) { return R.ok(sysMenuService.getMenuTree(parentId, menuName, type)); } /** * 根据角色ID获取菜单树 * @param roleId 角色ID * @return 包含菜单ID列表的响应结果 */ @GetMapping("/tree/{roleId}") @Operation(summary = "根据角色ID获取菜单树", description = "根据角色ID获取菜单树") public R getRoleTree(@PathVariable Long roleId) { return R.ok(sysMenuService.findMenuByRoleId(roleId).stream().map(SysMenu::getMenuId).toList()); } /** * 通过ID查询菜单的详细信息 * @param id 菜单ID * @return 包含菜单详细信息的响应对象 */ @GetMapping("/{id}") @Operation(summary = "通过ID查询菜单的详细信息", description = "通过ID查询菜单的详细信息") public R getById(@PathVariable Long id) { return R.ok(sysMenuService.getById(id)); } /** * 新增菜单 * @param sysMenu 菜单信息 * @return 操作结果 */ @SysLog("新增菜单") @PostMapping @HasPermission("sys_menu_add") @Operation(summary = "新增菜单", description = "新增菜单") public R saveMenu(@Valid @RequestBody SysMenu sysMenu) { sysMenuService.save(sysMenu); return R.ok(sysMenu); } /** * 根据菜单ID删除菜单 * @param id 要删除的菜单ID * @return 操作结果,成功返回success,失败返回false */ @SysLog("根据菜单ID删除菜单") @DeleteMapping("/{id}") @HasPermission("sys_menu_del") @Operation(summary = "根据菜单ID删除菜单", description = "根据菜单ID删除菜单") public R removeById(@PathVariable Long id) { return sysMenuService.removeMenuById(id); } /** * 更新菜单 * @param sysMenu 菜单对象 * @return 操作结果 */ @SysLog("更新菜单") @PutMapping @HasPermission("sys_menu_edit") @Operation(summary = "更新菜单", description = "更新菜单") public R updateMenu(@Valid @RequestBody SysMenu sysMenu) { return R.ok(sysMenuService.updateMenuById(sysMenu)); } } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysMobileController.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.controller; import com.pig4cloud.pig.admin.service.SysMobileService; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.security.annotation.Inner; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.AllArgsConstructor; import org.springframework.http.HttpHeaders; 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; /** * 手机管理模块控制器:提供手机验证码相关服务 * * @author lengleng * @date 2018/11/14 */ @RestController @AllArgsConstructor @RequestMapping("/mobile") @Tag(description = "mobile", name = "手机管理模块") @SecurityRequirement(name = HttpHeaders.AUTHORIZATION) public class SysMobileController { private final SysMobileService mobileService; /** * 发送短信验证码 * @param mobile 手机号码 * @return 操作结果 */ @Inner(value = false) @GetMapping("/{mobile}") @Operation(summary = "发送短信验证码", description = "发送短信验证码") public R sendSmsCode(@PathVariable String mobile) { return mobileService.sendSmsCode(mobile); } } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysPostController.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.controller; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.pig4cloud.pig.admin.api.entity.SysPost; import com.pig4cloud.pig.admin.api.vo.PostExcelVO; import com.pig4cloud.pig.admin.service.SysPostService; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.log.annotation.SysLog; import com.pig4cloud.pig.common.security.annotation.HasPermission; import com.pig4cloud.plugin.excel.annotation.RequestExcel; import com.pig4cloud.plugin.excel.annotation.ResponseExcel; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springdoc.core.annotations.ParameterObject; import org.springframework.http.HttpHeaders; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; import java.util.List; /** * 岗位信息表管理控制器 * * @author lengleng * @date 2025/05/30 */ @RestController @RequiredArgsConstructor @RequestMapping("/post") @Tag(description = "post", name = "岗位信息表管理模块") @SecurityRequirement(name = HttpHeaders.AUTHORIZATION) public class SysPostController { private final SysPostService sysPostService; /** * 获取岗位列表 * @return 包含岗位列表的响应结果 */ @GetMapping("/list") @Operation(summary = "获取岗位列表", description = "获取岗位列表") public R> listPosts() { return R.ok(sysPostService.list(Wrappers.emptyWrapper())); } /** * 分页查询岗位信息 * @param page 分页参数对象 * @param sysPost 岗位查询条件对象 * @return 分页查询结果 */ @GetMapping("/page") @HasPermission("sys_post_view") @Operation(description = "分页查询岗位信息", summary = "分页查询岗位信息") public R getPostPage(@ParameterObject Page page, @ParameterObject SysPost sysPost) { return R.ok(sysPostService.page(page, Wrappers.lambdaQuery() .like(StrUtil.isNotBlank(sysPost.getPostName()), SysPost::getPostName, sysPost.getPostName()))); } /** * 通过id查询岗位信息 * @param postId 岗位id * @return 包含岗位信息的响应结果 */ @HasPermission("sys_post_view") @GetMapping("/details/{postId}") @Operation(description = "通过id查询岗位信息", summary = "通过id查询岗位信息") public R getById(@PathVariable("postId") Long postId) { return R.ok(sysPostService.getById(postId)); } /** * 查询岗位详细信息 * @param query 查询条件 * @return 统一响应结果R,包含查询到的岗位信息 */ @GetMapping("/details") @HasPermission("sys_post_view") @Operation(description = "查询角色信息", summary = "查询角色信息") public R getDetails(SysPost query) { return R.ok(sysPostService.getOne(Wrappers.query(query), false)); } /** * 新增岗位信息 * @param sysPost 岗位信息对象 * @return 操作结果 */ @PostMapping @SysLog("新增岗位信息表") @HasPermission("sys_post_add") @Operation(description = "新增岗位信息表", summary = "新增岗位信息表") public R savePost(@RequestBody SysPost sysPost) { return R.ok(sysPostService.save(sysPost)); } /** * 修改岗位信息 * @param sysPost 岗位信息对象 * @return 操作结果 */ @PutMapping @SysLog("修改岗位信息表") @HasPermission("sys_post_edit") @Operation(description = "修改岗位信息表", summary = "修改岗位信息表") public R updatePost(@RequestBody SysPost sysPost) { return R.ok(sysPostService.updateById(sysPost)); } /** * 通过id批量删除岗位信息 * @param ids 岗位id数组 * @return 统一返回结果 */ @DeleteMapping @SysLog("通过id删除岗位信息表") @HasPermission("sys_post_del") @Operation(description = "通过id删除岗位信息表", summary = "通过id删除岗位信息表") public R removeById(@RequestBody Long[] ids) { return R.ok(sysPostService.removeBatchByIds(CollUtil.toList(ids))); } /** * 导出岗位信息到Excel表格 * @return 岗位信息Excel文件流 */ @ResponseExcel @GetMapping("/export") @HasPermission("sys_post_export") @Operation(description = "导出岗位信息到Excel表格", summary = "导出岗位信息到Excel表格") public List exportPosts() { return sysPostService.listPosts(); } /** * 导入岗位信息 * @param excelVOList 岗位Excel数据列表 * @param bindingResult 数据校验结果 * @return 导入结果 */ @PostMapping("/import") @HasPermission("sys_post_export") @Operation(description = "导入岗位信息", summary = "导入岗位信息") public R importRole(@RequestExcel List excelVOList, BindingResult bindingResult) { return sysPostService.importPost(excelVOList, bindingResult); } } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysPublicParamController.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.controller; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.pig4cloud.pig.admin.api.entity.SysPublicParam; import com.pig4cloud.pig.admin.service.SysPublicParamService; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.log.annotation.SysLog; import com.pig4cloud.pig.common.security.annotation.HasPermission; import com.pig4cloud.pig.common.security.annotation.Inner; import com.pig4cloud.plugin.excel.annotation.ResponseExcel; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.AllArgsConstructor; import org.springdoc.core.annotations.ParameterObject; import org.springframework.http.HttpHeaders; import org.springframework.web.bind.annotation.*; import java.util.List; /** * 公共参数控制器:提供公共参数的增删改查及同步功能 * * @author lengleng * @date 2025/05/30 */ @RestController @AllArgsConstructor @RequestMapping("/param") @Tag(description = "param", name = "公共参数配置管理模块") @SecurityRequirement(name = HttpHeaders.AUTHORIZATION) public class SysPublicParamController { private final SysPublicParamService sysPublicParamService; /** * 根据key查询公共参数值 * @param publicKey 公共参数key * @return 公共参数值 */ @Inner(value = false) @Operation(description = "查询公共参数值", summary = "根据key查询公共参数值") @GetMapping("/publicValue/{publicKey}") public R publicKey(@PathVariable("publicKey") String publicKey) { return R.ok(sysPublicParamService.getParamValue(publicKey)); } /** * 分页查询系统公共参数 * @param page 分页对象 * @param sysPublicParam 公共参数查询条件 * @return 分页查询结果 */ @GetMapping("/page") @Operation(description = "分页查询", summary = "分页查询") public R getParamPage(@ParameterObject Page page, @ParameterObject SysPublicParam sysPublicParam) { LambdaUpdateWrapper wrapper = Wrappers.lambdaUpdate() .like(StrUtil.isNotBlank(sysPublicParam.getPublicName()), SysPublicParam::getPublicName, sysPublicParam.getPublicName()) .like(StrUtil.isNotBlank(sysPublicParam.getPublicKey()), SysPublicParam::getPublicKey, sysPublicParam.getPublicKey()) .eq(StrUtil.isNotBlank(sysPublicParam.getSystemFlag()), SysPublicParam::getSystemFlag, sysPublicParam.getSystemFlag()); return R.ok(sysPublicParamService.page(page, wrapper)); } /** * 通过id查询公共参数 * @param publicId 公共参数id * @return 包含查询结果的响应对象 */ @Operation(description = "通过id查询公共参数", summary = "通过id查询公共参数") @GetMapping("/details/{publicId}") public R getById(@PathVariable("publicId") Long publicId) { return R.ok(sysPublicParamService.getById(publicId)); } /** * 获取系统公共参数详情 * @param param 系统公共参数查询对象 * @return 包含查询结果的响应对象 */ @GetMapping("/details") @Operation(description = "获取系统公共参数详情", summary = "获取系统公共参数详情") public R getDetail(@ParameterObject SysPublicParam param) { return R.ok(sysPublicParamService.getOne(Wrappers.query(param), false)); } /** * 新增公共参数 * @param sysPublicParam 公共参数对象 * @return 操作结果 */ @PostMapping @SysLog("新增公共参数") @Operation(description = "新增公共参数", summary = "新增公共参数") @HasPermission("sys_syspublicparam_add") public R saveParam(@RequestBody SysPublicParam sysPublicParam) { return R.ok(sysPublicParamService.save(sysPublicParam)); } /** * 修改公共参数 * @param sysPublicParam 公共参数对象 * @return 操作结果 */ @PutMapping @SysLog("修改公共参数") @HasPermission("sys_syspublicparam_edit") @Operation(description = "修改公共参数", summary = "修改公共参数") public R updateParam(@RequestBody SysPublicParam sysPublicParam) { return sysPublicParamService.updateParam(sysPublicParam); } /** * 通过id数组删除公共参数 * @param ids 要删除的公共参数id数组 * @return 操作结果 */ @DeleteMapping @SysLog("删除公共参数") @HasPermission("sys_syspublicparam_del") @Operation(description = "删除公共参数", summary = "删除公共参数") public R removeById(@RequestBody Long[] ids) { return R.ok(sysPublicParamService.removeParamByIds(ids)); } /** * 导出excel 表格 * @return */ @ResponseExcel @GetMapping("/export") @HasPermission("sys_syspublicparam_edit") @Operation(description = "导出公共参数", summary = "导出公共参数") public List exportParams() { return sysPublicParamService.list(); } /** * 同步参数到缓存 * @return 操作结果 */ @SysLog("同步参数") @PutMapping("/sync") @HasPermission("sys_syspublicparam_edit") @Operation(description = "同步参数到缓存", summary = "同步参数到缓存") public R syncParam() { return sysPublicParamService.syncParamCache(); } } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysRegisterController.java ================================================ package com.pig4cloud.pig.admin.controller; import com.pig4cloud.pig.admin.api.dto.RegisterUserDTO; import com.pig4cloud.pig.admin.service.SysUserService; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.log.annotation.SysLog; import com.pig4cloud.pig.common.security.annotation.Inner; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 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; /** * 用户注册控制器:提供用户注册功能 * * @author lengleng * @date 2025/05/30 */ @RestController @RequestMapping("/register") @RequiredArgsConstructor @Tag(description = "register", name = "注册用户管理模块") @ConditionalOnProperty(name = "register.user", matchIfMissing = true) public class SysRegisterController { private final SysUserService userService; /** * 注册用户 * @param registerUserDTO 注册用户信息DTO * @return 注册结果封装对象 */ @Inner(value = false) @SysLog("注册用户") @PostMapping("/user") @Operation(summary = "注册用户", description = "注册用户") public R registerUser(@RequestBody RegisterUserDTO registerUserDTO) { return userService.registerUser(registerUserDTO); } } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysRoleController.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.controller; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.pig4cloud.pig.admin.api.entity.SysRole; import com.pig4cloud.pig.admin.api.vo.RoleExcelVO; import com.pig4cloud.pig.admin.api.vo.RoleVO; import com.pig4cloud.pig.admin.service.SysRoleService; import com.pig4cloud.pig.common.core.constant.CacheConstants; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.log.annotation.SysLog; import com.pig4cloud.pig.common.security.annotation.HasPermission; import com.pig4cloud.plugin.excel.annotation.RequestExcel; import com.pig4cloud.plugin.excel.annotation.ResponseExcel; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.AllArgsConstructor; import org.springdoc.core.annotations.ParameterObject; import org.springframework.cache.annotation.CacheEvict; import org.springframework.http.HttpHeaders; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; import java.util.List; /** * 角色管理控制器:提供角色相关的增删改查及权限管理功能 * * @author lengleng * @date 2025/05/30 */ @RestController @AllArgsConstructor @RequestMapping("/role") @Tag(description = "role", name = "角色管理模块") @SecurityRequirement(name = HttpHeaders.AUTHORIZATION) public class SysRoleController { private final SysRoleService sysRoleService; /** * 通过ID查询角色信息 * @param id 角色ID * @return 包含角色信息的响应对象 */ @GetMapping("/details/{id}") @Operation(summary = "通过ID查询角色信息", description = "通过ID查询角色信息") public R getById(@PathVariable Long id) { return R.ok(sysRoleService.getById(id)); } /** * 查询角色详细信息 * @param query 角色查询条件对象 * @return 包含角色信息的响应结果 */ @GetMapping("/details") @Operation(summary = "查询角色详细信息", description = "查询角色详细信息") public R getDetails(@ParameterObject SysRole query) { return R.ok(sysRoleService.getOne(Wrappers.query(query), false)); } /** * 添加角色 * @param sysRole 角色信息 * @return 操作结果,成功返回success,失败返回false */ @SysLog("添加角色") @PostMapping @HasPermission("sys_role_add") @Operation(summary = "添加角色", description = "添加角色") @CacheEvict(value = CacheConstants.ROLE_DETAILS, allEntries = true) public R saveRole(@Valid @RequestBody SysRole sysRole) { return R.ok(sysRoleService.save(sysRole)); } /** * 修改角色信息 * @param sysRole 角色信息 * @return 操作结果,成功返回success,失败返回false */ @SysLog("修改角色信息") @PutMapping @HasPermission("sys_role_edit") @Operation(summary = "修改角色信息", description = "修改角色信息") @CacheEvict(value = CacheConstants.ROLE_DETAILS, allEntries = true) public R updateRole(@Valid @RequestBody SysRole sysRole) { return R.ok(sysRoleService.updateById(sysRole)); } /** * 根据ID数组删除角色 * @param ids 角色ID数组 * @return 操作结果 */ @SysLog("删除角色") @DeleteMapping @HasPermission("sys_role_del") @Operation(summary = "根据ID数组删除角色", description = "根据ID数组删除角色") @CacheEvict(value = CacheConstants.ROLE_DETAILS, allEntries = true) public R removeById(@RequestBody Long[] ids) { return R.ok(sysRoleService.removeRoleByIds(ids)); } /** * 获取角色列表 * @return 包含角色列表的响应结果 */ @GetMapping("/list") @Operation(summary = "获取角色列表", description = "获取角色列表") public R listRoles() { return R.ok(sysRoleService.list(Wrappers.emptyWrapper())); } /** * 分页查询角色信息 * @param page 分页对象 * @param role 查询条件对象 * @return 包含分页结果的响应对象 */ @GetMapping("/page") @Operation(summary = "分页查询角色信息", description = "分页查询角色信息") public R getRolePage(Page page, SysRole role) { return R.ok(sysRoleService.page(page, Wrappers.lambdaQuery() .like(StrUtil.isNotBlank(role.getRoleName()), SysRole::getRoleName, role.getRoleName()))); } /** * 更新角色菜单 * @param roleVo 角色VO对象 * @return 操作结果,成功返回success,失败返回false */ @SysLog("更新角色菜单") @PutMapping("/menu") @HasPermission("sys_role_perm") @Operation(summary = "更新角色菜单", description = "更新角色菜单") public R saveRoleMenus(@RequestBody RoleVO roleVo) { return R.ok(sysRoleService.updateRoleMenus(roleVo)); } /** * 通过角色ID列表查询角色信息 * @param roleIdList 角色ID列表 * @return 包含查询结果的响应对象 */ @PostMapping("/getRoleList") @Operation(summary = "通过角色ID列表查询角色信息", description = "通过角色ID列表查询角色信息") public R getRoleList(@RequestBody List roleIdList) { return R.ok(sysRoleService.listRolesByRoleIds(roleIdList, CollUtil.join(roleIdList, StrUtil.UNDERLINE))); } /** * 导出角色数据到Excel表格 * @return 角色数据列表 */ @ResponseExcel @GetMapping("/export") @HasPermission("sys_role_export") @Operation(summary = "导出角色数据到Excel表格", description = "导出角色数据到Excel表格") public List exportRoles() { return sysRoleService.listRoles(); } /** * 导入角色 * @param excelVOList 角色Excel数据列表 * @param bindingResult 数据校验结果 * @return 导入结果 */ @PostMapping("/import") @HasPermission("sys_role_export") @Operation(summary = "导入角色数据", description = "导入角色数据") public R importRole(@RequestExcel List excelVOList, BindingResult bindingResult) { return sysRoleService.importRole(excelVOList, bindingResult); } } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysSystemInfoController.java ================================================ package com.pig4cloud.pig.admin.controller; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.core.util.RedisUtils; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Strings; import org.springframework.data.redis.connection.RedisServerCommands; import org.springframework.data.redis.core.RedisCallback; import org.springframework.http.HttpHeaders; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.*; /** * 系统监控控制器:提供系统监控相关接口 * * @author lengleng * @date 2025/05/30 */ @RestController @RequestMapping("/system") @RequiredArgsConstructor @Tag(description = "system", name = "系统监控管理模块") @SecurityRequirement(name = HttpHeaders.AUTHORIZATION) public class SysSystemInfoController { /** * 获取Redis缓存监控信息 * @return 包含Redis信息、数据库大小和命令统计的响应结果 */ @GetMapping("/cache") @Operation(summary = "获取Redis缓存监控信息", description = "获取Redis缓存监控信息") public R cache() { Properties info = RedisUtils.execute(RedisServerCommands::info); Properties commandStats = RedisUtils.execute(connection -> connection.serverCommands().info("commandstats")); Object dbSize = RedisUtils.execute((RedisCallback) RedisServerCommands::dbSize); if (commandStats == null) { return R.failed("获取异常"); } Map result = new HashMap<>(3); 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", Strings.CS.removeStart(key, "cmdstat_")); data.put("value", StringUtils.substringBetween(property, "calls=", ",usec")); pieList.add(data); }); result.put("commandStats", pieList); return R.ok(result); } } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysTokenController.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.controller; import com.pig4cloud.pig.admin.api.feign.RemoteTokenService; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.log.annotation.SysLog; import com.pig4cloud.pig.common.security.annotation.HasPermission; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.AllArgsConstructor; import org.springframework.http.HttpHeaders; import org.springframework.web.bind.annotation.*; import java.util.Map; /** * 令牌管理控制器:提供令牌的分页查询和删除功能 * * @author lengleng * @date 2025/05/30 */ @RestController @AllArgsConstructor @RequestMapping("/sys-token") @Tag(description = "token", name = "令牌管理模块") @SecurityRequirement(name = HttpHeaders.AUTHORIZATION) public class SysTokenController { private final RemoteTokenService remoteTokenService; /** * 获取分页token信息 * @param params 请求参数集合 * @return 包含token分页信息的响应结果 */ @PostMapping("/page") @HasPermission("sys_token_del") @Operation(summary = "获取分页token信息", description = "获取分页token信息") public R getTokenPage(@RequestBody Map params) { return remoteTokenService.getTokenPage(params); } /** * 根据token数组删除token * @param tokens 需要删除的token数组 * @return 操作结果,成功返回success,失败返回false */ @SysLog("删除用户token") @DeleteMapping("/delete") @HasPermission("sys_token_del") @Operation(summary = "删除用户token", description = "删除用户token") public R removeById(@RequestBody String[] tokens) { for (String token : tokens) { remoteTokenService.removeTokenById(token); } return R.ok(); } } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/SysUserController.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.controller; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.pig4cloud.pig.admin.api.dto.UserDTO; import com.pig4cloud.pig.admin.api.dto.UserInfo; import com.pig4cloud.pig.admin.api.entity.SysUser; import com.pig4cloud.pig.admin.api.vo.UserExcelVO; import com.pig4cloud.pig.admin.service.SysUserService; import com.pig4cloud.pig.common.core.constant.CommonConstants; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.log.annotation.SysLog; import com.pig4cloud.pig.common.security.annotation.HasPermission; import com.pig4cloud.pig.common.security.annotation.Inner; import com.pig4cloud.pig.common.security.util.SecurityUtils; import com.pig4cloud.plugin.excel.annotation.RequestExcel; import com.pig4cloud.plugin.excel.annotation.ResponseExcel; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.AllArgsConstructor; import org.springdoc.core.annotations.ParameterObject; import org.springframework.http.HttpHeaders; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; import java.util.List; /** * 用户管理控制器 * * @author lengleng * @date 2025/05/30 */ @RestController @AllArgsConstructor @RequestMapping("/user") @Tag(description = "user", name = "用户管理模块") @SecurityRequirement(name = HttpHeaders.AUTHORIZATION) public class SysUserController { private final SysUserService userService; /** * 查询用户信息 * @param userDTO 用户信息查询参数 * @return 包含用户信息的R对象 */ @Inner @GetMapping(value = { "/info/query" }) @Operation(summary = "查询用户信息", description = "查询用户信息") public R info(UserDTO userDTO) { return userService.getUserInfo(userDTO); } /** * 获取当前登录用户的全部信息 * @return 包含用户信息的响应结果 */ @GetMapping(value = { "/info" }) @Operation(summary = "获取当前登录用户的全部信息", description = "获取当前登录用户的全部信息") public R info() { String username = SecurityUtils.getUser().getUsername(); UserDTO userDTO = new UserDTO(); userDTO.setUsername(username); // 获取用户信息,不返回数据库密码字段 R userInfoR = userService.getUserInfo(userDTO); if (userInfoR.getData() != null) { userInfoR.getData().setPassword(null); } return userInfoR; } /** * 通过ID查询用户信息 * @param id 用户ID * @return 包含用户信息的响应对象 */ @GetMapping("/details/{id}") @Operation(summary = "通过ID查询用户信息", description = "通过ID查询用户信息") public R user(@PathVariable Long id) { return R.ok(userService.getUserById(id)); } /** * 查询用户详细信息 * @param query 用户查询条件对象 * @return 包含查询结果的响应对象,用户不存在时返回null */ @Inner(value = false) @GetMapping("/details") @Operation(summary = "查询用户详细信息", description = "查询用户详细信息") public R getDetails(@ParameterObject SysUser query) { SysUser sysUser = userService.getOne(Wrappers.query(query), false); return R.ok(sysUser == null ? null : CommonConstants.SUCCESS); } /** * 删除用户信息 * @param ids 用户ID数组 * @return 操作结果 */ @SysLog("删除用户信息") @DeleteMapping @HasPermission("sys_user_del") @Operation(summary = "根据ID删除用户", description = "根据ID删除用户") public R userDel(@RequestBody Long[] ids) { return R.ok(userService.removeUserByIds(ids)); } /** * 添加用户 * @param userDto 用户信息DTO * @return 操作结果,成功返回success,失败返回false */ @SysLog("添加用户") @PostMapping @HasPermission("sys_user_add") @Operation(summary = "添加用户", description = "添加用户") public R saveUser(@RequestBody UserDTO userDto) { return R.ok(userService.saveUser(userDto)); } /** * 更新用户信息 * @param userDto 用户信息DTO对象 * @return 包含操作结果的R对象 */ @SysLog("更新用户信息") @PutMapping @HasPermission("sys_user_edit") @Operation(summary = "更新用户信息", description = "更新用户信息") public R updateUser(@Valid @RequestBody UserDTO userDto) { return R.ok(userService.updateUser(userDto)); } /** * 分页查询用户 * @param page 参数集 * @param userDTO 查询参数列表 * @return 用户集合 */ @GetMapping("/page") @Operation(summary = "分页查询用户", description = "分页查询用户") public R getUserPage(@ParameterObject Page page, @ParameterObject UserDTO userDTO) { return R.ok(userService.getUsersWithRolePage(page, userDTO)); } /** * 修改个人信息 * @param userDto 用户信息传输对象 * @return 操作结果,成功返回success,失败返回false */ @SysLog("修改个人信息") @PutMapping("/edit") @Operation(summary = "修改个人信息", description = "修改个人信息") public R updateUserInfo(@Valid @RequestBody UserDTO userDto) { return userService.updateUserInfo(userDto); } /** * 导出用户数据到Excel表格 * @param userDTO 用户查询条件 * @return 用户数据列表 */ @ResponseExcel @GetMapping("/export") @HasPermission("sys_user_export") @Operation(summary = "导出用户数据到Excel表格", description = "导出用户数据到Excel表格") public List exportUsers(UserDTO userDTO) { return userService.listUsers(userDTO); } /** * 导入用户信息 * @param excelVOList 用户Excel数据列表 * @param bindingResult 数据校验结果 * @return 导入结果 */ @PostMapping("/import") @HasPermission("sys_user_export") @Operation(summary = "导入用户信息", description = "导入用户信息") public R importUser(@RequestExcel List excelVOList, BindingResult bindingResult) { return userService.importUsers(excelVOList, bindingResult); } /** * 锁定指定用户 * @param username 用户名 * @return 操作结果 */ @PutMapping("/lock/{username}") @Operation(summary = "锁定指定用户", description = "锁定指定用户") public R lockUser(@PathVariable String username) { return userService.lockUser(username); } /** * 修改当前用户密码 * @param userDto 用户数据传输对象,包含新密码等信息 * @return 操作结果 */ @PutMapping("/password") @Operation(summary = "修改当前用户密码", description = "修改当前用户密码") public R password(@RequestBody UserDTO userDto) { String username = SecurityUtils.getUser().getUsername(); userDto.setUsername(username); return userService.changePassword(userDto); } /** * 检查密码是否符合要求 * @param password 待检查的密码 * @return 检查结果 */ @PostMapping("/check") @Operation(summary = "检查密码是否符合要求", description = "检查密码是否符合要求") public R check(String password) { return userService.checkPassword(password); } } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/mapper/SysDeptMapper.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.pig4cloud.pig.admin.api.entity.SysDept; import org.apache.ibatis.annotations.Mapper; /** * 部门管理 Mapper 接口 * * @author lengleng * @since 2018-01-20 */ @Mapper public interface SysDeptMapper extends BaseMapper { } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/mapper/SysDictItemMapper.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.pig4cloud.pig.admin.api.entity.SysDictItem; import org.apache.ibatis.annotations.Mapper; /** * 系统字典项数据访问接口 * * @author lengleng * @date 2025/05/30 */ @Mapper public interface SysDictItemMapper extends BaseMapper { } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/mapper/SysDictMapper.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.pig4cloud.pig.admin.api.entity.SysDict; import org.apache.ibatis.annotations.Mapper; /** * 字典表 Mapper 接口 * * @author lengleng * @date 2025/06/27 */ @Mapper public interface SysDictMapper extends BaseMapper { } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/mapper/SysFileMapper.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.pig4cloud.pig.admin.api.entity.SysFile; import org.apache.ibatis.annotations.Mapper; /** * 系统文件映射接口 * * @author lengleng * @date 2025/05/30 */ @Mapper public interface SysFileMapper extends BaseMapper { } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/mapper/SysLogMapper.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.pig4cloud.pig.admin.api.entity.SysLog; import org.apache.ibatis.annotations.Mapper; /** * 系统日志表 Mapper 接口 * * @author lengleng */ @Mapper public interface SysLogMapper extends BaseMapper { } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/mapper/SysMenuMapper.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.pig4cloud.pig.admin.api.entity.SysMenu; import org.apache.ibatis.annotations.Mapper; import java.util.List; /** *

* 菜单权限表 Mapper 接口 *

* * @author lengleng * @since 2017-10-29 */ @Mapper public interface SysMenuMapper extends BaseMapper { /** * 通过角色编号查询菜单 * @param roleId 角色ID * @return */ List listMenusByRoleId(Long roleId); } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/mapper/SysOauthClientDetailsMapper.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.pig4cloud.pig.admin.api.entity.SysOauthClientDetails; import org.apache.ibatis.annotations.Mapper; /** * 系统OAuth客户端详情 Mapper接口 * * @author lengleng * @date 2025/06/27 */ @Mapper public interface SysOauthClientDetailsMapper extends BaseMapper { } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/mapper/SysPostMapper.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.pig4cloud.pig.admin.api.entity.SysPost; import org.apache.ibatis.annotations.Mapper; import java.util.List; /** * 岗位信息表 Mapper 接口 * * @author lengleng * @date 2025/06/27 */ @Mapper public interface SysPostMapper extends BaseMapper { /** * 通过用户ID,查询岗位信息 * @param userId 用户id * @return 岗位信息 */ List listPostsByUserId(Long userId); } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/mapper/SysPublicParamMapper.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.pig4cloud.pig.admin.api.entity.SysPublicParam; import org.apache.ibatis.annotations.Mapper; /** * 公共参数配置 * * @author Lucky * @date 2019-04-29 */ @Mapper public interface SysPublicParamMapper extends BaseMapper { } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/mapper/SysRoleMapper.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.pig4cloud.pig.admin.api.entity.SysRole; import org.apache.ibatis.annotations.Mapper; import java.util.List; /** *

* Mapper 接口 *

* * @author lengleng * @since 2017-10-29 */ @Mapper public interface SysRoleMapper extends BaseMapper { /** * 通过用户ID查询角色信息 * @param userId 用户ID * @return 角色信息列表 */ List listRolesByUserId(Long userId); } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/mapper/SysRoleMenuMapper.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.pig4cloud.pig.admin.api.entity.SysRoleMenu; import org.apache.ibatis.annotations.Mapper; /** * 角色菜单表 Mapper 接口 * * @author lengleng * @since 2017-10-29 */ @Mapper public interface SysRoleMenuMapper extends BaseMapper { } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/mapper/SysUserMapper.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.pig4cloud.pig.admin.api.dto.UserDTO; import com.pig4cloud.pig.admin.api.entity.SysUser; import com.pig4cloud.pig.admin.api.vo.UserVO; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.util.List; /** *

* 用户表 Mapper 接口 *

* * @author lengleng * @since 2017-10-29 */ @Mapper public interface SysUserMapper extends BaseMapper { /** * 根据用户DTO获取用户VO * @param userDTO 用户查询条件DTO * @return 用户信息VO */ UserVO getUser(@Param("query") UserDTO userDTO); /** * 分页查询用户信息(含角色) * @param page 分页参数 * @param userDTO 用户查询条件 * @return 分页用户信息列表 */ IPage getUsersPage(Page page, @Param("query") UserDTO userDTO); /** * 查询用户列表 * @param userDTO 查询条件 * @return 用户VO列表 */ List listUsers(@Param("query") UserDTO userDTO); } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/mapper/SysUserPostMapper.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.admin.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.pig4cloud.pig.admin.api.entity.SysUserPost; import org.apache.ibatis.annotations.Mapper; /** *

* 用户岗位 Mapper 接口 *

* * @author fxz * @since 2022/3/19 */ @Mapper public interface SysUserPostMapper extends BaseMapper { } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/mapper/SysUserRoleMapper.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.pig4cloud.pig.admin.api.entity.SysUserRole; import org.apache.ibatis.annotations.Mapper; /** *

* 用户角色表 Mapper 接口 *

* * @author lengleng * @since 2017-10-29 */ @Mapper public interface SysUserRoleMapper extends BaseMapper { } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysDeptService.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.service; import cn.hutool.core.lang.tree.Tree; import com.baomidou.mybatisplus.extension.service.IService; import com.pig4cloud.pig.admin.api.entity.SysDept; import com.pig4cloud.pig.admin.api.vo.DeptExcelVo; import com.pig4cloud.pig.common.core.util.R; import org.springframework.validation.BindingResult; import java.util.List; /** * 部门管理服务接口 * * @author lengleng * @since 2018-01-20 */ public interface SysDeptService extends IService { /** * 查询部门树菜单 * @param deptName 部门名称 * @return 部门树结构 */ List> getDeptTree(String deptName); /** * 根据部门ID删除部门 * @param id 要删除的部门ID * @return 删除操作是否成功,成功返回true,失败返回false */ Boolean removeDeptById(Long id); /** * 导出部门Excel数据列表 * @return 部门Excel数据列表 */ List exportDepts(); /** * 导入部门数据 * @param excelVOList 部门Excel数据列表 * @param bindingResult 数据校验结果 * @return 导入结果 */ R importDept(List excelVOList, BindingResult bindingResult); /** * 获取指定部门的所有后代部门列表 * @param deptId 部门ID * @return 后代部门列表,如果不存在则返回空列表 */ List listDescendants(Long deptId); } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysDictItemService.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.service; import com.baomidou.mybatisplus.extension.service.IService; import com.pig4cloud.pig.admin.api.entity.SysDictItem; import com.pig4cloud.pig.common.core.util.R; /** * 字典项服务接口 * * @author lengleng * @date 2025/05/30 */ public interface SysDictItemService extends IService { /** * 删除字典项 * @param id 字典项ID * @return 操作结果 */ R removeDictItem(Long id); /** * 更新字典项 * @param item 需要更新的字典项 * @return 操作结果 */ R updateDictItem(SysDictItem item); } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysDictService.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.service; import com.baomidou.mybatisplus.extension.service.IService; import com.pig4cloud.pig.admin.api.entity.SysDict; import com.pig4cloud.pig.common.core.util.R; /** * 字典表服务接口 提供字典数据的增删改查及缓存同步功能 * * @author lengleng * @date 2025/05/30 */ public interface SysDictService extends IService { /** * 根据ID列表删除字典 * @param ids 要删除的字典ID数组 * @return 操作结果 */ R removeDictByIds(Long[] ids); /** * 更新字典 * @param sysDict 要更新的字典对象 * @return 操作结果 */ R updateDict(SysDict sysDict); /** * 同步字典缓存(清空缓存) * @return 操作结果 */ R syncDictCache(); } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysFileService.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.service; import com.baomidou.mybatisplus.extension.service.IService; import com.pig4cloud.pig.admin.api.entity.SysFile; import com.pig4cloud.pig.common.core.util.R; import jakarta.servlet.http.HttpServletResponse; import org.springframework.web.multipart.MultipartFile; /** * 文件管理服务接口 *

* 提供文件上传、获取、删除等操作 *

* * @author Luckly * @date 2019-06-18 17:18:42 */ public interface SysFileService extends IService { /** * 上传文件 * @param file 要上传的文件 * @return 包含文件信息的响应结果,失败时返回错误信息 */ R uploadFile(MultipartFile file); /** * 从指定存储桶中获取文件并写入HTTP响应流 * @param bucket 存储桶名称 * @param fileName 文件名 * @param response HTTP响应对象 */ void getFile(String bucket, String fileName, HttpServletResponse response); /** * 根据ID删除文件 * @param id 文件ID * @return 删除是否成功,文件不存在时返回false */ Boolean removeFile(Long id); } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysLogService.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; import com.pig4cloud.pig.admin.api.dto.SysLogDTO; import com.pig4cloud.pig.admin.api.entity.SysLog; import java.util.List; /** *

* 日志表 服务类 *

* * @author lengleng * @since 2017-11-20 */ public interface SysLogService extends IService { /** * 分页查询系统日志 * @param page 分页对象 * @param sysLog 系统日志 * @return 系统日志分页数据 */ Page getLogPage(Page page, SysLogDTO sysLog); /** * 保存日志 * @param sysLog 日志实体 * @return Boolean */ Boolean saveLog(SysLog sysLog); /** * 查询日志列表 * @param sysLog 查询条件 * @return 日志列表 */ List listLogs(SysLogDTO sysLog); } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysMenuService.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.service; import cn.hutool.core.lang.tree.Tree; import com.baomidou.mybatisplus.extension.service.IService; import com.pig4cloud.pig.admin.api.entity.SysMenu; import com.pig4cloud.pig.common.core.util.R; import java.util.List; import java.util.Set; /** * 菜单权限服务接口 *

* 提供菜单权限相关的服务方法,包括查询、删除、更新和构建菜单树等操作 *

* * @author lengleng * @date 2025/06/27 */ public interface SysMenuService extends IService { /** * 通过角色编号查询URL 权限 * @param roleId 角色ID * @return 菜单列表 */ List findMenuByRoleId(Long roleId); /** * 级联删除菜单 * @param id 菜单ID * @return 成功、失败 */ R removeMenuById(Long id); /** * 更新菜单信息 * @param sysMenu 菜单信息 * @return 成功、失败 */ Boolean updateMenuById(SysMenu sysMenu); /** * 构建树查询 * @param parentId 父级菜单ID * @param menuName 菜单名称 * @param type 类型 * @return 菜单树 */ List> getMenuTree(Long parentId, String menuName, String type); /** * 查询菜单 * @param all 全部菜单 * @param type 类型 * @param parentId 父节点ID * @return */ List> filterMenu(Set all, String type, Long parentId); } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysMobileService.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.service; import com.pig4cloud.pig.common.core.util.R; /** * 系统手机服务接口:提供手机验证码发送功能 * * @author lengleng * @date 2025/05/30 */ public interface SysMobileService { /** * 发送手机验证码 * @param mobile 手机号码 * @return 发送结果,成功返回true,失败返回false */ R sendSmsCode(String mobile); } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysOauthClientDetailsService.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; import com.pig4cloud.pig.admin.api.entity.SysOauthClientDetails; import com.pig4cloud.pig.common.core.util.R; /** * OAuth2客户端详情服务接口 * * @author lengleng * @since 2018-05-15 */ public interface SysOauthClientDetailsService extends IService { /** * 根据客户端信息更新客户端详情 * @param clientDetails 客户端详情信息 * @return 更新结果,成功返回true */ Boolean updateClientById(SysOauthClientDetails clientDetails); /** * 保存客户端信息 * @param clientDetails 客户端详细信息 * @return 操作是否成功 */ Boolean saveClient(SysOauthClientDetails clientDetails); /** * 分页查询OAuth客户端详情 * @param page 分页参数 * @param query 查询条件 * @return 分页查询结果 */ Page getClientPage(Page page, SysOauthClientDetails query); /** * 同步客户端缓存 * @return 操作结果 */ R syncClientCache(); } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysPostService.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.service; import com.baomidou.mybatisplus.extension.service.IService; import com.pig4cloud.pig.admin.api.entity.SysPost; import com.pig4cloud.pig.admin.api.vo.PostExcelVO; import com.pig4cloud.pig.common.core.util.R; import org.springframework.validation.BindingResult; import java.util.List; /** * 岗位信息表 * * @author fxz * @date 2022-03-26 12:50:43 */ public interface SysPostService extends IService { /** * 获取岗位列表用于导出Excel * @return 岗位Excel数据列表 */ List listPosts(); /** * 导入岗位信息 * @param excelVOList 岗位Excel数据列表 * @param bindingResult 数据校验结果 * @return 导入结果(R对象) */ R importPost(List excelVOList, BindingResult bindingResult); } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysPublicParamService.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.service; import com.baomidou.mybatisplus.extension.service.IService; import com.pig4cloud.pig.admin.api.entity.SysPublicParam; import com.pig4cloud.pig.common.core.util.R; /** * 系统公共参数配置表 服务类 * * @author lengleng * @date 2025/05/30 */ public interface SysPublicParamService extends IService { /** * 根据公共参数key获取对应的value值 * @param publicKey 公共参数key * @return 公共参数value,未找到时返回null */ String getParamValue(String publicKey); /** * 更新系统公共参数 * @param sysPublicParam 系统公共参数对象 * @return 操作结果 */ R updateParam(SysPublicParam sysPublicParam); /** * 根据ID删除参数 * @param publicIds 参数ID数组 * @return 删除结果 */ R removeParamByIds(Long[] publicIds); /** * 同步参数缓存 * @return 操作结果 */ R syncParamCache(); } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysRoleMenuService.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.service; import com.baomidou.mybatisplus.extension.service.IService; import com.pig4cloud.pig.admin.api.entity.SysRoleMenu; /** * 角色菜单表服务接口 * * @author lengleng * @since 2017-10-29 */ public interface SysRoleMenuService extends IService { /** * 更新角色菜单 * @param roleId 角色ID * @param menuIds 菜单ID字符串,以逗号分隔 * @return 更新是否成功 */ Boolean saveRoleMenus(Long roleId, String menuIds); } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysRoleService.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.service; import com.baomidou.mybatisplus.extension.service.IService; import com.pig4cloud.pig.admin.api.entity.SysRole; import com.pig4cloud.pig.admin.api.vo.RoleExcelVO; import com.pig4cloud.pig.admin.api.vo.RoleVO; import com.pig4cloud.pig.common.core.util.R; import org.springframework.validation.BindingResult; import java.util.List; /** * 系统角色服务接口 *

* 提供角色相关的业务功能,包括角色查询、删除、更新菜单及导入导出等操作 *

* * @author lengleng * @since 2017-10-29 */ public interface SysRoleService extends IService { /** * 通过用户ID查询角色信息 * @param userId 用户ID * @return 角色信息列表 */ List listRolesByUserId(Long userId); /** * 根据角色ID列表查询角色信息 * @param roleIdList 角色ID列表,不能为空 * @param key 缓存键值 * @return 查询到的角色列表 */ List listRolesByRoleIds(List roleIdList, String key); /** * 通过角色ID数组删除角色 * @param ids 要删除的角色ID数组 * @return 删除是否成功 */ Boolean removeRoleByIds(Long[] ids); /** * 更新角色菜单列表 * @param roleVo 包含角色和菜单列表信息的VO对象 * @return 更新是否成功 */ Boolean updateRoleMenus(RoleVO roleVo); /** * 导入角色 * @param excelVOList 角色列表 * @param bindingResult 错误信息列表 * @return 导入结果 */ R importRole(List excelVOList, BindingResult bindingResult); /** * 查询全部角色列表 * @return 角色列表,包含角色Excel视图对象 */ List listRoles(); } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysUserRoleService.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.service; import com.baomidou.mybatisplus.extension.service.IService; import com.pig4cloud.pig.admin.api.entity.SysUserRole; /** *

* 用户角色表 服务类 *

* * @author lengleng * @since 2017-10-29 */ public interface SysUserRoleService extends IService { } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysUserService.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.service; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; import com.pig4cloud.pig.admin.api.dto.RegisterUserDTO; import com.pig4cloud.pig.admin.api.dto.UserDTO; import com.pig4cloud.pig.admin.api.dto.UserInfo; import com.pig4cloud.pig.admin.api.entity.SysUser; import com.pig4cloud.pig.admin.api.vo.UserExcelVO; import com.pig4cloud.pig.admin.api.vo.UserVO; import com.pig4cloud.pig.common.core.util.R; import org.springframework.validation.BindingResult; import java.util.List; /** * 系统用户服务接口 *

* 提供用户信息查询、分页查询、增删改查等操作 * * @author lengleng * @date 2025/05/30 */ public interface SysUserService extends IService { /** * 根据用户信息查询用户详情 * @param query 用户查询条件 * @return 用户详细信息 */ R getUserInfo(UserDTO query); /** * 分页查询用户信息(包含角色信息) * @param page 分页对象 * @param userDTO 查询参数 * @return 分页结果 */ IPage getUsersWithRolePage(Page page, UserDTO userDTO); /** * 删除用户 * @param ids 用户 * @return boolean */ Boolean removeUserByIds(Long[] ids); /** * 更新当前用户基本信息 * @param userDto 用户信息 * @return Boolean */ R updateUserInfo(UserDTO userDto); /** * 更新指定用户信息 * @param userDto 用户信息DTO对象 * @return 更新是否成功 */ Boolean updateUser(UserDTO userDto); /** * 通过ID查询用户信息 * @param id 用户ID * @return 用户信息 */ UserVO getUserById(Long id); /** * 保存用户信息 * @param userDto DTO 对象 * @return success/fail */ Boolean saveUser(UserDTO userDto); /** * 查询全部的用户 * @param userDTO 查询条件 * @return list */ List listUsers(UserDTO userDTO); /** * excel 导入用户 * @param excelVOList excel 列表数据 * @param bindingResult 错误数据 * @return ok fail */ R importUsers(List excelVOList, BindingResult bindingResult); /** * 注册用户 * @param userDto 用户信息 * @return success/false */ R registerUser(RegisterUserDTO userDto); /** * 锁定用户 * @param username 用户名 * @return 包含操作结果的R对象,true表示锁定成功 */ R lockUser(String username); /** * 修改用户密码 * @param userDto 包含用户信息的DTO对象 * @return 操作结果 */ R changePassword(UserDTO userDto); /** * 校验密码 * @param password 待校验的密码明文 * @return 校验结果 */ R checkPassword(String password); } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysDeptServiceImpl.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.service.impl; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.BindingResult; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.pig4cloud.pig.admin.api.entity.SysDept; import com.pig4cloud.pig.admin.api.vo.DeptExcelVo; import com.pig4cloud.pig.admin.mapper.SysDeptMapper; import com.pig4cloud.pig.admin.service.SysDeptService; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.plugin.excel.vo.ErrorMessage; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.tree.Tree; import cn.hutool.core.lang.tree.TreeNode; import cn.hutool.core.lang.tree.TreeUtil; import cn.hutool.core.util.StrUtil; import lombok.AllArgsConstructor; /** * 部门管理服务实现类 * * @author lengleng * @date 2025/05/30 * @since 2018-01-20 */ @Service @AllArgsConstructor public class SysDeptServiceImpl extends ServiceImpl implements SysDeptService { private final SysDeptMapper deptMapper; /** * 根据部门ID删除部门(包含级联删除子部门) * @param id 要删除的部门ID * @return 删除操作是否成功,始终返回true * @throws Exception 事务执行过程中可能抛出的异常 */ @Override @Transactional(rollbackFor = Exception.class) public Boolean removeDeptById(Long id) { // 级联删除部门 List idList = this.listDescendants(id).stream().map(SysDept::getDeptId).toList(); Optional.ofNullable(idList).filter(CollUtil::isNotEmpty).ifPresent(this::removeByIds); return Boolean.TRUE; } /** * 查询部门树结构 * @param deptName 部门名称(模糊查询) * @return 部门树结构列表,模糊查询时返回平铺列表 */ @Override public List> getDeptTree(String deptName) { // 查询全部部门 List deptAllList = deptMapper .selectList(Wrappers.lambdaQuery().like(StrUtil.isNotBlank(deptName), SysDept::getName, deptName)); // 权限内部门 List> collect = deptAllList.stream() .filter(dept -> dept.getDeptId().intValue() != dept.getParentId()) .sorted(Comparator.comparingInt(SysDept::getSortOrder)) .map(dept -> { TreeNode treeNode = new TreeNode(); treeNode.setId(dept.getDeptId()); treeNode.setParentId(dept.getParentId()); treeNode.setName(dept.getName()); treeNode.setWeight(dept.getSortOrder()); // 有权限不返回标识 Map extra = new HashMap<>(8); extra.put(SysDept.Fields.createTime, dept.getCreateTime()); treeNode.setExtra(extra); return treeNode; }) .toList(); // 模糊查询 不组装树结构 直接返回 表格方便编辑 if (StrUtil.isNotBlank(deptName)) { return collect.stream().map(node -> { Tree tree = new Tree<>(); tree.putAll(node.getExtra()); BeanUtils.copyProperties(node, tree); return tree; }).toList(); } return TreeUtil.build(collect, 0L); } /** * 导出部门列表为Excel视图对象列表 * @return 部门Excel视图对象列表,包含部门名称、父部门名称和排序号 */ @Override public List exportDepts() { List list = this.list(); List deptExcelVos = list.stream().map(item -> { DeptExcelVo deptExcelVo = new DeptExcelVo(); deptExcelVo.setName(item.getName()); Optional first = this.list() .stream() .filter(it -> item.getParentId().equals(it.getDeptId())) .map(SysDept::getName) .findFirst(); deptExcelVo.setParentName(first.orElse("根部门")); deptExcelVo.setSortOrder(item.getSortOrder()); return deptExcelVo; }).toList(); return deptExcelVos; } /** * 导入部门信息 * @param excelVOList 部门Excel数据列表 * @param bindingResult 数据校验结果 * @return 导入结果,包含错误信息或成功信息 */ @Override public R importDept(List excelVOList, BindingResult bindingResult) { List errorMessageList = (List) bindingResult.getTarget(); List deptList = this.list(); for (DeptExcelVo item : excelVOList) { Set errorMsg = new HashSet<>(); boolean exsitUsername = deptList.stream().anyMatch(sysDept -> item.getName().equals(sysDept.getName())); if (exsitUsername) { errorMsg.add("部门名称已经存在"); } SysDept one = this.getOne(Wrappers.lambdaQuery().eq(SysDept::getName, item.getParentName())); if (item.getParentName().equals("根部门")) { one = new SysDept(); one.setDeptId(0L); } if (one == null) { errorMsg.add("上级部门不存在"); } if (CollUtil.isEmpty(errorMsg)) { SysDept sysDept = new SysDept(); sysDept.setName(item.getName()); sysDept.setParentId(one.getDeptId()); sysDept.setSortOrder(item.getSortOrder()); baseMapper.insert(sysDept); } else { // 数据不合法情况 errorMessageList.add(new ErrorMessage(item.getLineNum(), errorMsg)); } } if (CollUtil.isNotEmpty(errorMessageList)) { return R.failed(errorMessageList); } return R.ok(null, "部门导入成功"); } /** * 查询部门及其所有子部门 * @param deptId 目标部门ID * @return 包含目标部门及其所有子部门的列表 */ @Override public List listDescendants(Long deptId) { // 查询全部部门 List allDeptList = baseMapper.selectList(Wrappers.emptyWrapper()); // 递归查询所有子节点 List resDeptList = new ArrayList<>(); recursiveDept(allDeptList, deptId, resDeptList); // 添加当前节点 resDeptList.addAll(allDeptList.stream().filter(sysDept -> deptId.equals(sysDept.getDeptId())).toList()); return resDeptList; } /** * 递归查询所有子节点 * @param allDeptList 所有部门列表 * @param parentId 父部门ID * @param resDeptList 结果集合 */ private void recursiveDept(List allDeptList, Long parentId, List resDeptList) { // 使用 Stream API 进行筛选和遍历 allDeptList.stream().filter(sysDept -> sysDept.getParentId().equals(parentId)).forEach(sysDept -> { resDeptList.add(sysDept); recursiveDept(allDeptList, sysDept.getDeptId(), resDeptList); }); } } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysDictItemServiceImpl.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.pig4cloud.pig.admin.api.entity.SysDict; import com.pig4cloud.pig.admin.api.entity.SysDictItem; import com.pig4cloud.pig.admin.mapper.SysDictItemMapper; import com.pig4cloud.pig.admin.service.SysDictItemService; import com.pig4cloud.pig.admin.service.SysDictService; import com.pig4cloud.pig.common.core.constant.CacheConstants; import com.pig4cloud.pig.common.core.constant.enums.DictTypeEnum; import com.pig4cloud.pig.common.core.exception.ErrorCodes; import com.pig4cloud.pig.common.core.util.MsgUtils; import com.pig4cloud.pig.common.core.util.R; import lombok.AllArgsConstructor; import org.springframework.cache.annotation.CacheEvict; import org.springframework.stereotype.Service; /** * 字典项服务实现类 * * @author lengleng * @date 2025/05/30 */ @Service @AllArgsConstructor public class SysDictItemServiceImpl extends ServiceImpl implements SysDictItemService { private final SysDictService dictService; /** * 删除字典项 * @param id 字典项ID * @return 操作结果 * @see R */ @Override @CacheEvict(value = CacheConstants.DICT_DETAILS, allEntries = true) public R removeDictItem(Long id) { // 根据ID查询字典ID SysDictItem dictItem = this.getById(id); SysDict dict = dictService.getById(dictItem.getDictId()); // 系统内置 if (DictTypeEnum.SYSTEM.getType().equals(dict.getSystemFlag())) { return R.failed(MsgUtils.getMessage(ErrorCodes.SYS_DICT_DELETE_SYSTEM)); } return R.ok(this.removeById(id)); } /** * 更新字典项 * @param item 需要更新的字典项 * @return 操作结果,包含成功或失败信息 * @see R */ @Override @CacheEvict(value = CacheConstants.DICT_DETAILS, key = "#item.dictType") public R updateDictItem(SysDictItem item) { // 查询字典 SysDict dict = dictService.getById(item.getDictId()); // 系统内置 if (DictTypeEnum.SYSTEM.getType().equals(dict.getSystemFlag())) { return R.failed(MsgUtils.getMessage(ErrorCodes.SYS_DICT_UPDATE_SYSTEM)); } return R.ok(this.updateById(item)); } } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysDictServiceImpl.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.service.impl; import java.util.List; import org.springframework.cache.annotation.CacheEvict; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.pig4cloud.pig.admin.api.entity.SysDict; import com.pig4cloud.pig.admin.api.entity.SysDictItem; import com.pig4cloud.pig.admin.mapper.SysDictItemMapper; import com.pig4cloud.pig.admin.mapper.SysDictMapper; import com.pig4cloud.pig.admin.service.SysDictService; import com.pig4cloud.pig.common.core.constant.CacheConstants; import com.pig4cloud.pig.common.core.constant.enums.DictTypeEnum; import com.pig4cloud.pig.common.core.exception.ErrorCodes; import com.pig4cloud.pig.common.core.util.MsgUtils; import com.pig4cloud.pig.common.core.util.R; import cn.hutool.core.collection.CollUtil; import lombok.AllArgsConstructor; /** * 系统字典服务实现类 * * @author lengleng * @date 2025/05/30 */ @Service @AllArgsConstructor public class SysDictServiceImpl extends ServiceImpl implements SysDictService { private final SysDictItemMapper dictItemMapper; /** * 根据ID删除字典 * @param ids 字典ID数组 * @return 操作结果 */ @Override @Transactional(rollbackFor = Exception.class) @CacheEvict(value = CacheConstants.DICT_DETAILS, allEntries = true) public R removeDictByIds(Long[] ids) { List dictIdList = baseMapper.selectByIds(CollUtil.toList(ids)) .stream() .filter(sysDict -> !sysDict.getSystemFlag().equals(DictTypeEnum.SYSTEM.getType()))// 系统内置类型不删除 .map(SysDict::getId) .toList(); baseMapper.deleteByIds(dictIdList); dictItemMapper.delete(Wrappers.lambdaQuery().in(SysDictItem::getDictId, dictIdList)); return R.ok(); } /** * 更新字典数据 * @param dict 字典对象 * @return 操作结果 * @see R 返回结果封装类 */ @Override @CacheEvict(value = CacheConstants.DICT_DETAILS, key = "#dict.dictType") public R updateDict(SysDict dict) { SysDict sysDict = this.getById(dict.getId()); // 系统内置 if (DictTypeEnum.SYSTEM.getType().equals(sysDict.getSystemFlag())) { return R.failed(MsgUtils.getMessage(ErrorCodes.SYS_DICT_UPDATE_SYSTEM)); } this.updateById(dict); return R.ok(dict); } /** * 同步字典缓存(清空缓存) * @return 操作结果 */ @Override @CacheEvict(value = CacheConstants.DICT_DETAILS, allEntries = true) public R syncDictCache() { return R.ok(); } } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysFileServiceImpl.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.service.impl; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.pig4cloud.pig.admin.api.entity.SysFile; import com.pig4cloud.pig.admin.mapper.SysFileMapper; import com.pig4cloud.pig.admin.service.SysFileService; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.file.core.FileProperties; import com.pig4cloud.pig.common.file.core.FileTemplate; import jakarta.servlet.http.HttpServletResponse; import lombok.AllArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import java.io.InputStream; import java.util.HashMap; import java.util.Map; import java.util.Objects; /** * 文件管理 * * @author Luckly * @date 2019-06-18 17:18:42 */ @Slf4j @Service @AllArgsConstructor public class SysFileServiceImpl extends ServiceImpl implements SysFileService { private final FileTemplate fileTemplate; private final FileProperties properties; /** * 上传文件 * @param file 要上传的文件 * @return 包含文件信息的响应结果,失败时返回错误信息 * @throws Exception 文件上传过程中可能出现的异常 */ @Override public R uploadFile(MultipartFile file) { String fileName = IdUtil.simpleUUID() + StrUtil.DOT + FileUtil.extName(file.getOriginalFilename()); Map resultMap = new HashMap<>(4); resultMap.put(SysFile.Fields.bucketName, properties.getBucketName()); resultMap.put(SysFile.Fields.fileName, fileName); resultMap.put("url", String.format("/admin/sys-file/%s/%s", properties.getBucketName(), fileName)); try (InputStream inputStream = file.getInputStream()) { fileTemplate.putObject(properties.getBucketName(), fileName, inputStream, file.getContentType()); // 文件管理数据记录,收集管理追踪文件 fileLog(file, fileName); } catch (Exception e) { log.error("上传失败", e); return R.failed(e.getLocalizedMessage()); } return R.ok(resultMap); } /** * 从指定存储桶中获取文件并写入HTTP响应流 * @param bucket 存储桶名称 * @param fileName 文件名 * @param response HTTP响应对象 */ @Override public void getFile(String bucket, String fileName, HttpServletResponse response) { try (InputStream inputStream = (InputStream) fileTemplate.getObject(bucket, fileName)) { response.setContentType("application/octet-stream; charset=UTF-8"); response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + URLUtil.encode(fileName)); IoUtil.copy(inputStream, response.getOutputStream()); } catch (Exception e) { log.error("文件读取异常: {}", e.getLocalizedMessage()); } } /** * 根据ID删除文件 * @param id 文件ID * @return 删除是否成功,文件不存在时返回false * @throws Exception 删除过程中可能抛出的异常 */ @Override @SneakyThrows @Transactional(rollbackFor = Exception.class) public Boolean removeFile(Long id) { SysFile file = this.getById(id); if (Objects.isNull(file)) { return Boolean.FALSE; } fileTemplate.removeObject(properties.getBucketName(), file.getFileName()); return this.removeById(id); } /** * 记录文件管理数据 * @param file 上传文件 * @param fileName 文件名 */ private void fileLog(MultipartFile file, String fileName) { SysFile sysFile = new SysFile(); sysFile.setFileName(fileName); sysFile.setOriginal(file.getOriginalFilename()); sysFile.setFileSize(file.getSize()); sysFile.setType(FileUtil.extName(file.getOriginalFilename())); sysFile.setBucketName(properties.getBucketName()); this.save(sysFile); } } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysLogServiceImpl.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.service.impl; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.pig4cloud.pig.admin.api.dto.SysLogDTO; import com.pig4cloud.pig.admin.api.entity.SysLog; import com.pig4cloud.pig.admin.mapper.SysLogMapper; import com.pig4cloud.pig.admin.service.SysLogService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; /** * 系统日志服务实现类 * * @author lengleng * @date 2025/05/30 * @since 2017-11-20 */ @Service public class SysLogServiceImpl extends ServiceImpl implements SysLogService { /** * 分页查询系统日志 * @param page 分页参数 * @param sysLog 日志查询条件 * @return 分页结果 */ @Override public Page getLogPage(Page page, SysLogDTO sysLog) { return baseMapper.selectPage(page, buildQuery(sysLog)); } /** * 保存日志 * @param sysLog 日志对象 * @return 保存成功返回true */ @Override @Transactional(rollbackFor = Exception.class) public Boolean saveLog(SysLog sysLog) { baseMapper.insert(sysLog); return Boolean.TRUE; } /** * 查询日志列表 * @param sysLog 查询条件DTO对象 * @return 日志列表 */ @Override public List listLogs(SysLogDTO sysLog) { return baseMapper.selectList(buildQuery(sysLog)); } /** * 构建查询条件 * @param sysLog 前端查询条件DTO * @return 构建好的LambdaQueryWrapper对象 */ private LambdaQueryWrapper buildQuery(SysLogDTO sysLog) { LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); if (StrUtil.isNotBlank(sysLog.getLogType())) { wrapper.eq(SysLog::getLogType, sysLog.getLogType()); } if (ArrayUtil.isNotEmpty(sysLog.getCreateTime())) { wrapper.ge(SysLog::getCreateTime, sysLog.getCreateTime()[0]) .le(SysLog::getCreateTime, sysLog.getCreateTime()[1]); } return wrapper; } } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysMenuServiceImpl.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.service.impl; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; import org.springframework.beans.BeanUtils; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.pig4cloud.pig.admin.api.entity.SysMenu; import com.pig4cloud.pig.admin.api.entity.SysRoleMenu; import com.pig4cloud.pig.admin.mapper.SysMenuMapper; import com.pig4cloud.pig.admin.mapper.SysRoleMenuMapper; import com.pig4cloud.pig.admin.service.SysMenuService; import com.pig4cloud.pig.common.core.constant.CacheConstants; import com.pig4cloud.pig.common.core.constant.CommonConstants; import com.pig4cloud.pig.common.core.constant.enums.MenuTypeEnum; import com.pig4cloud.pig.common.core.exception.ErrorCodes; import com.pig4cloud.pig.common.core.util.MsgUtils; import com.pig4cloud.pig.common.core.util.R; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.tree.Tree; import cn.hutool.core.lang.tree.TreeNode; import cn.hutool.core.lang.tree.TreeUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.StrUtil; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; /** * 菜单权限表服务实现类 * * @author lengleng * @date 2025/05/30 */ @Service @AllArgsConstructor public class SysMenuServiceImpl extends ServiceImpl implements SysMenuService { private final SysRoleMenuMapper sysRoleMenuMapper; /** * 根据角色ID查询菜单列表 * @param roleId 角色ID * @return 菜单列表,如果结果为空则不会被缓存 * @see CacheConstants#MENU_DETAILS */ @Override @Cacheable(value = CacheConstants.MENU_DETAILS, key = "#roleId", unless = "#result.isEmpty()") public List findMenuByRoleId(Long roleId) { return baseMapper.listMenusByRoleId(roleId); } /** * 根据ID删除菜单 * @param id 菜单ID * @return 删除结果 * @throws Exception 事务回滚时抛出异常 */ @Override @Transactional(rollbackFor = Exception.class) @CacheEvict(value = CacheConstants.MENU_DETAILS, allEntries = true) public R removeMenuById(Long id) { // 查询父节点为当前节点的节点 List menuList = this.list(Wrappers.query().lambda().eq(SysMenu::getParentId, id)); if (CollUtil.isNotEmpty(menuList)) { return R.failed(MsgUtils.getMessage(ErrorCodes.SYS_MENU_DELETE_EXISTING)); } sysRoleMenuMapper.delete(Wrappers.query().lambda().eq(SysRoleMenu::getMenuId, id)); // 删除当前菜单及其子菜单 return R.ok(this.removeById(id)); } /** * 根据ID更新菜单信息 * @param sysMenu 菜单实体对象 * @return 更新是否成功 */ @Override @CacheEvict(value = CacheConstants.MENU_DETAILS, allEntries = true) public Boolean updateMenuById(SysMenu sysMenu) { return this.updateById(sysMenu); } /** * 构建菜单树结构 * @param parentId 父节点ID,为空时使用默认根节点 * @param menuName 菜单名称,支持模糊查询 * @param type 菜单类型 * @return 菜单树结构列表,模糊查询时返回平铺列表 */ @Override public List> getMenuTree(Long parentId, String menuName, String type) { Long parent = parentId == null ? CommonConstants.MENU_TREE_ROOT_ID : parentId; List> collect = baseMapper .selectList(Wrappers.lambdaQuery() .like(StrUtil.isNotBlank(menuName), SysMenu::getName, menuName) .eq(StrUtil.isNotBlank(type), SysMenu::getMenuType, type) .orderByAsc(SysMenu::getSortOrder)) .stream() .map(getNodeFunction()) .toList(); // 模糊查询 不组装树结构 直接返回 表格方便编辑 if (StrUtil.isNotBlank(menuName)) { return collect.stream().map(node -> { Tree tree = new Tree<>(); tree.putAll(node.getExtra()); BeanUtils.copyProperties(node, tree); return tree; }).toList(); } return TreeUtil.build(collect, parent); } /** * 根据类型和父节点ID过滤菜单并构建树形结构 * @param all 全部菜单集合 * @param type 菜单类型 * @param parentId 父节点ID,为空时使用根节点ID * @return 构建好的菜单树形结构列表 */ @Override public List> filterMenu(Set all, String type, Long parentId) { List> collect = all.stream().filter(menuTypePredicate(type)).map(getNodeFunction()).toList(); Long parent = parentId == null ? CommonConstants.MENU_TREE_ROOT_ID : parentId; return TreeUtil.build(collect, parent); } /** * 获取将SysMenu转换为TreeNode的函数 * @return 转换函数,将SysMenu对象转换为TreeNode对象 */ @NotNull private Function> getNodeFunction() { return menu -> { TreeNode node = new TreeNode<>(); node.setId(menu.getMenuId()); node.setName(menu.getName()); node.setParentId(menu.getParentId()); node.setWeight(menu.getSortOrder()); // 扩展属性 Map extra = new HashMap<>(); extra.put(SysMenu.Fields.path, menu.getPath()); extra.put(SysMenu.Fields.menuType, menu.getMenuType()); extra.put(SysMenu.Fields.permission, menu.getPermission()); extra.put(SysMenu.Fields.sortOrder, menu.getSortOrder()); // 适配 vue3 Map meta = new HashMap<>(); meta.put("title", menu.getName()); meta.put("isLink", menu.getPath() != null && menu.getPath().startsWith("http") ? menu.getPath() : ""); meta.put("isHide", !BooleanUtil.toBooleanObject(menu.getVisible())); meta.put("isKeepAlive", BooleanUtil.toBooleanObject(menu.getKeepAlive())); meta.put("isAffix", false); meta.put("isIframe", BooleanUtil.toBooleanObject(menu.getEmbedded())); meta.put(SysMenu.Fields.icon, menu.getIcon()); // 增加英文 meta.put(SysMenu.Fields.enName, menu.getEnName()); extra.put("meta", meta); node.setExtra(extra); return node; }; } /** * menu 类型断言 * @param type 类型 * @return Predicate */ private Predicate menuTypePredicate(String type) { return vo -> { if (MenuTypeEnum.TOP_MENU.getDescription().equals(type)) { return MenuTypeEnum.TOP_MENU.getType().equals(vo.getMenuType()); } // 其他查询 左侧 + 顶部 return !MenuTypeEnum.BUTTON.getType().equals(vo.getMenuType()); }; } } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysMobileServiceImpl.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.service.impl; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.RandomUtil; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.pig4cloud.pig.admin.api.entity.SysUser; import com.pig4cloud.pig.admin.mapper.SysUserMapper; import com.pig4cloud.pig.admin.service.SysMobileService; import com.pig4cloud.pig.common.core.constant.CacheConstants; import com.pig4cloud.pig.common.core.constant.SecurityConstants; import com.pig4cloud.pig.common.core.exception.ErrorCodes; import com.pig4cloud.pig.common.core.util.MsgUtils; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.core.util.RedisUtils; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.dromara.sms4j.api.SmsBlend; import org.dromara.sms4j.api.entity.SmsResponse; import org.dromara.sms4j.core.factory.SmsFactory; import org.springframework.stereotype.Service; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.TimeUnit; /** * 手机登录相关业务实现类 * * @author lengleng * @date 2025/05/30 */ @Slf4j @Service @AllArgsConstructor public class SysMobileServiceImpl implements SysMobileService { private final SysUserMapper userMapper; /** * 发送手机验证码 * @param mobile 手机号码 * @return 返回操作结果,包含验证码发送状态及验证码信息 */ @Override public R sendSmsCode(String mobile) { List userList = userMapper .selectList(Wrappers.query().lambda().eq(SysUser::getPhone, mobile)); if (CollUtil.isEmpty(userList)) { log.info("手机号未注册:{}", mobile); return R.ok(Boolean.FALSE, MsgUtils.getMessage(ErrorCodes.SYS_APP_PHONE_UNREGISTERED, mobile)); } String cacheKey = CacheConstants.DEFAULT_CODE_KEY + mobile; String codeObj = RedisUtils.get(cacheKey); if (codeObj != null) { log.info("手机号验证码未过期:{},{}", mobile, codeObj); return R.ok(Boolean.FALSE, MsgUtils.getMessage(ErrorCodes.SYS_APP_SMS_OFTEN)); } String code = RandomUtil.randomNumbers(Integer.parseInt(SecurityConstants.CODE_SIZE)); log.info("手机号生成验证码成功:{},{}", mobile, code); RedisUtils.set(cacheKey, code, SecurityConstants.CODE_TIME, TimeUnit.SECONDS); // 集成短信服务发送验证码 SmsBlend smsBlend = SmsFactory.getSmsBlend(); if (Objects.isNull(smsBlend)) { return R.ok(Boolean.FALSE, MsgUtils.getMessage(ErrorCodes.SYS_SMS_BLEND_UNREGISTERED)); } SmsResponse smsResponse = smsBlend.sendMessage(mobile, new LinkedHashMap<>(Map.of("code", code))); log.debug("调用短信服务发送验证码结果:{}", smsResponse); return R.ok(Boolean.TRUE); } } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysOauthClientDetailsServiceImpl.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.service.impl; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.pig4cloud.pig.admin.api.entity.SysOauthClientDetails; import com.pig4cloud.pig.admin.mapper.SysOauthClientDetailsMapper; import com.pig4cloud.pig.admin.service.SysOauthClientDetailsService; import com.pig4cloud.pig.common.core.constant.CacheConstants; import com.pig4cloud.pig.common.core.util.R; import lombok.RequiredArgsConstructor; import org.springframework.cache.annotation.CacheEvict; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /** * OAuth2客户端详情服务实现类 * * @author lengleng * @since 2018-05-15 */ @Service @RequiredArgsConstructor public class SysOauthClientDetailsServiceImpl extends ServiceImpl implements SysOauthClientDetailsService { /** * 根据客户端信息更新客户端详情 * @param clientDetails 客户端详情信息 * @return 更新结果,成功返回true */ @Override @CacheEvict(value = CacheConstants.CLIENT_DETAILS_KEY, key = "#clientDetails.clientId") @Transactional(rollbackFor = Exception.class) public Boolean updateClientById(SysOauthClientDetails clientDetails) { this.insertOrUpdate(clientDetails); return Boolean.TRUE; } /** * 保存客户端信息 * @param clientDetails 客户端详细信息 * @return 操作是否成功 */ @Override @Transactional(rollbackFor = Exception.class) public Boolean saveClient(SysOauthClientDetails clientDetails) { this.insertOrUpdate(clientDetails); return Boolean.TRUE; } /** * 插入或更新客户端对象 * @param clientDetails 客户端详情对象 * @return 更新后的客户端详情对象 */ private SysOauthClientDetails insertOrUpdate(SysOauthClientDetails clientDetails) { // 更新数据库 saveOrUpdate(clientDetails); return clientDetails; } /** * 分页查询OAuth客户端详情 * @param page 分页参数 * @param query 查询条件 * @return 分页查询结果 */ @Override public Page getClientPage(Page page, SysOauthClientDetails query) { return baseMapper.selectPage(page, Wrappers.query(query)); } /** * 同步客户端缓存 * @return 操作结果 */ @Override @CacheEvict(value = CacheConstants.CLIENT_DETAILS_KEY, allEntries = true) public R syncClientCache() { return R.ok(); } } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysPostServiceImpl.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.service.impl; import java.util.HashSet; import java.util.List; import java.util.Set; import org.springframework.stereotype.Service; import org.springframework.validation.BindingResult; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.pig4cloud.pig.admin.api.entity.SysPost; import com.pig4cloud.pig.admin.api.vo.PostExcelVO; import com.pig4cloud.pig.admin.mapper.SysPostMapper; import com.pig4cloud.pig.admin.service.SysPostService; import com.pig4cloud.pig.common.core.exception.ErrorCodes; import com.pig4cloud.pig.common.core.util.MsgUtils; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.plugin.excel.vo.ErrorMessage; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; /** * 岗位信息表服务实现类 * * @author lengleng * @date 2025/05/30 */ @Service public class SysPostServiceImpl extends ServiceImpl implements SysPostService { /** * 导入岗位 * @param excelVOList 岗位列表 * @param bindingResult 错误信息列表 * @return ok fail */ @Override public R importPost(List excelVOList, BindingResult bindingResult) { // 通用校验获取失败的数据 List errorMessageList = (List) bindingResult.getTarget(); // 个性化校验逻辑 List postList = this.list(); // 执行数据插入操作 组装 PostDto for (PostExcelVO excel : excelVOList) { Set errorMsg = new HashSet<>(); // 检验岗位名称或者岗位编码是否存在 boolean existPost = postList.stream() .anyMatch(post -> excel.getPostName().equals(post.getPostName()) || excel.getPostCode().equals(post.getPostCode())); if (existPost) { errorMsg.add(MsgUtils.getMessage(ErrorCodes.SYS_POST_NAMEORCODE_EXISTING, excel.getPostName(), excel.getPostCode())); } // 数据合法情况 if (CollUtil.isEmpty(errorMsg)) { insertExcelPost(excel); } else { // 数据不合法 errorMessageList.add(new ErrorMessage(excel.getLineNum(), errorMsg)); } } if (CollUtil.isNotEmpty(errorMessageList)) { return R.failed(errorMessageList); } return R.ok(); } /** * 获取岗位列表并转换为Excel导出对象 * @return 岗位Excel导出对象列表 */ @Override public List listPosts() { List postList = this.list(Wrappers.emptyWrapper()); // 转换成execl 对象输出 return postList.stream().map(post -> { PostExcelVO postExcelVO = new PostExcelVO(); BeanUtil.copyProperties(post, postExcelVO); return postExcelVO; }).toList(); } /** * 插入Excel中的岗位数据 * @param excel 包含岗位信息的Excel数据对象 */ private void insertExcelPost(PostExcelVO excel) { SysPost sysPost = new SysPost(); BeanUtil.copyProperties(excel, sysPost); this.save(sysPost); } } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysPublicParamServiceImpl.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.admin.service.impl; import java.util.List; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.pig4cloud.pig.admin.api.entity.SysPublicParam; import com.pig4cloud.pig.admin.mapper.SysPublicParamMapper; import com.pig4cloud.pig.admin.service.SysPublicParamService; import com.pig4cloud.pig.common.core.constant.CacheConstants; import com.pig4cloud.pig.common.core.constant.enums.DictTypeEnum; import com.pig4cloud.pig.common.core.exception.ErrorCodes; import com.pig4cloud.pig.common.core.util.MsgUtils; import com.pig4cloud.pig.common.core.util.R; import cn.hutool.core.collection.CollUtil; import lombok.AllArgsConstructor; /** * 系统公共参数服务实现类 * * @author lengleng * @date 2025/05/30 */ @Service @AllArgsConstructor public class SysPublicParamServiceImpl extends ServiceImpl implements SysPublicParamService { /** * 根据公共参数key获取对应的value值 * @param publicKey 公共参数key * @return 公共参数value,未找到时返回null * @Cacheable 使用缓存,缓存名称为PARAMS_DETAILS,key为publicKey,当结果为null时不缓存 */ @Override @Cacheable(value = CacheConstants.PARAMS_DETAILS, key = "#publicKey", unless = "#result == null ") public String getParamValue(String publicKey) { SysPublicParam sysPublicParam = this.baseMapper .selectOne(Wrappers.lambdaQuery().eq(SysPublicParam::getPublicKey, publicKey)); if (sysPublicParam != null) { return sysPublicParam.getPublicValue(); } return null; } /** * 更新系统公共参数 * @param sysPublicParam 系统公共参数对象 * @return 操作结果 * @see R */ @Override @CacheEvict(value = CacheConstants.PARAMS_DETAILS, key = "#sysPublicParam.publicKey") public R updateParam(SysPublicParam sysPublicParam) { SysPublicParam param = this.getById(sysPublicParam.getPublicId()); // 系统内置 if (DictTypeEnum.SYSTEM.getType().equals(param.getSystemFlag())) { return R.failed(MsgUtils.getMessage(ErrorCodes.SYS_PARAM_DELETE_SYSTEM)); } return R.ok(this.updateById(sysPublicParam)); } /** * 根据ID列表删除参数 * @param publicIds 参数ID数组 * @return 操作结果 * @see CacheConstants#PARAMS_DETAILS 缓存常量 */ @Override @CacheEvict(value = CacheConstants.PARAMS_DETAILS, allEntries = true) public R removeParamByIds(Long[] publicIds) { List idList = this.baseMapper.selectByIds(CollUtil.toList(publicIds)) .stream() .filter(p -> !p.getSystemFlag().equals(DictTypeEnum.SYSTEM.getType()))// 系统内置的跳过不能删除 .map(SysPublicParam::getPublicId) .toList(); return R.ok(this.removeBatchByIds(idList)); } /** * 同步参数缓存 * @return 操作结果 */ @Override @CacheEvict(value = CacheConstants.PARAMS_DETAILS, allEntries = true) public R syncParamCache() { return R.ok(); } } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysRoleMenuServiceImpl.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.service.impl; import java.util.Arrays; import java.util.List; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CacheEvict; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.pig4cloud.pig.admin.api.entity.SysRoleMenu; import com.pig4cloud.pig.admin.mapper.SysRoleMenuMapper; import com.pig4cloud.pig.admin.service.SysRoleMenuService; import com.pig4cloud.pig.common.core.constant.CacheConstants; import cn.hutool.core.util.StrUtil; import lombok.AllArgsConstructor; /** * 角色菜单表服务实现类 * * @author lengleng * @date 2025/05/30 */ @Service @AllArgsConstructor public class SysRoleMenuServiceImpl extends ServiceImpl implements SysRoleMenuService { private final CacheManager cacheManager; /** * @param roleId 角色 * @param menuIds 菜单ID拼成的字符串,每个id之间根据逗号分隔 * @return */ @Override @Transactional(rollbackFor = Exception.class) @CacheEvict(value = CacheConstants.MENU_DETAILS, key = "#roleId") public Boolean saveRoleMenus(Long roleId, String menuIds) { this.remove(Wrappers.query().lambda().eq(SysRoleMenu::getRoleId, roleId)); if (StrUtil.isBlank(menuIds)) { return Boolean.TRUE; } List roleMenuList = Arrays.stream(menuIds.split(StrUtil.COMMA)).map(menuId -> { SysRoleMenu roleMenu = new SysRoleMenu(); roleMenu.setRoleId(roleId); roleMenu.setMenuId(Long.valueOf(menuId)); return roleMenu; }).toList(); // 清空userinfo cacheManager.getCache(CacheConstants.USER_DETAILS).clear(); this.saveBatch(roleMenuList); return Boolean.TRUE; } } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysRoleServiceImpl.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.service.impl; import java.util.HashSet; import java.util.List; import java.util.Set; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.BindingResult; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.pig4cloud.pig.admin.api.entity.SysRole; import com.pig4cloud.pig.admin.api.entity.SysRoleMenu; import com.pig4cloud.pig.admin.api.vo.RoleExcelVO; import com.pig4cloud.pig.admin.api.vo.RoleVO; import com.pig4cloud.pig.admin.mapper.SysRoleMapper; import com.pig4cloud.pig.admin.service.SysRoleMenuService; import com.pig4cloud.pig.admin.service.SysRoleService; import com.pig4cloud.pig.common.core.constant.CacheConstants; import com.pig4cloud.pig.common.core.exception.ErrorCodes; import com.pig4cloud.pig.common.core.util.MsgUtils; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.plugin.excel.vo.ErrorMessage; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import lombok.AllArgsConstructor; /** * 系统角色服务实现类 * * @author lengleng * @since 2017-10-29 */ @Service @AllArgsConstructor public class SysRoleServiceImpl extends ServiceImpl implements SysRoleService { private SysRoleMenuService roleMenuService; /** * 通过用户ID查询角色信息 * @param userId 用户ID * @return 角色信息列表 */ @Override public List listRolesByUserId(Long userId) { return baseMapper.listRolesByUserId(userId); } /** * 根据角色ID查询角色列表 * @param roleIdList 角色ID列表 * @param key 缓存key * @return 角色列表 */ @Override @Cacheable(value = CacheConstants.ROLE_DETAILS, key = "#key", unless = "#result.isEmpty()") public List listRolesByRoleIds(List roleIdList, String key) { return baseMapper.selectByIds(roleIdList); } /** * 通过角色ID删除角色并清空角色菜单缓存 * @param ids 角色ID数组 * @return 删除是否成功 */ @Override @Transactional(rollbackFor = Exception.class) public Boolean removeRoleByIds(Long[] ids) { roleMenuService .remove(Wrappers.update().lambda().in(SysRoleMenu::getRoleId, CollUtil.toList(ids))); return this.removeBatchByIds(CollUtil.toList(ids)); } /** * 更新角色菜单列表 * @param roleVo 包含角色ID和菜单ID列表的角色对象 * @return 更新是否成功 */ @Override public Boolean updateRoleMenus(RoleVO roleVo) { return roleMenuService.saveRoleMenus(roleVo.getRoleId(), roleVo.getMenuIds()); } /** * 导入角色 * @param excelVOList 角色列表 * @param bindingResult 错误信息列表 * @return ok fail */ @Override public R importRole(List excelVOList, BindingResult bindingResult) { // 通用校验获取失败的数据 List errorMessageList = (List) bindingResult.getTarget(); // 个性化校验逻辑 List roleList = this.list(); // 执行数据插入操作 组装 RoleDto for (RoleExcelVO excel : excelVOList) { Set errorMsg = new HashSet<>(); // 检验角色名称或者角色编码是否存在 boolean existRole = roleList.stream() .anyMatch(sysRole -> excel.getRoleName().equals(sysRole.getRoleName()) || excel.getRoleCode().equals(sysRole.getRoleCode())); if (existRole) { errorMsg.add(MsgUtils.getMessage(ErrorCodes.SYS_ROLE_NAMEORCODE_EXISTING, excel.getRoleName(), excel.getRoleCode())); } // 数据合法情况 if (CollUtil.isEmpty(errorMsg)) { insertExcelRole(excel); } else { // 数据不合法情况 errorMessageList.add(new ErrorMessage(excel.getLineNum(), errorMsg)); } } if (CollUtil.isNotEmpty(errorMessageList)) { return R.failed(errorMessageList); } return R.ok(); } /** * 查询全部角色列表并转换为Excel视图对象 * @return 角色Excel视图对象列表 */ @Override public List listRoles() { List roleList = this.list(Wrappers.emptyWrapper()); // 转换成execl 对象输出 return roleList.stream().map(role -> { RoleExcelVO roleExcelVO = new RoleExcelVO(); BeanUtil.copyProperties(role, roleExcelVO); return roleExcelVO; }).toList(); } /** * 插入Excel中的角色数据 * @param excel 包含角色信息的Excel数据对象 */ private void insertExcelRole(RoleExcelVO excel) { SysRole sysRole = new SysRole(); sysRole.setRoleName(excel.getRoleName()); sysRole.setRoleDesc(excel.getRoleDesc()); sysRole.setRoleCode(excel.getRoleCode()); this.save(sysRole); } } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysUserRoleServiceImpl.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.pig4cloud.pig.admin.api.entity.SysUserRole; import com.pig4cloud.pig.admin.mapper.SysUserRoleMapper; import com.pig4cloud.pig.admin.service.SysUserRoleService; import org.springframework.stereotype.Service; /** *

* 用户角色表 服务实现类 *

* * @author lengleng * @since 2017-10-29 */ @Service public class SysUserRoleServiceImpl extends ServiceImpl implements SysUserRoleService { } ================================================ FILE: pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysUserServiceImpl.java ================================================ /* * * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) * */ package com.pig4cloud.pig.admin.service.impl; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.pig4cloud.pig.admin.api.dto.RegisterUserDTO; import com.pig4cloud.pig.admin.api.dto.UserDTO; import com.pig4cloud.pig.admin.api.dto.UserInfo; import com.pig4cloud.pig.admin.api.entity.*; import com.pig4cloud.pig.admin.api.util.ParamResolver; import com.pig4cloud.pig.admin.api.vo.UserExcelVO; import com.pig4cloud.pig.admin.api.vo.UserVO; import com.pig4cloud.pig.admin.mapper.SysUserMapper; import com.pig4cloud.pig.admin.mapper.SysUserPostMapper; import com.pig4cloud.pig.admin.mapper.SysUserRoleMapper; import com.pig4cloud.pig.admin.service.*; import com.pig4cloud.pig.common.core.constant.CacheConstants; import com.pig4cloud.pig.common.core.constant.CommonConstants; import com.pig4cloud.pig.common.core.exception.ErrorCodes; import com.pig4cloud.pig.common.core.util.MsgUtils; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.security.util.SecurityUtils; import com.pig4cloud.plugin.excel.vo.ErrorMessage; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CacheEvict; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.BindingResult; import java.time.LocalDateTime; import java.util.*; import java.util.stream.Collectors; /** * 系统用户服务实现类 * * @author lengleng * @date 2025/05/30 */ @Slf4j @Service @AllArgsConstructor public class SysUserServiceImpl extends ServiceImpl implements SysUserService { private static final PasswordEncoder ENCODER = new BCryptPasswordEncoder(); private final SysMenuService sysMenuService; private final SysRoleService sysRoleService; private final SysPostService sysPostService; private final SysDeptService sysDeptService; private final SysUserRoleMapper sysUserRoleMapper; private final SysUserPostMapper sysUserPostMapper; private final CacheManager cacheManager; /** * 保存用户信息 * @param userDto 用户数据传输对象 * @return 操作是否成功 * @throws Exception 事务回滚时抛出异常 */ @Override @Transactional(rollbackFor = Exception.class) public Boolean saveUser(UserDTO userDto) { SysUser sysUser = new SysUser(); BeanUtils.copyProperties(userDto, sysUser); sysUser.setDelFlag(CommonConstants.STATUS_NORMAL); sysUser.setCreateBy(userDto.getUsername()); sysUser.setPassword(ENCODER.encode(userDto.getPassword())); baseMapper.insert(sysUser); // 保存用户岗位信息 Optional.ofNullable(userDto.getPost()).ifPresent(posts -> posts.forEach(postId -> { SysUserPost userPost = new SysUserPost(); userPost.setUserId(sysUser.getUserId()); userPost.setPostId(postId); sysUserPostMapper.insert(userPost); })); // 如果角色为空,赋默认角色 if (CollUtil.isEmpty(userDto.getRole())) { // 获取默认角色编码 String defaultRole = ParamResolver.getStr("USER_DEFAULT_ROLE"); // 默认角色 SysRole sysRole = sysRoleService .getOne(Wrappers.lambdaQuery().eq(SysRole::getRoleCode, defaultRole)); userDto.setRole(Collections.singletonList(sysRole.getRoleId())); } // 插入用户角色关系表 userDto.getRole().forEach(roleId -> { SysUserRole userRole = new SysUserRole(); userRole.setUserId(sysUser.getUserId()); userRole.setRoleId(roleId); sysUserRoleMapper.insert(userRole); }); return Boolean.TRUE; } /** * 查询用户全部信息,包括角色和权限 * @param query 用户查询条件 * @return 包含用户角色和权限的用户信息对象 */ @Override public R getUserInfo(UserDTO query) { UserVO dbUser = baseMapper.getUser(query); if (dbUser == null) { return R.failed(MsgUtils.getMessage(ErrorCodes.SYS_USER_USERINFO_EMPTY, query.getUsername())); } UserInfo userInfo = new UserInfo(); BeanUtils.copyProperties(dbUser, userInfo); // 设置权限列表(menu.permission) List permissions = dbUser.getRoleList() .stream() .map(SysRole::getRoleId) .flatMap(roleId -> sysMenuService.findMenuByRoleId(roleId).stream()) .filter(menu -> StrUtil.isNotEmpty(menu.getPermission())) .map(SysMenu::getPermission) .toList(); userInfo.setPermissions(permissions); return R.ok(userInfo); } /** * 分页查询用户信息(包含角色信息) * @param page 分页对象 * @param userDTO 查询参数 * @return 包含用户和角色信息的分页结果 */ @Override public IPage getUsersWithRolePage(Page page, UserDTO userDTO) { return baseMapper.getUsersPage(page, userDTO); } /** * 通过ID查询用户信息 * @param id 用户ID * @return 用户信息VO对象 */ @Override public UserVO getUserById(Long id) { UserDTO query = new UserDTO(); query.setUserId(id); return baseMapper.getUser(query); } /** * 根据用户ID列表删除用户及相关缓存 * @param ids 用户ID数组 * @return 删除成功返回true * @throws Exception 事务回滚时抛出异常 */ @Override @Transactional(rollbackFor = Exception.class) public Boolean removeUserByIds(Long[] ids) { List idList = CollUtil.toList(ids); // 删除 spring cache Cache cache = cacheManager.getCache(CacheConstants.USER_DETAILS); baseMapper.selectByIds(idList).forEach(user -> cache.evictIfPresent(user.getUsername())); sysUserRoleMapper.delete(Wrappers.lambdaQuery().in(SysUserRole::getUserId, idList)); this.removeBatchByIds(idList); return Boolean.TRUE; } /** * 更新用户信息 * @param userDto 用户数据传输对象 * @return 操作结果,包含更新是否成功 */ @Override @CacheEvict(value = CacheConstants.USER_DETAILS, key = "#userDto.username") public R updateUserInfo(UserDTO userDto) { SysUser sysUser = new SysUser(); sysUser.setPhone(userDto.getPhone()); sysUser.setUserId(SecurityUtils.getUser().getId()); sysUser.setAvatar(userDto.getAvatar()); sysUser.setNickname(userDto.getNickname()); sysUser.setName(userDto.getName()); sysUser.setEmail(userDto.getEmail()); return R.ok(this.updateById(sysUser)); } /** * 更新用户信息 * @param userDto 用户数据传输对象,包含需要更新的用户信息 * @return 更新成功返回true */ @Override @Transactional(rollbackFor = Exception.class) @CacheEvict(value = CacheConstants.USER_DETAILS, key = "#userDto.username") public Boolean updateUser(UserDTO userDto) { // 更新用户表信息 SysUser sysUser = new SysUser(); BeanUtils.copyProperties(userDto, sysUser); sysUser.setUpdateTime(LocalDateTime.now()); if (StrUtil.isNotBlank(userDto.getPassword())) { sysUser.setPassword(ENCODER.encode(userDto.getPassword())); } this.updateById(sysUser); // 更新用户角色表 if (Objects.nonNull(userDto.getRole())) { // 删除用户角色关系 sysUserRoleMapper .delete(Wrappers.lambdaQuery().eq(SysUserRole::getUserId, userDto.getUserId())); userDto.getRole().forEach(roleId -> { SysUserRole userRole = new SysUserRole(); userRole.setUserId(sysUser.getUserId()); userRole.setRoleId(roleId); sysUserRoleMapper.insert(userRole); }); } if (Objects.nonNull(userDto.getPost())) { // 删除用户岗位关系 sysUserPostMapper .delete(Wrappers.lambdaQuery().eq(SysUserPost::getUserId, userDto.getUserId())); userDto.getPost().forEach(postId -> { SysUserPost userPost = new SysUserPost(); userPost.setUserId(sysUser.getUserId()); userPost.setPostId(postId); sysUserPostMapper.insert(userPost); }); } return Boolean.TRUE; } /** * 查询用户列表并转换为Excel导出格式 * @param userDTO 用户查询条件 * @return 用户Excel视图对象列表 */ @Override public List listUsers(UserDTO userDTO) { // 根据数据权限查询全部的用户信息 List voList = baseMapper.listUsers(userDTO); // 转换成execl 对象输出 return voList.stream().map(userVO -> { UserExcelVO excelVO = new UserExcelVO(); BeanUtils.copyProperties(userVO, excelVO); excelVO.setRoleNameList( userVO.getRoleList().stream().map(SysRole::getRoleName).collect(Collectors.joining(StrUtil.COMMA))); excelVO.setPostNameList( userVO.getPostList().stream().map(SysPost::getPostName).collect(Collectors.joining(StrUtil.COMMA))); if (Objects.nonNull(userVO.getDept())) { excelVO.setDeptName(userVO.getDept().getName()); } return excelVO; }).toList(); } /** * 导入用户数据 * @param excelVOList Excel数据列表 * @param bindingResult 校验结果 * @return 导入结果,包含成功或失败信息 */ @Override public R importUsers(List excelVOList, BindingResult bindingResult) { // 通用校验获取失败的数据 List errorMessageList = (List) bindingResult.getTarget(); List deptList = sysDeptService.list(); List roleList = sysRoleService.list(); List postList = sysPostService.list(); // 执行数据插入操作 组装 UserDto for (UserExcelVO excel : excelVOList) { // 个性化校验逻辑 List userList = this.list(); Set errorMsg = new HashSet<>(); // 校验用户名是否存在 if (userList.stream().anyMatch(sysUser -> excel.getUsername().equals(sysUser.getUsername()))) { errorMsg.add(MsgUtils.getMessage(ErrorCodes.SYS_USER_USERNAME_EXISTING, excel.getUsername())); } // 判断输入的部门名称列表是否合法 Optional deptOptional = deptList.stream() .filter(dept -> excel.getDeptName().equals(dept.getName())) .findFirst(); if (deptOptional.isEmpty()) { errorMsg.add(MsgUtils.getMessage(ErrorCodes.SYS_DEPT_DEPTNAME_INEXISTENCE, excel.getDeptName())); } // 判断输入的角色名称列表是否合法 List roleNameList = StrUtil.split(excel.getRoleNameList(), StrUtil.COMMA); List roleCollList = roleList.stream() .filter(role -> roleNameList.stream().anyMatch(name -> role.getRoleName().equals(name))) .toList(); if (roleCollList.size() != roleNameList.size()) { errorMsg.add(MsgUtils.getMessage(ErrorCodes.SYS_ROLE_ROLENAME_INEXISTENCE, excel.getRoleNameList())); } // 判断输入的部门名称列表是否合法 List postNameList = StrUtil.split(excel.getPostNameList(), StrUtil.COMMA); List postCollList = postList.stream() .filter(post -> postNameList.stream().anyMatch(name -> post.getPostName().equals(name))) .toList(); if (postCollList.size() != postNameList.size()) { errorMsg.add(MsgUtils.getMessage(ErrorCodes.SYS_POST_POSTNAME_INEXISTENCE, excel.getPostNameList())); } // 数据合法情况 if (CollUtil.isEmpty(errorMsg)) { insertExcelUser(excel, deptOptional, roleCollList, postCollList); } else { // 数据不合法情况 errorMessageList.add(new ErrorMessage(excel.getLineNum(), errorMsg)); } } if (CollUtil.isNotEmpty(errorMessageList)) { return R.failed(errorMessageList); } return R.ok(); } /** * 插入Excel导入的用户信息 * @param excel Excel用户数据对象 * @param deptOptional 部门信息Optional对象 * @param roleCollList 角色列表 * @param postCollList 岗位列表 */ private void insertExcelUser(UserExcelVO excel, Optional deptOptional, List roleCollList, List postCollList) { UserDTO userDTO = new UserDTO(); userDTO.setUsername(excel.getUsername()); userDTO.setPhone(excel.getPhone()); userDTO.setNickname(excel.getNickname()); userDTO.setName(excel.getName()); userDTO.setEmail(excel.getEmail()); // 批量导入初始密码为手机号 userDTO.setPassword(userDTO.getPhone()); // 根据部门名称查询部门ID userDTO.setDeptId(deptOptional.get().getDeptId()); // 插入岗位名称 List postIdList = postCollList.stream().map(SysPost::getPostId).toList(); userDTO.setPost(postIdList); // 根据角色名称查询角色ID List roleIdList = roleCollList.stream().map(SysRole::getRoleId).toList(); userDTO.setRole(roleIdList); // 插入用户 this.saveUser(userDTO); } /** * 注册用户并赋予默认角色 * @param userDto 用户注册信息DTO * @return 注册结果,包含成功或失败状态 */ @Override @Transactional(rollbackFor = Exception.class) public R registerUser(RegisterUserDTO userDto) { // 判断用户名是否存在 SysUser sysUser = this.getOne(Wrappers.lambdaQuery().eq(SysUser::getUsername, userDto.getUsername())); if (sysUser != null) { String message = MsgUtils.getMessage(ErrorCodes.SYS_USER_USERNAME_EXISTING, userDto.getUsername()); return R.failed(message); } UserDTO user = new UserDTO(); BeanUtils.copyProperties(userDto, user); return R.ok(saveUser(user)); } /** * 锁定用户 * @param username 用户名 * @return 操作结果,包含是否成功的信息 */ @Override @CacheEvict(value = CacheConstants.USER_DETAILS, key = "#username") public R lockUser(String username) { SysUser sysUser = baseMapper.selectOne(Wrappers.lambdaQuery().eq(SysUser::getUsername, username)); if (Objects.nonNull(sysUser)) { sysUser.setLockFlag(CommonConstants.STATUS_LOCK); baseMapper.updateById(sysUser); } return R.ok(); } /** * 修改用户密码 * @param userDto 用户信息传输对象,包含用户名、原密码和新密码 * @return 操作结果,成功返回R.ok(),失败返回错误信息 * @CacheEvict 清除用户详情缓存 */ @Override @CacheEvict(value = CacheConstants.USER_DETAILS, key = "#userDto.username") public R changePassword(UserDTO userDto) { SysUser sysUser = baseMapper.selectById(SecurityUtils.getUser().getId()); if (Objects.isNull(sysUser)) { return R.failed("用户不存在"); } if (StrUtil.isEmpty(userDto.getPassword())) { return R.failed("原密码不能为空"); } if (!ENCODER.matches(userDto.getPassword(), sysUser.getPassword())) { log.info("原密码错误,修改个人信息失败:{}", userDto.getUsername()); return R.failed(MsgUtils.getMessage(ErrorCodes.SYS_USER_UPDATE_PASSWORDERROR)); } if (StrUtil.isEmpty(userDto.getNewpassword1())) { return R.failed("新密码不能为空"); } String password = ENCODER.encode(userDto.getNewpassword1()); this.update(Wrappers.lambdaUpdate() .set(SysUser::getPassword, password) .eq(SysUser::getUserId, sysUser.getUserId())); return R.ok(); } /** * 校验用户密码是否正确 * @param password 待校验的密码 * @return 校验结果,成功返回R.ok(),失败返回R.failed() */ @Override public R checkPassword(String password) { SysUser sysUser = baseMapper.selectById(SecurityUtils.getUser().getId()); if (!ENCODER.matches(password, sysUser.getPassword())) { log.info("原密码错误"); return R.failed("密码输入错误"); } else { return R.ok(); } } } ================================================ FILE: pig-upms/pig-upms-biz/src/main/resources/application.yml ================================================ server: port: 4000 spring: application: name: @artifactId@ cloud: nacos: username: @nacos.username@ password: @nacos.password@ discovery: server-addr: ${NACOS_HOST:127.0.0.1}:${NACOS_PORT:8848} config: server-addr: ${spring.cloud.nacos.discovery.server-addr} config: import: - nacos:application-@profiles.active@.yml - nacos:${spring.application.name}-@profiles.active@.yml ================================================ FILE: pig-upms/pig-upms-biz/src/main/resources/logback-spring.xml ================================================ ${CONSOLE_LOG_PATTERN} ${log.path}/debug.log ${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz 50MB 30 ${FILE_LOG_PATTERN} ${log.path}/error.log ${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz 50MB 30 ${FILE_LOG_PATTERN} ERROR ================================================ FILE: pig-upms/pig-upms-biz/src/main/resources/mapper/SysDeptMapper.xml ================================================ ================================================ FILE: pig-upms/pig-upms-biz/src/main/resources/mapper/SysMenuMapper.xml ================================================ ================================================ FILE: pig-upms/pig-upms-biz/src/main/resources/mapper/SysPostMapper.xml ================================================ ================================================ FILE: pig-upms/pig-upms-biz/src/main/resources/mapper/SysRoleMapper.xml ================================================ ================================================ FILE: pig-upms/pig-upms-biz/src/main/resources/mapper/SysUserMapper.xml ================================================ SELECT * FROM sys_user u u.del_flag = '0' AND u.user_id = #{query.userId} AND u.username LIKE #{usernameLike} AND u.dept_id = #{query.deptId} AND u.phone LIKE #{phoneLike} ORDER BY u.create_time DESC ================================================ FILE: pig-upms/pom.xml ================================================ 4.0.0 com.pig4cloud pig ${revision} pig-upms pig 通用用户权限管理聚合模块 pom pig-upms-api pig-upms-biz ================================================ FILE: pig-visual/pig-codegen/Dockerfile ================================================ FROM registry.cn-hangzhou.aliyuncs.com/dockerhub_mirror/java:21-anolis WORKDIR /pig-codegen ARG JAR_FILE=target/pig-codegen.jar COPY ${JAR_FILE} app.jar EXPOSE 5002 ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms128m -Xmx256m -Djava.security.egd=file:/dev/./urandom" CMD sleep 60; java $JAVA_OPTS -jar app.jar ================================================ FILE: pig-visual/pig-codegen/pom.xml ================================================ 4.0.0 com.pig4cloud pig-visual ${revision} pig-codegen jar 代码生成模块 0.0.6 8.7.2-jdk17-20240808 com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config com.pig4cloud pig-common-mybatis com.pig4cloud pig-common-datasource com.baomidou mybatis-plus-spring-boot3-starter com.mysql mysql-connector-j org.anyline anyline-environment-spring-data-jdbc ${anyline.version} org.anyline anyline-data-jdbc-mysql ${anyline.version} com.pig4cloud pig-common-core cn.hutool hutool-json com.pig4cloud pig-common-swagger com.pig4cloud pig-common-xss com.pig4cloud pig-common-security com.pig4cloud pig-common-log org.apache.velocity velocity-engine-core org.apache.velocity.tools velocity-tools-generic group.springframework.plugin screw-spring-boot-starter ${screw.version} org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-undertow cloud true org.springframework.boot spring-boot-maven-plugin repackage CLASSIC io.fabric8 docker-maven-plugin false boot ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/PigCodeGenApplication.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen; import com.pig4cloud.pig.common.datasource.annotation.EnableDynamicDataSource; import com.pig4cloud.pig.common.feign.annotation.EnablePigFeignClients; import com.pig4cloud.pig.common.security.annotation.EnablePigResourceServer; import com.pig4cloud.pig.common.swagger.annotation.EnablePigDoc; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; /** * 代码生成模块应用启动类 * * @author lengleng * @date 2025/05/31 */ @EnableDynamicDataSource @EnablePigFeignClients @EnablePigDoc("gen") @EnableDiscoveryClient @EnablePigResourceServer @SpringBootApplication public class PigCodeGenApplication { public static void main(String[] args) { SpringApplication.run(PigCodeGenApplication.class, args); } } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/config/PigCodeGenDefaultProperties.java ================================================ package com.pig4cloud.pig.codegen.config; import cn.smallbun.screw.core.constant.DefaultConstants; import lombok.Data; import org.anyline.util.ConfigTable; import org.springframework.beans.factory.InitializingBean; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; /** * 代码生成默认配置类 * * @author lengleng * @date 2025/05/31 */ @Data @Configuration(proxyBeanMethods = false) @ConfigurationProperties(prefix = PigCodeGenDefaultProperties.PREFIX) public class PigCodeGenDefaultProperties implements InitializingBean { public static final String PREFIX = "codegen"; /** * 是否开启在线更新 */ private boolean autoCheckVersion = true; /** * 模板项目地址 */ private String onlineUrl = DefaultConstants.CGTM_URL; /** * 生成代码的包名 */ private String packageName = "com.pig4cloud.pig"; /** * 生成代码的版本 */ private String version = "1.0.0"; /** * 生成代码的模块名 */ private String moduleName = "admin"; /** * 生成代码的后端路径 */ private String backendPath = "pig"; /** * 生成代码的前端路径 */ private String frontendPath = "pig-ui"; /** * 生成代码的作者 */ private String author = "pig"; /** * 生成代码的邮箱 */ private String email = "sw@pigx.vip"; /** * 表单布局(一列、两列) */ private Integer formLayout = 2; /** * 下载方式 (0 文件下载、1写入目录) */ private String generatorType = "0"; @Override public void afterPropertiesSet() throws Exception { ConfigTable.KEEP_ADAPTER = 0; } } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/controller/GenDsConfController.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.controller; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.StrUtil; import cn.smallbun.screw.boot.config.Screw; import cn.smallbun.screw.boot.properties.ScrewProperties; import com.baomidou.dynamic.datasource.DynamicRoutingDataSource; import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.pig4cloud.pig.codegen.entity.GenDatasourceConf; import com.pig4cloud.pig.codegen.service.GenDatasourceConfService; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.core.util.SpringContextHolder; import com.pig4cloud.pig.common.security.annotation.Inner; import com.pig4cloud.pig.common.xss.core.XssCleanIgnore; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import org.springframework.http.HttpHeaders; import org.springframework.web.bind.annotation.*; import javax.sql.DataSource; /** * 数据源管理控制器 * * @author lengleng * @date 2025/05/31 */ @RestController @RequiredArgsConstructor @RequestMapping("/dsconf") @Tag(description = "dsconf", name = "数据源管理模块") public class GenDsConfController { private final GenDatasourceConfService datasourceConfService; private final Screw screw; /** * 分页查询数据源配置 * @param page 分页参数对象 * @param datasourceConf 数据源配置查询条件 * @return 分页查询结果 */ @GetMapping("/page") @Operation(summary = "分页查询数据源配置", description = "分页查询数据源配置") public R getDsConfPage(Page page, GenDatasourceConf datasourceConf) { return R.ok(datasourceConfService.page(page, Wrappers.lambdaQuery() .like(StrUtil.isNotBlank(datasourceConf.getDsName()), GenDatasourceConf::getDsName, datasourceConf.getDsName()))); } /** * 查询全部数据源列表 * @return 包含全部数据源列表的响应结果 */ @Inner(value = false) @GetMapping("/list") @Operation(summary = "查询全部数据源列表", description = "查询全部数据源列表") public R listDsConfs() { return R.ok(datasourceConfService.list()); } /** * 根据ID查询数据源表 * @param id 数据源ID * @return 包含查询结果的响应对象 */ @GetMapping("/{id}") @Operation(summary = "根据ID查询数据源表", description = "根据ID查询数据源表") public R getDsConfById(@PathVariable("id") Long id) { return R.ok(datasourceConfService.getById(id)); } /** * 新增数据源表 * @param datasourceConf 数据源配置信息 * @return 操作结果 */ @PostMapping @XssCleanIgnore @Operation(summary = "新增数据源表", description = "新增数据源表") public R saveDsConf(@RequestBody GenDatasourceConf datasourceConf) { return R.ok(datasourceConfService.saveDsByEnc(datasourceConf)); } /** * 修改数据源表 * @param conf 数据源表配置信息 * @return 操作结果 */ @PutMapping @XssCleanIgnore @Operation(summary = "修改数据源表", description = "修改数据源表") public R updateDsConf(@RequestBody GenDatasourceConf conf) { return R.ok(datasourceConfService.updateDsByEnc(conf)); } /** * 通过id数组删除数据源表 * @param ids 要删除的数据源id数组 * @return 包含操作结果的R对象 */ @DeleteMapping @Operation(summary = "通过id数组删除数据源表", description = "通过id数组删除数据源表") public R removeDsConfByIds(@RequestBody Long[] ids) { return R.ok(datasourceConfService.removeByDsId(ids)); } /** * 生成指定数据源的数据库文档并输出到响应流 * @param dsName 数据源名称 * @param response HTTP响应对象 * @throws Exception 生成文档或IO操作过程中可能抛出的异常 */ @SneakyThrows @GetMapping("/doc") @Operation(summary = "生成指定数据源的数据库文档并输出到响应流", description = "生成指定数据源的数据库文档并输出到响应流") public void generatorDoc(String dsName, HttpServletResponse response) { // 设置指定的数据源 DynamicRoutingDataSource dynamicRoutingDataSource = SpringContextHolder.getBean(DynamicRoutingDataSource.class); DynamicDataSourceContextHolder.push(dsName); DataSource dataSource = dynamicRoutingDataSource.determineDataSource(); // 设置指定的目标表 ScrewProperties screwProperties = SpringContextHolder.getBean(ScrewProperties.class); // 生成 byte[] data = screw.documentGeneration(dsName, dataSource, screwProperties).toByteArray(); response.reset(); response.addHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(data.length)); response.setContentType("application/octet-stream"); IoUtil.write(response.getOutputStream(), Boolean.FALSE, data); } } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/controller/GenFieldTypeController.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.controller; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.pig4cloud.pig.codegen.entity.GenFieldType; import com.pig4cloud.pig.codegen.service.GenFieldTypeService; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.log.annotation.SysLog; import com.pig4cloud.plugin.excel.annotation.ResponseExcel; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpHeaders; import org.springframework.web.bind.annotation.*; import java.util.List; /** * 列属性管理控制器 * * @author lengleng * @date 2025/05/31 */ @RestController @RequiredArgsConstructor @RequestMapping("/fieldtype") @Tag(description = "fieldtype", name = "列属性管理模块") @SecurityRequirement(name = HttpHeaders.AUTHORIZATION) public class GenFieldTypeController { private final GenFieldTypeService fieldTypeService; /** * 分页查询字段类型 * @param page 分页对象 * @param fieldType 字段类型查询条件 * @return 分页查询结果 */ @GetMapping("/page") @Operation(summary = "分页查询", description = "分页查询") public R getFieldTypePage(Page page, GenFieldType fieldType) { return R.ok(fieldTypeService.page(page, Wrappers.lambdaQuery() .like(StrUtil.isNotBlank(fieldType.getColumnType()), GenFieldType::getColumnType, fieldType.getColumnType()))); } /** * 查询列表 * @param fieldType 查询条件 * @return 包含查询结果的响应对象 */ @GetMapping("/list") @Operation(summary = "查询列表", description = "查询列表") public R listFieldTypes(GenFieldType fieldType) { return R.ok(fieldTypeService.list(Wrappers.query(fieldType))); } /** * 通过id查询列属性 * @param id 列属性id * @return 包含查询结果的响应对象 */ @GetMapping("/details/{id}") @Operation(summary = "通过id查询", description = "通过id查询") public R getFieldTypeById(@PathVariable("id") Long id) { return R.ok(fieldTypeService.getById(id)); } /** * 根据查询条件获取字段类型详情 * @param query 字段类型查询条件 * @return 包含查询结果的响应对象 */ @GetMapping("/details") @Operation(summary = "根据查询条件获取字段类型详情", description = "根据查询条件获取字段类型详情") public R getFieldTypeDetails(GenFieldType query) { return R.ok(fieldTypeService.getOne(Wrappers.query(query), false)); } /** * 新增列属性 * @param fieldType 列属性对象 * @return 操作结果 */ @PostMapping @SysLog("新增列属性") @Operation(summary = "新增列属性", description = "新增列属性") public R saveFieldType(@RequestBody GenFieldType fieldType) { return R.ok(fieldTypeService.save(fieldType)); } /** * 修改列属性 * @param fieldType 列属性对象 * @return 操作结果 */ @PutMapping @SysLog("修改列属性") @Operation(summary = "修改列属性", description = "修改列属性") public R updateFieldType(@RequestBody GenFieldType fieldType) { return R.ok(fieldTypeService.updateById(fieldType)); } /** * 通过id批量删除列属性 * @param ids 要删除的列属性id数组 * @return 操作结果 */ @DeleteMapping @SysLog("通过id删除列属性") @Operation(summary = "通过id删除列属性", description = "通过id删除列属性") public R removeFieldTypeByIds(@RequestBody Long[] ids) { return R.ok(fieldTypeService.removeBatchByIds(CollUtil.toList(ids))); } /** * 导出excel表格 * @param fieldType 查询条件 * @return excel文件数据列表 */ @ResponseExcel @GetMapping("/export") @Operation(summary = "导出excel表格", description = "导出excel表格") public List exportFieldTypes(GenFieldType fieldType) { return fieldTypeService.list(Wrappers.query(fieldType)); } } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/controller/GenGroupController.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.controller; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.pig4cloud.pig.codegen.entity.GenGroupEntity; import com.pig4cloud.pig.codegen.service.GenGroupService; import com.pig4cloud.pig.codegen.util.vo.GroupVO; import com.pig4cloud.pig.codegen.util.vo.TemplateGroupDTO; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.log.annotation.SysLog; import com.pig4cloud.pig.common.security.annotation.HasPermission; import com.pig4cloud.plugin.excel.annotation.ResponseExcel; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpHeaders; import org.springframework.web.bind.annotation.*; import java.util.List; /** * 模板分组管理控制器 * * @author lengleng * @date 2025/05/31 */ @RestController @RequiredArgsConstructor @RequestMapping("/group") @Tag(description = "group", name = "模板分组管理模块") @SecurityRequirement(name = HttpHeaders.AUTHORIZATION) public class GenGroupController { private final GenGroupService genGroupService; /** * 分页查询模板分组 * @param page 分页对象 * @param genGroup 模板分组查询条件 * @return 分页查询结果 */ @GetMapping("/page") @HasPermission("codegen_group_view") @Operation(summary = "分页查询模板分组", description = "分页查询模板分组") public R getGroupPage(Page page, GenGroupEntity genGroup) { LambdaQueryWrapper wrapper = Wrappers.lambdaQuery() .like(genGroup.getId() != null, GenGroupEntity::getId, genGroup.getId()) .like(StrUtil.isNotEmpty(genGroup.getGroupName()), GenGroupEntity::getGroupName, genGroup.getGroupName()); return R.ok(genGroupService.page(page, wrapper)); } /** * 通过id查询模板分组 * @param id id * @return R */ @GetMapping("/{id}") @HasPermission("codegen_group_view") @Operation(summary = "通过id查询模板分组", description = "通过id查询模板分组") public R getGroupById(@PathVariable("id") Long id) { return R.ok(genGroupService.getGroupVoById(id)); } /** * 新增模板分组 * @param genTemplateGroup 模板分组 * @return R */ @PostMapping @SysLog("新增模板分组") @HasPermission("codegen_group_add") @Operation(summary = "新增模板分组", description = "新增模板分组") public R saveGroup(@RequestBody TemplateGroupDTO genTemplateGroup) { genGroupService.saveGenGroup(genTemplateGroup); return R.ok(); } /** * 修改模板分组 * @param groupVo 模板分组 * @return R */ @PutMapping @SysLog("修改模板分组") @HasPermission("codegen_group_edit") @Operation(summary = "修改模板分组", description = "修改模板分组") public R updateGroup(@RequestBody GroupVO groupVo) { genGroupService.updateGroupAndTemplateById(groupVo); return R.ok(); } /** * 通过id删除模板分组 * @param ids id列表 * @return R */ @DeleteMapping @SysLog("通过id删除模板分组") @HasPermission("codegen_group_del") @Operation(summary = "通过id删除模板分组", description = "通过id删除模板分组") public R removeGroupByIds(@RequestBody Long[] ids) { genGroupService.delGroupAndTemplate(ids); return R.ok(); } /** * 导出excel 表格 * @param genGroup 查询条件 * @return excel 文件流 */ @ResponseExcel @GetMapping("/export") @HasPermission("codegen_group_export") @Operation(summary = "导出模板分组", description = "导出模板分组") public List exportGroups(GenGroupEntity genGroup) { return genGroupService.list(Wrappers.query(genGroup)); } /** * 查询列表 * @return 包含列表数据的响应信息 */ @GetMapping("/list") @Operation(summary = "查询列表", description = "查询列表") public R listGroups() { List list = genGroupService .list(Wrappers.lambdaQuery().orderByDesc(GenGroupEntity::getCreateTime)); return R.ok(list); } } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/controller/GenTableController.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.controller; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.pig4cloud.pig.codegen.entity.GenTable; import com.pig4cloud.pig.codegen.entity.GenTableColumnEntity; import com.pig4cloud.pig.codegen.service.GenTableColumnService; import com.pig4cloud.pig.codegen.service.GenTableService; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.log.annotation.SysLog; import com.pig4cloud.plugin.excel.annotation.ResponseExcel; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpHeaders; import org.springframework.web.bind.annotation.*; import java.util.List; /** * 代码表管理控制器 * * @author lengleng * @date 2025/05/31 */ @RestController @RequiredArgsConstructor @RequestMapping("/table") @Tag(description = "table", name = "代码表管理模块") @SecurityRequirement(name = HttpHeaders.AUTHORIZATION) public class GenTableController { private final GenTableColumnService tableColumnService; /** * 表服务 */ private final GenTableService tableService; /** * 分页查询 * @param page 分页对象 * @param table 列属性 * @return */ @GetMapping("/page") @Operation(summary = "分页查询", description = "分页查询") public R getTablePage(Page page, GenTable table) { return R.ok(tableService.queryTablePage(page, table)); } /** * 通过id查询表信息(代码生成设置 + 表 + 字段设置) * @param id id * @return R */ @GetMapping("/{id}") @Operation(summary = "通过id查询", description = "通过id查询") public R getTableById(@PathVariable("id") Long id) { return R.ok(tableService.getById(id)); } /** * 查询数据源所有表 * @param dsName 数据源名称 * @return 包含表列表的响应结果 */ @GetMapping("/list/{dsName}") @Operation(summary = "查询数据源所有表", description = "查询数据源所有表") public R listTables(@PathVariable("dsName") String dsName) { return R.ok(tableService.queryTableList(dsName)); } /** * 获取表信息 * @param dsName 数据源 * @param tableName 表名称 */ @GetMapping("/{dsName}/{tableName}") @Operation(summary = "获取表信息", description = "获取表信息") public R getTable(@PathVariable("dsName") String dsName, @PathVariable String tableName) { return R.ok(tableService.queryOrBuildTable(dsName, tableName)); } /** * 查询表DDL语句 * @param dsName 数据源 * @param tableName 表名称 */ @GetMapping("/column/{dsName}/{tableName}") @Operation(summary = "查询表Column的DDL语句", description = "查询表Column的DDL语句") public R getTableColumn(@PathVariable("dsName") String dsName, @PathVariable String tableName) throws Exception { return R.ok(tableService.queryTableColumn(dsName, tableName)); } /** * 查询表DDL语句 * @param dsName 数据源 * @param tableName 表名称 */ @GetMapping("/ddl/{dsName}/{tableName}") @Operation(summary = "查询表DDL语句", description = "查询表DDL语句") public R getTableDdl(@PathVariable("dsName") String dsName, @PathVariable String tableName) throws Exception { return R.ok(tableService.queryTableDdl(dsName, tableName)); } /** * 同步表信息 * @param dsName 数据源 * @param tableName 表名称 */ @GetMapping("/sync/{dsName}/{tableName}") @Operation(summary = "同步表信息", description = "同步表信息") public R syncTable(@PathVariable("dsName") String dsName, @PathVariable String tableName) { // 表配置删除 tableService.remove( Wrappers.lambdaQuery().eq(GenTable::getDsName, dsName).eq(GenTable::getTableName, tableName)); // 字段配置删除 tableColumnService.remove(Wrappers.lambdaQuery() .eq(GenTableColumnEntity::getDsName, dsName) .eq(GenTableColumnEntity::getTableName, tableName)); return R.ok(tableService.queryOrBuildTable(dsName, tableName)); } /** * 修改列属性 * @param table 列属性 * @return R */ @PutMapping @SysLog("修改列属性") @Operation(summary = "修改列属性", description = "修改列属性") public R updateTable(@RequestBody GenTable table) { return R.ok(tableService.updateById(table)); } /** * 修改表字段数据 * @param dsName 数据源 * @param tableName 表名称 * @param tableFieldList 字段列表 */ @PutMapping("/field/{dsName}/{tableName}") @Operation(summary = "修改表字段数据", description = "修改表字段数据") public R updateTableField(@PathVariable("dsName") String dsName, @PathVariable String tableName, @RequestBody List tableFieldList) { tableColumnService.updateTableField(dsName, tableName, tableFieldList); return R.ok(); } /** * 导出excel 表格 * @param table 查询条件 * @return excel 文件流 */ @ResponseExcel @GetMapping("/export") @Operation(summary = "导出字段数据", description = "导出字段数据") public List exportTables(GenTable table) { return tableService.list(Wrappers.query(table)); } } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/controller/GenTemplateController.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.controller; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.pig4cloud.pig.codegen.entity.GenTemplateEntity; import com.pig4cloud.pig.codegen.service.GenTemplateService; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.log.annotation.SysLog; import com.pig4cloud.pig.common.security.annotation.HasPermission; import com.pig4cloud.pig.common.xss.core.XssCleanIgnore; import com.pig4cloud.plugin.excel.annotation.ResponseExcel; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpHeaders; import org.springframework.web.bind.annotation.*; import java.util.List; /** * 模板管理控制器 * * @author lengleng * @date 2025/05/31 */ @RestController @RequiredArgsConstructor @RequestMapping("/template") @Tag(description = "template", name = "模板管理模块") @SecurityRequirement(name = HttpHeaders.AUTHORIZATION) public class GenTemplateController { private final GenTemplateService genTemplateService; /** * 分页查询模板信息 * @param page 分页参数对象 * @param genTemplate 模板查询条件 * @return 分页查询结果 */ @Operation(summary = "分页查询", description = "分页查询") @GetMapping("/page") @HasPermission("codegen_template_view") public R getTemplatePage(Page page, GenTemplateEntity genTemplate) { LambdaQueryWrapper wrapper = Wrappers.lambdaQuery() .like(genTemplate.getId() != null, GenTemplateEntity::getId, genTemplate.getId()) .like(StrUtil.isNotEmpty(genTemplate.getTemplateName()), GenTemplateEntity::getTemplateName, genTemplate.getTemplateName()); return R.ok(genTemplateService.page(page, wrapper)); } /** * 查询全部模板 * @return */ @Operation(summary = "查询全部", description = "查询全部") @GetMapping("/list") @HasPermission("codegen_template_view") public R listTemplates() { return R.ok(genTemplateService .list(Wrappers.lambdaQuery().orderByDesc(GenTemplateEntity::getCreateTime))); } /** * 通过id查询模板 * @param id id * @return R */ @Operation(summary = "通过id查询", description = "通过id查询") @GetMapping("/{id}") @HasPermission("codegen_template_view") public R getTemplateById(@PathVariable("id") Long id) { return R.ok(genTemplateService.getById(id)); } /** * 新增模板 * @param genTemplate 模板 * @return R */ @XssCleanIgnore @Operation(summary = "新增模板", description = "新增模板") @SysLog("新增模板") @PostMapping @HasPermission("codegen_template_add") public R saveTemplate(@RequestBody GenTemplateEntity genTemplate) { return R.ok(genTemplateService.save(genTemplate)); } /** * 修改模板 * @param genTemplate 模板 * @return R */ @XssCleanIgnore @Operation(summary = "修改模板", description = "修改模板") @SysLog("修改模板") @PutMapping @HasPermission("codegen_template_edit") public R updateTemplate(@RequestBody GenTemplateEntity genTemplate) { return R.ok(genTemplateService.updateById(genTemplate)); } /** * 通过id删除模板 * @param ids id列表 * @return R */ @Operation(summary = "通过id删除模板", description = "通过id删除模板") @SysLog("通过id删除模板") @DeleteMapping @HasPermission("codegen_template_del") public R removeTemplateByIds(@RequestBody Long[] ids) { return R.ok(genTemplateService.removeBatchByIds(CollUtil.toList(ids))); } /** * 导出excel 表格 * @param genTemplate 查询条件 * @return excel 文件流 */ @ResponseExcel @GetMapping("/export") @HasPermission("codegen_template_export") @Operation(summary = "导出模板", description = "导出模板") public List exportTemplates(GenTemplateEntity genTemplate) { return genTemplateService.list(Wrappers.query(genTemplate)); } /** * 在线更新模板 * @return R */ @Operation(summary = "在线更新模板", description = "在线更新模板") @GetMapping("/online") @HasPermission("codegen_template_view") public R online() { return genTemplateService.onlineUpdate(); } /** * 检查版本 * @return {@link R } */ @Operation(summary = "在线检查模板", description = "在线检查模板") @GetMapping("/checkVersion") @HasPermission("codegen_template_view") public R checkVersion() { return genTemplateService.checkVersion(); } } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/controller/GenTemplateGroupController.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.controller; import cn.hutool.core.collection.CollUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.pig4cloud.pig.codegen.entity.GenTemplateGroupEntity; import com.pig4cloud.pig.codegen.service.GenTemplateGroupService; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.log.annotation.SysLog; import com.pig4cloud.pig.common.security.annotation.HasPermission; import com.pig4cloud.plugin.excel.annotation.ResponseExcel; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpHeaders; import org.springframework.web.bind.annotation.*; import java.util.List; /** * 模板分组关联表 * * @author PIG * @date 2023-02-22 09:25:15 */ @RestController @RequiredArgsConstructor @RequestMapping("/templateGroup") @Tag(description = "templateGroup", name = "模板分组关联表管理模块") @SecurityRequirement(name = HttpHeaders.AUTHORIZATION) public class GenTemplateGroupController { private final GenTemplateGroupService genTemplateGroupService; /** * 分页查询 * @param page 分页对象 * @param genTemplateGroup 模板分组关联表 * @return */ @Operation(summary = "分页查询", description = "分页查询") @GetMapping("/page") @HasPermission("codegen_templateGroup_view") public R getTemplateGroupPage(Page page, GenTemplateGroupEntity genTemplateGroup) { LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); return R.ok(genTemplateGroupService.page(page, wrapper)); } /** * 通过id查询模板分组关联表 * @param groupId id * @return R */ @Operation(summary = "通过id查询", description = "通过id查询") @GetMapping("/{groupId}") @HasPermission("codegen_templateGroup_view") public R getTemplateGroupById(@PathVariable("groupId") Long groupId) { return R.ok(genTemplateGroupService.getById(groupId)); } /** * 新增模板分组关联表 * @param genTemplateGroup 模板分组关联表 * @return R */ @Operation(summary = "新增模板分组关联表", description = "新增模板分组关联表") @SysLog("新增模板分组关联表") @PostMapping @HasPermission("codegen_templateGroup_add") public R saveTemplateGroup(@RequestBody GenTemplateGroupEntity genTemplateGroup) { return R.ok(genTemplateGroupService.save(genTemplateGroup)); } /** * 修改模板分组关联表 * @param genTemplateGroup 模板分组关联表 * @return R */ @Operation(summary = "修改模板分组关联表", description = "修改模板分组关联表") @SysLog("修改模板分组关联表") @PutMapping @HasPermission("codegen_templateGroup_edit") public R updateTemplateGroup(@RequestBody GenTemplateGroupEntity genTemplateGroup) { return R.ok(genTemplateGroupService.updateById(genTemplateGroup)); } /** * 通过id删除模板分组关联表 * @param ids groupId列表 * @return R */ @Operation(summary = "通过id删除模板分组关联表", description = "通过id删除模板分组关联表") @SysLog("通过id删除模板分组关联表") @DeleteMapping @HasPermission("codegen_templateGroup_del") public R removeTemplateGroupByIds(@RequestBody Long[] ids) { return R.ok(genTemplateGroupService.removeBatchByIds(CollUtil.toList(ids))); } /** * 导出excel 表格 * @param genTemplateGroup 查询条件 * @return excel 文件流 */ @ResponseExcel @GetMapping("/export") @HasPermission("codegen_templateGroup_export") @Operation(summary = "导出excel模板分组", description = "导出excel模板分组") public List exportTemplateGroups(GenTemplateGroupEntity genTemplateGroup) { return genTemplateGroupService.list(Wrappers.query(genTemplateGroup)); } } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/controller/GeneratorController.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.controller; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.StrUtil; import com.pig4cloud.pig.codegen.service.GeneratorService; import com.pig4cloud.pig.common.core.util.R; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import org.springframework.http.HttpHeaders; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import java.io.ByteArrayOutputStream; import java.util.List; import java.util.Map; import java.util.zip.ZipOutputStream; /** * 代码生成器控制器 * * @author lengleng * @date 2025/05/31 */ @RestController @RequiredArgsConstructor @RequestMapping("/generator") @Tag(description = "generator", name = "代码生成器控制器管理模块") public class GeneratorController { private final GeneratorService generatorService; /** * ZIP 下载生成代码 * @param tableIds 数据表ID * @param response 流输出对象 */ @SneakyThrows @GetMapping("/download") @Operation(summary = "ZIP下载生成代码", description = "ZIP下载生成代码") public void download(String tableIds, HttpServletResponse response) { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ZipOutputStream zip = new ZipOutputStream(outputStream); // 生成代码 for (String tableId : tableIds.split(StrUtil.COMMA)) { generatorService.downloadCode(Long.parseLong(tableId), zip); } IoUtil.close(zip); // zip压缩包数据 byte[] data = outputStream.toByteArray(); response.reset(); response.setHeader(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment; filename=%s.zip", tableIds)); response.addHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(data.length)); response.setContentType("application/octet-stream; charset=UTF-8"); IoUtil.write(response.getOutputStream(), false, data); } /** * 生成代码 * @param tableIds 表ID列表,多个ID用逗号分隔 * @return 操作结果 * @throws Exception 生成代码过程中可能抛出的异常 */ @ResponseBody @GetMapping("/code") @Operation(summary = "生成代码", description = "生成代码") public R code(String tableIds) throws Exception { // 生成代码 for (String tableId : tableIds.split(StrUtil.COMMA)) { generatorService.generatorCode(Long.valueOf(tableId)); } return R.ok(); } /** * 预览代码 * @param tableId 表ID * @return 代码预览结果列表 */ @SneakyThrows @GetMapping("/preview") @Operation(summary = "预览代码", description = "预览代码") public List> preview(Long tableId) { return generatorService.preview(tableId); } } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/entity/ColumnEntity.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.entity; import lombok.Data; /** * @author lengleng * @date 2018/07/29 列属性: https://blog.csdn.net/lkforce/article/details/79557482 */ @Data public class ColumnEntity { /** * 列表 */ private String columnName; /** * 数据类型 */ private String dataType; /** * JAVA 数据类型 */ private String javaType; /** * 备注 */ private String comments; /** * 驼峰属性 */ private String caseAttrName; /** * 普通属性 */ private String lowerAttrName; /** * 属性类型 */ private String attrType; /** * 其他信息 */ private String extra; /** * 字段类型 */ private String columnType; /** * 是否可以为空 */ private Boolean nullable; /** * 是否隐藏 */ private Boolean hidden; } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/entity/GenConfig.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.entity; import lombok.Data; /** * @author lengleng * @date 2018/8/2 生成配置 */ @Data public class GenConfig { /** * 数据源name */ private String dsName; /** * 包名 */ private String packageName; /** * 作者 */ private String author; /** * 模块名称 */ private String moduleName; /** * 表前缀 */ private String tablePrefix; /** * 表名称 */ private String tableName; /** * 表备注 */ private String comments; /** * 代码风格 0 - avue 1 - element 2 - uview */ private String style; } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/entity/GenDatasourceConf.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.entity; import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.extension.activerecord.Model; import lombok.Data; import lombok.EqualsAndHashCode; import java.time.LocalDateTime; /** * 数据源表 * * @author lengleng * @date 2019-03-31 16:00:20 */ @Data @TableName("gen_datasource_conf") @EqualsAndHashCode(callSuper = true) public class GenDatasourceConf extends Model { private static final long serialVersionUID = 1L; /** * 主键 */ @TableId(type = IdType.ASSIGN_ID) private Long id; /** * 名称 */ private String name; /** * 数据库类型 */ private String dsType; /** * 配置类型 (0 主机形式 | 1 url形式) */ private Integer confType; /** * 主机地址 */ private String host; /** * 端口 */ private Integer port; /** * jdbc-url */ private String url; /** * 实例 */ private String instance; /** * 数据库名称 */ private String dsName; /** * 用户名 */ private String username; /** * 密码 */ private String password; /** * 创建时间 */ @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; /** * 修改时间 */ @TableField(fill = FieldFill.UPDATE) private LocalDateTime updateTime; /** * 0-正常,1-删除 */ @TableLogic @TableField(fill = FieldFill.INSERT) private String delFlag; } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/entity/GenFieldType.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.entity; import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.extension.activerecord.Model; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import java.time.LocalDateTime; /** * 列属性 * * @author pigx code generator * @date 2023-02-06 20:16:01 */ @Data @TableName("gen_field_type") @EqualsAndHashCode(callSuper = true) @Schema(description = "列属性") public class GenFieldType extends Model { private static final long serialVersionUID = 1L; /** * id */ @TableId(type = IdType.ASSIGN_ID) @Schema(description = "id") private Long id; /** * 字段类型 */ @Schema(description = "字段类型") private String columnType; /** * 属性类型 */ @Schema(description = "属性类型") private String attrType; /** * 属性包名 */ @Schema(description = "属性包名") private String packageName; /** * 创建人 */ @TableField(fill = FieldFill.INSERT) @Schema(description = "创建人") private String createBy; /** * 修改人 */ @TableField(fill = FieldFill.UPDATE) @Schema(description = "修改人") private String updateBy; /** * 创建时间 */ @Schema(description = "创建时间") @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; /** * 修改时间 */ @Schema(description = "修改时间") @TableField(fill = FieldFill.UPDATE) private LocalDateTime updateTime; /** * 删除标识(0-正常,1-删除) */ @TableLogic @TableField(fill = FieldFill.INSERT) @Schema(description = "删除标记,1:已删除,0:正常") private String delFlag; } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/entity/GenGroupEntity.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.entity; import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.extension.activerecord.Model; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import java.time.LocalDateTime; /** * 模板分组 * * @author PIG * @date 2023-02-21 20:01:53 */ @Data @TableName("gen_group") @EqualsAndHashCode(callSuper = true) @Schema(description = "模板分组") public class GenGroupEntity extends Model { private static final long serialVersionUID = 1L; /** * id */ @TableId(type = IdType.ASSIGN_ID) @Schema(description = "id") private Long id; /** * 分组名称 */ @Schema(description = "分组名称") private String groupName; /** * 分组描述 */ @Schema(description = "分组描述") @TableField(fill = FieldFill.INSERT) private String groupDesc; /** * 创建人 */ @TableField(fill = FieldFill.INSERT) @Schema(description = "创建人") private String createBy; /** * 修改人 */ @TableField(fill = FieldFill.UPDATE) @Schema(description = "修改人") private String updateBy; /** * 创建时间 */ @Schema(description = "创建时间") @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; /** * 修改时间 */ @Schema(description = "修改时间") @TableField(fill = FieldFill.UPDATE) private LocalDateTime updateTime; /** * 删除标识(0-正常,1-删除) */ @TableLogic @TableField(fill = FieldFill.INSERT) @Schema(description = "删除标记,1:已删除,0:正常") private String delFlag; } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/entity/GenTable.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.entity; 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.baomidou.mybatisplus.extension.activerecord.Model; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import java.time.LocalDateTime; import java.util.List; /** * 列属性 * * @author pigx code generator * @date 2023-02-06 20:34:55 */ @Data @TableName("gen_table") @EqualsAndHashCode(callSuper = true) @Schema(description = "列属性") public class GenTable extends Model { private static final long serialVersionUID = 1L; /** * id */ @TableId(type = IdType.ASSIGN_ID) @Schema(description = "id") private Long id; /** * 数据源名称 */ @Schema(description = "数据源名称") private String dsName; /** * 数据源类型 */ @Schema(description = "数据源类型") private String dbType; /** * 表名 */ @Schema(description = "表名") private String tableName; /** * 类名 */ @Schema(description = "类名") private String className; /** * 说明 */ @Schema(description = "说明") private String tableComment; /** * 作者 */ @Schema(description = "作者") private String author; /** * 邮箱 */ @Schema(description = "邮箱") private String email; /** * 项目包名 */ @Schema(description = "项目包名") private String packageName; /** * 项目版本号 */ @Schema(description = "项目版本号") private String version; /** * 生成方式 0:zip压缩包 1:自定义目录 */ @Schema(description = "生成方式 0:zip压缩包 1:自定义目录") private String generatorType; /** * 后端生成路径 */ @Schema(description = "后端生成路径") private String backendPath; /** * 前端生成路径 */ @Schema(description = "前端生成路径") private String frontendPath; /** * 模块名 */ @Schema(description = "模块名") private String moduleName; /** * 功能名 */ @Schema(description = "功能名") private String functionName; /** * 表单布局 1:一列 2:两列 */ @Schema(description = "表单布局 1:一列 2:两列") private Integer formLayout; /** * 基类ID */ @Schema(description = "基类ID") private Long baseclassId; /** * 创建时间 */ @Schema(description = "创建时间") private LocalDateTime createTime; /** * 代码生成风格 */ private Long style; /** * 子表名称 */ private String childTableName; /** * 主表关联键 */ private String mainField; /** * 子表关联键 */ private String childField; /** * 字段列表 */ @TableField(exist = false) private List fieldList; /** * 子表字段列表 */ @TableField(exist = false) private List childFieldList; /** * 代码风格(模版分组信息) */ @TableField(exist = false) private List groupList; } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/entity/GenTableColumnEntity.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.entity; import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.extension.activerecord.Model; import lombok.Data; import lombok.EqualsAndHashCode; /** * @author lengleng * @date 2023-02-06 * * 记录表字段的配置信息 */ @Data @TableName("gen_table_column") @EqualsAndHashCode(callSuper = true) public class GenTableColumnEntity extends Model { private static final long serialVersionUID = 1L; /** * 主键 */ @TableId(type = IdType.ASSIGN_ID) private Long id; /** * 数据源名 */ private String dsName; /** * 表名称 */ private String tableName; /** * 字段名称 */ private String fieldName; /** * 排序 */ private Integer sort; /** * 字段类型 */ private String fieldType; /** * 字段说明 */ private String fieldComment; /** * 属性名 */ private String attrName; /** * 属性类型 */ private String attrType; /** * 属性包名 */ private String packageName; /** * 自动填充 */ private String autoFill; /** * 主键 0:否 1:是 */ private String primaryPk; /** * 基类字段 0:否 1:是 */ private String baseField; /** * 表单项 0:否 1:是 */ private String formItem; /** * 表单必填 0:否 1:是 */ private String formRequired; /** * 表单类型 */ private String formType; /** * 表单效验 */ private String formValidator; /** * 列表项 0:否 1:是 */ private String gridItem; /** * 列表排序 0:否 1:是 */ private String gridSort; /** * 查询项 0:否 1:是 */ private String queryItem; /** * 查询方式 */ private String queryType; /** * 查询表单类型 */ private String queryFormType; /** * 字段字典类型 */ @TableField(updateStrategy = FieldStrategy.ALWAYS) private String fieldDict; } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/entity/GenTemplateEntity.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.entity; import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.extension.activerecord.Model; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import java.time.LocalDateTime; /** * 模板 * * @author PIG * @date 2023-02-21 17:15:44 */ @Data @TableName("gen_template") @EqualsAndHashCode(callSuper = true) @Schema(description = "模板") public class GenTemplateEntity extends Model { private static final long serialVersionUID = 1L; /** * 主键 */ @TableId(type = IdType.ASSIGN_ID) @Schema(description = "主键") private Long id; /** * 模板名称 */ @Schema(description = "模板名称") private String templateName; /** * 模板路径 */ @Schema(description = "模板路径") private String generatorPath; /** * 模板描述 */ @Schema(description = "模板描述") private String templateDesc; /** * 模板代码 */ @Schema(description = "模板代码") private String templateCode; /** * 创建人 */ @TableField(fill = FieldFill.INSERT) @Schema(description = "创建人") private String createBy; /** * 修改人 */ @TableField(fill = FieldFill.UPDATE) @Schema(description = "修改人") private String updateBy; /** * 创建时间 */ @Schema(description = "创建时间") @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; /** * 修改时间 */ @Schema(description = "修改时间") @TableField(fill = FieldFill.UPDATE) private LocalDateTime updateTime; /** * 删除标识(0-正常,1-删除) */ @TableLogic @TableField(fill = FieldFill.INSERT) @Schema(description = "删除标记,1:已删除,0:正常") private String delFlag; } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/entity/GenTemplateGroupEntity.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.entity; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.extension.activerecord.Model; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; /** * 模板分组关联表 * * @author PIG * @date 2023-02-22 09:25:15 */ @Data @TableName("gen_template_group") @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) @Schema(description = "模板分组关联表") public class GenTemplateGroupEntity extends Model { private static final long serialVersionUID = 1L; /** * 分组id */ @Schema(description = "分组id") private Long groupId; /** * 模板id */ @Schema(description = "模板id") private Long templateId; } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/entity/TableEntity.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.entity; import lombok.Data; import java.util.List; /** * @author lengleng * @date 2018/07/29 表属性: https://blog.csdn.net/lkforce/article/details/79557482 */ @Data public class TableEntity { /** * 名称 */ private String tableName; /** * 备注 */ private String comments; /** * 主键 */ private ColumnEntity pk; /** * 列名 */ private List columns; /** * 驼峰类型 */ private String caseClassName; /** * 普通类型 */ private String lowerClassName; /** * 数据库类型 (用于根据数据库个性化) */ private String dbType; } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/mapper/GenDatasourceConfMapper.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.pig4cloud.pig.codegen.entity.GenDatasourceConf; import org.apache.ibatis.annotations.Mapper; /** * 数据源表 Mapper 接口 * * @author lengleng * @date 2019-03-31 16:00:20 */ @Mapper public interface GenDatasourceConfMapper extends BaseMapper { } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/mapper/GenDynamicMapper.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.mapper; import com.baomidou.mybatisplus.annotation.InterceptorIgnore; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.util.LinkedHashMap; import java.util.List; /** * 动态查询Mapper接口 * * @author lengleng * @date 2025/05/31 */ @Mapper public interface GenDynamicMapper { /** * 动态SQL查询 * @param sq SQL查询语句 * @return 查询结果列表,每个结果以LinkedHashMap形式存储 */ @InterceptorIgnore(tenantLine = "true") List> dynamicQuerySql(@Param("value") String sq); } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/mapper/GenFieldTypeMapper.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.pig4cloud.pig.codegen.entity.GenFieldType; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.util.Set; /** * 字段类型映射器接口:用于操作字段类型相关数据库操作 * * @author lengleng * @date 2025/05/31 */ @Mapper public interface GenFieldTypeMapper extends BaseMapper { /** * 根据tableId,获取包列表 * @param dsName 数据源名称 * @param tableName 表名称 * @return 返回包列表 */ Set getPackageByTableId(@Param("dsName") String dsName, @Param("tableName") String tableName); } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/mapper/GenGroupMapper.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.pig4cloud.pig.codegen.entity.GenGroupEntity; import com.pig4cloud.pig.codegen.util.vo.GroupVO; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; /** * 模板分组 Mapper 接口 * * @author lengleng * @date 2025/05/31 */ @Mapper public interface GenGroupMapper extends BaseMapper { /** * 根据ID获取分组VO对象 * @param id 分组ID * @return 分组VO对象 */ GroupVO getGroupVoById(@Param("id") Long id); } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/mapper/GenTableColumnMapper.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.pig4cloud.pig.codegen.entity.GenTableColumnEntity; import org.apache.ibatis.annotations.Mapper; /** * 代码生成表列属性Mapper接口 * * @author lengleng * @date 2025/05/31 */ @Mapper public interface GenTableColumnMapper extends BaseMapper { } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/mapper/GenTableMapper.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.pig4cloud.pig.codegen.entity.GenTable; import org.apache.ibatis.annotations.Mapper; /** * 代码生成表 Mapper 接口 * * @author lengleng * @date 2025/05/31 */ @Mapper public interface GenTableMapper extends BaseMapper { } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/mapper/GenTemplateGroupMapper.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.pig4cloud.pig.codegen.entity.GenTemplateGroupEntity; import org.apache.ibatis.annotations.Mapper; /** * 模板分组关联表 Mapper 接口 * * @author lengleng * @date 2025/05/31 */ @Mapper public interface GenTemplateGroupMapper extends BaseMapper { } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/mapper/GenTemplateMapper.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.pig4cloud.pig.codegen.entity.GenTemplateEntity; import org.apache.ibatis.annotations.Mapper; import java.util.List; /** * 代码生成模板Mapper接口 * * @author lengleng * @date 2025/05/31 */ @Mapper public interface GenTemplateMapper extends BaseMapper { /** * 根据模板组ID查询模板列表 * @param groupId 模板组ID * @return 模板实体列表 */ List listTemplateById(Long groupId); } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/GenDatasourceConfService.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.service; import com.baomidou.mybatisplus.extension.service.IService; import com.pig4cloud.pig.codegen.entity.GenDatasourceConf; /** * 数据源配置服务接口 提供数据源的增删改查及校验等功能 * * @author lengleng * @date 2025/05/31 */ public interface GenDatasourceConfService extends IService { /** * 保存数据源并加密 * @param genDatasourceConf 数据源配置信息 * @return 保存是否成功 */ Boolean saveDsByEnc(GenDatasourceConf genDatasourceConf); /** * 更新数据源 * @param genDatasourceConf 数据源配置信息 * @return 更新是否成功 */ Boolean updateDsByEnc(GenDatasourceConf genDatasourceConf); /** * 添加动态数据源 * @param datasourceConf 数据源配置信息 */ void addDynamicDataSource(GenDatasourceConf datasourceConf); /** * 校验数据源配置是否有效 * @param datasourceConf 数据源配置信息 * @return true表示有效,false表示无效 */ Boolean checkDataSource(GenDatasourceConf datasourceConf); /** * 通过数据源ID删除数据源 * @param dsIds 数据源ID数组 * @return 删除是否成功 */ Boolean removeByDsId(Long[] dsIds); } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/GenFieldTypeService.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.service; import com.baomidou.mybatisplus.extension.service.IService; import com.pig4cloud.pig.codegen.entity.GenFieldType; import java.util.Set; /** * 列属性服务接口 * * @author lengleng * @date 2025/05/31 */ public interface GenFieldTypeService extends IService { /** * 根据tableId,获取包列表 * @param dsName 数据源名称 * @param tableName 表名称 * @return 返回包列表 */ Set getPackageByTableId(String dsName, String tableName); } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/GenGroupService.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.service; import com.baomidou.mybatisplus.extension.service.IService; import com.pig4cloud.pig.codegen.entity.GenGroupEntity; import com.pig4cloud.pig.codegen.util.vo.GroupVO; import com.pig4cloud.pig.codegen.util.vo.TemplateGroupDTO; /** * 模板分组服务接口 * * @author lengleng * @date 2025/05/31 */ public interface GenGroupService extends IService { /** * 保存生成模板组 * @param genTemplateGroup 模板组DTO对象 */ void saveGenGroup(TemplateGroupDTO genTemplateGroup); /** * 删除分组极其关系 * @param ids */ void delGroupAndTemplate(Long[] ids); /** * 查询group数据 * @param id */ GroupVO getGroupVoById(Long id); /** * 更新group数据 * @param GroupVo */ void updateGroupAndTemplateById(GroupVO GroupVo); } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/GenTableColumnService.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.service; import com.baomidou.mybatisplus.extension.service.IService; import com.pig4cloud.pig.codegen.entity.GenTableColumnEntity; import java.util.List; /** * 代码生成表列服务接口 * * @author lengleng * @date 2025/05/31 */ public interface GenTableColumnService extends IService { /** * 初始化字段列表 * @param tableFieldList 表字段实体列表 */ void initFieldList(List tableFieldList); /** * 更新表字段信息 * @param dsName 数据源名称 * @param tableName 表名 * @param tableFieldList 表字段列表 */ void updateTableField(String dsName, String tableName, List tableFieldList); } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/GenTableService.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.service; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; import com.pig4cloud.pig.codegen.entity.GenTable; import org.anyline.metadata.Table; import java.util.List; /** * 代码生成表服务接口 * * @author lengleng * @date 2025/05/31 */ public interface GenTableService extends IService { /** * 查询对应数据源的表 * @param page 分页信息 * @param table 查询条件 * @return 表 */ IPage queryTablePage(Page page, GenTable table); /** * 查询表信息(列),然后插入到中间表中 * @param dsName 数据源 * @param tableName 表名 * @return GenTable */ GenTable queryOrBuildTable(String dsName, String tableName); /** * 查询表ddl 语句 * @param dsName 数据源名称 * @param tableName 表名称 * @return ddl 语句 * @throws Exception */ String queryTableDdl(String dsName, String tableName) throws Exception; /** * 查询数据源里面的全部表 * @param dsName 数据源名称 * @return table */ List queryTableList(String dsName); /** * 查询表的全部字段 * @param dsName 数据源 * @param tableName 表名称 * @return column */ List queryTableColumn(String dsName, String tableName); } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/GenTemplateGroupService.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.service; import com.baomidou.mybatisplus.extension.service.IService; import com.pig4cloud.pig.codegen.entity.GenTemplateGroupEntity; /** * 模板分组关联服务接口 * * @author lengleng * @date 2025/05/31 */ public interface GenTemplateGroupService extends IService { } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/GenTemplateService.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.service; import com.baomidou.mybatisplus.extension.service.IService; import com.pig4cloud.pig.codegen.entity.GenTemplateEntity; import com.pig4cloud.pig.common.core.util.R; /** * 代码生成模板服务接口 * * @author lengleng * @date 2025/05/31 */ public interface GenTemplateService extends IService { /** * 检查版本信息 * @return 返回检查结果,包含版本信息 */ R checkVersion(); /** * 在线更新 * @return 更新结果 */ R onlineUpdate(); } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/GeneratorService.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.service; import java.util.List; import java.util.Map; import java.util.zip.ZipOutputStream; /** * 代码生成服务接口 * * @author lengleng * @date 2025/05/31 */ public interface GeneratorService { /** * 生成代码zip写出 * @param tableId 表 * @param zip 输出流 */ void downloadCode(Long tableId, ZipOutputStream zip); /** * 预览代码 * @param tableId 表 * @return [{模板名称:渲染结果}] */ List> preview(Long tableId); /** * 目标目录写入渲染结果 * @param tableId 表 */ void generatorCode(Long tableId); } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/impl/GenDatasourceConfServiceImpl.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.service.impl; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.dynamic.datasource.DynamicRoutingDataSource; import com.baomidou.dynamic.datasource.creator.DataSourceCreator; import com.baomidou.dynamic.datasource.creator.DataSourceProperty; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.pig4cloud.pig.codegen.entity.GenDatasourceConf; import com.pig4cloud.pig.codegen.mapper.GenDatasourceConfMapper; import com.pig4cloud.pig.codegen.service.GenDatasourceConfService; import com.pig4cloud.pig.common.core.util.SpringContextHolder; import com.pig4cloud.pig.common.datasource.util.DsConfTypeEnum; import com.pig4cloud.pig.common.datasource.util.DsJdbcUrlEnum; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.jasypt.encryption.StringEncryptor; import org.springframework.stereotype.Service; import javax.sql.DataSource; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; /** * 数据源配置服务实现类 * *

* 提供数据源的增删改查及校验功能,支持数据源密码加密存储 *

* * @author lengleng * @date 2025/05/31 */ @Slf4j @Service @RequiredArgsConstructor public class GenDatasourceConfServiceImpl extends ServiceImpl implements GenDatasourceConfService { private final StringEncryptor stringEncryptor; private final DataSourceCreator hikariDataSourceCreator; /** * 保存数据源配置并进行加密处理 * @param conf 数据源配置信息 * @return 保存成功返回true,失败返回false */ @Override public Boolean saveDsByEnc(GenDatasourceConf conf) { // 校验配置合法性 if (!checkDataSource(conf)) { return Boolean.FALSE; } // 添加动态数据源 addDynamicDataSource(conf); // 更新数据库配置 conf.setPassword(stringEncryptor.encrypt(conf.getPassword())); this.baseMapper.insert(conf); return Boolean.TRUE; } /** * 更新加密数据源 * @param conf 数据源配置信息 * @return 更新成功返回true,失败返回false */ @Override public Boolean updateDsByEnc(GenDatasourceConf conf) { if (!checkDataSource(conf)) { return Boolean.FALSE; } // 先移除 DynamicRoutingDataSource dynamicRoutingDataSource = SpringContextHolder.getBean(DynamicRoutingDataSource.class); dynamicRoutingDataSource.removeDataSource(baseMapper.selectById(conf.getId()).getName()); // 再添加 addDynamicDataSource(conf); // 更新数据库配置 if (StrUtil.isNotBlank(conf.getPassword())) { conf.setPassword(stringEncryptor.encrypt(conf.getPassword())); } this.baseMapper.updateById(conf); return Boolean.TRUE; } /** * 通过数据源ID删除数据源 * @param dsIds 数据源ID数组 * @return 删除是否成功 */ @Override public Boolean removeByDsId(Long[] dsIds) { DynamicRoutingDataSource dynamicRoutingDataSource = SpringContextHolder.getBean(DynamicRoutingDataSource.class); this.baseMapper.selectByIds(CollUtil.toList(dsIds)) .forEach(ds -> dynamicRoutingDataSource.removeDataSource(ds.getName())); this.baseMapper.deleteByIds(CollUtil.toList(dsIds)); return Boolean.TRUE; } /** * 添加动态数据源 * @param conf 数据源配置信息 */ @Override public void addDynamicDataSource(GenDatasourceConf conf) { DataSourceProperty dataSourceProperty = new DataSourceProperty(); dataSourceProperty.setPoolName(conf.getName()); dataSourceProperty.setUrl(conf.getUrl()); dataSourceProperty.setUsername(conf.getUsername()); dataSourceProperty.setPassword(conf.getPassword()); DataSource dataSource = hikariDataSourceCreator.createDataSource(dataSourceProperty); DynamicRoutingDataSource dynamicRoutingDataSource = SpringContextHolder.getBean(DynamicRoutingDataSource.class); dynamicRoutingDataSource.addDataSource(dataSourceProperty.getPoolName(), dataSource); } /** * 校验数据源配置是否有效 * @param conf 数据源配置信息 * @return 数据源配置是否有效,true表示有效 * @throws RuntimeException 数据库连接失败时抛出异常 */ @Override public Boolean checkDataSource(GenDatasourceConf conf) { String url; // JDBC 配置形式 if (DsConfTypeEnum.JDBC.getType().equals(conf.getConfType())) { url = conf.getUrl(); } else if (DsJdbcUrlEnum.MSSQL.getDbName().equals(conf.getDsType())) { // 主机形式 sql server 特殊处理 DsJdbcUrlEnum urlEnum = DsJdbcUrlEnum.get(conf.getDsType()); url = String.format(urlEnum.getUrl(), conf.getHost(), conf.getPort(), conf.getDsName()); } else { DsJdbcUrlEnum urlEnum = DsJdbcUrlEnum.get(conf.getDsType()); url = String.format(urlEnum.getUrl(), conf.getHost(), conf.getPort(), conf.getDsName()); } conf.setUrl(url); try (Connection connection = DriverManager.getConnection(url, conf.getUsername(), conf.getPassword())) { } catch (SQLException e) { log.error("数据源配置 {} , 获取链接失败", conf.getName(), e); throw new RuntimeException("数据库配置错误,链接失败"); } return Boolean.TRUE; } } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/impl/GenFieldTypeServiceImpl.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.service.impl; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.pig4cloud.pig.codegen.entity.GenFieldType; import com.pig4cloud.pig.codegen.mapper.GenFieldTypeMapper; import com.pig4cloud.pig.codegen.service.GenFieldTypeService; import org.springframework.stereotype.Service; import java.util.Set; import java.util.stream.Collectors; /** * 列属性 * * @author pigx code generator * @date 2023-02-06 20:16:01 */ @Service public class GenFieldTypeServiceImpl extends ServiceImpl implements GenFieldTypeService { /** * 根据tableId,获取包列表 * @param dsName 数据源名称 * @param tableName 表名称 * @return 返回包列表 */ @Override public Set getPackageByTableId(String dsName, String tableName) { Set importList = baseMapper.getPackageByTableId(dsName, tableName); return importList.stream().filter(StrUtil::isNotBlank).collect(Collectors.toSet()); } } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/impl/GenGroupServiceImpl.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.service.impl; import java.util.LinkedList; import java.util.List; import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.pig4cloud.pig.codegen.entity.GenGroupEntity; import com.pig4cloud.pig.codegen.entity.GenTemplateGroupEntity; import com.pig4cloud.pig.codegen.mapper.GenGroupMapper; import com.pig4cloud.pig.codegen.service.GenGroupService; import com.pig4cloud.pig.codegen.service.GenTemplateGroupService; import com.pig4cloud.pig.codegen.util.vo.GroupVO; import com.pig4cloud.pig.codegen.util.vo.TemplateGroupDTO; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import lombok.AllArgsConstructor; /** * 模板分组服务实现类 * * @author lengleng * @date 2025/05/31 */ @Service @AllArgsConstructor public class GenGroupServiceImpl extends ServiceImpl implements GenGroupService { private final GenTemplateGroupService genTemplateGroupService; /** * 保存模板分组信息 * @param genTemplateGroup 模板分组DTO对象,包含分组信息及关联模板ID列表 */ @Override public void saveGenGroup(TemplateGroupDTO genTemplateGroup) { // 1.保存group GenGroupEntity groupEntity = new GenGroupEntity(); BeanUtil.copyProperties(genTemplateGroup, groupEntity); baseMapper.insert(groupEntity); // 2.保存关系 List goals = new LinkedList<>(); for (Long TemplateId : genTemplateGroup.getTemplateId()) { GenTemplateGroupEntity templateGroup = new GenTemplateGroupEntity(); templateGroup.setTemplateId(TemplateId).setGroupId(groupEntity.getId()); goals.add(templateGroup); } genTemplateGroupService.saveBatch(goals); } /** * 按照分组ID数组删除分组及其关联模板 * @param ids 分组ID数组 */ @Override public void delGroupAndTemplate(Long[] ids) { // 删除分组 this.removeBatchByIds(CollUtil.toList(ids)); // 删除关系 genTemplateGroupService.remove(Wrappers.lambdaQuery() .in(GenTemplateGroupEntity::getGroupId, CollUtil.toList(ids))); } /** * 根据ID查询组信息 * @param id 组ID * @return 组信息视图对象 */ @Override public GroupVO getGroupVoById(Long id) { return baseMapper.getGroupVoById(id); } /** * 根据ID更新分组及其关联模板 * @param groupVo 分组VO对象,包含分组ID和模板ID列表 */ @Override public void updateGroupAndTemplateById(GroupVO groupVo) { // 1.更新自身 GenGroupEntity groupEntity = new GenGroupEntity(); BeanUtil.copyProperties(groupVo, groupEntity); this.updateById(groupEntity); // 2.更新模板 // 2.1根据id删除之前的模板 genTemplateGroupService.remove( Wrappers.lambdaQuery().eq(GenTemplateGroupEntity::getGroupId, groupVo.getId())); // 2.2根据ids创建新的模板分组赋值 List goals = new LinkedList<>(); for (Long templateId : groupVo.getTemplateId()) { goals.add(new GenTemplateGroupEntity().setGroupId(groupVo.getId()).setTemplateId(templateId)); } genTemplateGroupService.saveBatch(goals); } } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/impl/GenTableColumnServiceImpl.java ================================================ package com.pig4cloud.pig.codegen.service.impl; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.pig4cloud.pig.codegen.entity.GenFieldType; import com.pig4cloud.pig.codegen.entity.GenTableColumnEntity; import com.pig4cloud.pig.codegen.mapper.GenFieldTypeMapper; import com.pig4cloud.pig.codegen.mapper.GenTableColumnMapper; import com.pig4cloud.pig.codegen.service.GenTableColumnService; import cn.hutool.core.text.NamingCase; import lombok.RequiredArgsConstructor; /** * 表字段信息管理服务实现类 * * @author lengleng * @date 2025/05/31 */ @Service @RequiredArgsConstructor public class GenTableColumnServiceImpl extends ServiceImpl implements GenTableColumnService { private final GenFieldTypeMapper fieldTypeMapper; /** * 初始化表单字段列表,主要是将数据库表中的字段转化为表单需要的字段数据格式,并为审计字段排序 * @param tableFieldList 表单字段列表 */ public void initFieldList(List tableFieldList) { // 字段类型、属性类型映射 List list = fieldTypeMapper.selectList(Wrappers.emptyWrapper()); Map fieldTypeMap = new LinkedHashMap<>(list.size()); list.forEach( fieldTypeMapping -> fieldTypeMap.put(fieldTypeMapping.getColumnType().toLowerCase(), fieldTypeMapping)); // 索引计数器 AtomicInteger index = new AtomicInteger(0); tableFieldList.forEach(field -> { // 将字段名转化为驼峰格式 field.setAttrName(NamingCase.toCamelCase(field.getFieldName())); // 获取字段对应的类型 GenFieldType fieldTypeMapping = fieldTypeMap.getOrDefault(field.getFieldType().toLowerCase(), null); if (fieldTypeMapping == null) { // 没找到对应的类型,则为Object类型 field.setAttrType("Object"); } else { field.setAttrType(fieldTypeMapping.getAttrType()); field.setPackageName(fieldTypeMapping.getPackageName()); } // 设置查询类型和表单查询类型都为“=” field.setQueryType("="); field.setQueryFormType("text"); // 设置表单类型为文本框类型 field.setFormType("text"); // 保证审计字段最后显示 field.setSort(Objects.isNull(field.getSort()) ? index.getAndIncrement() : field.getSort()); }); } /** * 更新指定数据源和表名的表单字段信息 * @param dsName 数据源名称 * @param tableName 表名 * @param tableFieldList 表单字段列表 */ @Override public void updateTableField(String dsName, String tableName, List tableFieldList) { AtomicInteger sort = new AtomicInteger(); this.updateBatchById(tableFieldList.stream().peek(field -> field.setSort(sort.getAndIncrement())).toList()); } } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/impl/GenTableServiceImpl.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.service.impl; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.text.NamingCase; import cn.hutool.core.util.EnumUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.pig4cloud.pig.codegen.config.PigCodeGenDefaultProperties; import com.pig4cloud.pig.codegen.entity.GenGroupEntity; import com.pig4cloud.pig.codegen.entity.GenTable; import com.pig4cloud.pig.codegen.entity.GenTableColumnEntity; import com.pig4cloud.pig.codegen.mapper.GenTableMapper; import com.pig4cloud.pig.codegen.service.GenGroupService; import com.pig4cloud.pig.codegen.service.GenTableColumnService; import com.pig4cloud.pig.codegen.service.GenTableService; import com.pig4cloud.pig.codegen.util.AutoFillEnum; import com.pig4cloud.pig.codegen.util.BoolFillEnum; import com.pig4cloud.pig.codegen.util.CommonColumnFiledEnum; import com.pig4cloud.pig.codegen.util.GenKit; import lombok.RequiredArgsConstructor; import org.anyline.metadata.Column; import org.anyline.metadata.Database; import org.anyline.metadata.Table; import org.anyline.proxy.CacheProxy; import org.anyline.proxy.ServiceProxy; import org.anyline.service.AnylineService; import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Objects; /** * 代码生成表服务实现类 * * @author lengleng * @date 2025/05/31 */ @Service @RequiredArgsConstructor public class GenTableServiceImpl extends ServiceImpl implements GenTableService { private final PigCodeGenDefaultProperties configurationProperties; private final GenTableColumnService columnService; private final GenGroupService genGroupService; /** * 查询表ddl 语句 * @param dsName 数据源名称 * @param tableName 表名称 * @return ddl 语句 * @throws Exception */ @Override public String queryTableDdl(String dsName, String tableName) throws Exception { // 手动切换数据源 DynamicDataSourceContextHolder.push(dsName); Table table = ServiceProxy.metadata().table(tableName); // 获取表结构 table.execute(false);// 不执行SQL ServiceProxy.ddl().create(table); return table.getDdl();// 返回创建表的DDL } /** * 查询表的全部字段 * @param dsName 数据源 * @param tableName 表名称 * @return column */ @Override public List queryTableColumn(String dsName, String tableName) { // 手动切换数据源 DynamicDataSourceContextHolder.push(dsName); CacheProxy.clear(); return ServiceProxy.metadata().columns(tableName).values().stream().map(Column::getName).toList(); } /** * 查询对应数据源的表 * @param page 分页信息 * @param table 查询条件 * @return 表 */ @Override public IPage queryTablePage(Page
page, GenTable table) { // 手动切换数据源 DynamicDataSourceContextHolder.push(table.getDsName()); CacheProxy.clear(); List
tableList = ServiceProxy.metadata().tables().values().stream().filter(t -> { if (StrUtil.isBlank(table.getTableName())) { return true; } return StrUtil.containsIgnoreCase(t.getName(false), table.getTableName()); }).toList(); // 根据 page 进行分页 List
records = CollUtil.page((int) page.getCurrent() - 1, (int) page.getSize(), tableList); page.setTotal(tableList.size()); page.setRecords(records); return page; } /** * 查询数据源里面的全部表 * @param dsName 数据源名称 * @return table */ @Override public List queryTableList(String dsName) { // 手动切换数据源 DynamicDataSourceContextHolder.push(dsName); CacheProxy.clear(); return ServiceProxy.metadata().tables().values().stream().map(Table::getName).toList(); } /** * 查询表信息(列),然后插入到中间表中 * @param dsName 数据源 * @param tableName 表名 * @return GenTable */ @Override public GenTable queryOrBuildTable(String dsName, String tableName) { GenTable genTable = baseMapper.selectOne( Wrappers.lambdaQuery().eq(GenTable::getTableName, tableName).eq(GenTable::getDsName, dsName)); // 如果 genTable 为空, 执行导入 if (Objects.isNull(genTable)) { genTable = this.tableImport(dsName, tableName); } List fieldList = columnService.list(Wrappers.lambdaQuery() .eq(GenTableColumnEntity::getDsName, dsName) .eq(GenTableColumnEntity::getTableName, tableName) .orderByAsc(GenTableColumnEntity::getSort)); genTable.setFieldList(fieldList); // 查询模板分组信息 List groupEntities = genGroupService .list(Wrappers.lambdaQuery().orderByDesc(GenGroupEntity::getCreateTime)); genTable.setGroupList(groupEntities); return genTable; } /** * 导入表结构并生成代码配置 * @param dsName 数据源名称 * @param tableName 表名 * @return 生成的表配置信息 * @Transactional 启用事务,遇到异常时回滚 */ @Transactional(rollbackFor = Exception.class) protected GenTable tableImport(String dsName, String tableName) { // 手动切换数据源 DynamicDataSourceContextHolder.push(dsName); // 查询表是否存在 GenTable table = new GenTable(); // 从数据库获取表信息 CacheProxy.clear(); AnylineService service = ServiceProxy.service(); Table tableMetadata = service.metadata().table(tableName); Database database = service.metadata().database(); // 获取默认表配置信息 () table.setPackageName(configurationProperties.getPackageName()); table.setVersion(configurationProperties.getVersion()); table.setBackendPath(configurationProperties.getBackendPath()); table.setFrontendPath(configurationProperties.getFrontendPath()); table.setAuthor(configurationProperties.getAuthor()); table.setEmail(configurationProperties.getEmail()); table.setTableName(tableName); table.setDsName(dsName); table.setTableComment(tableMetadata.getComment()); table.setDbType(database.getDatabase().title()); table.setFormLayout(configurationProperties.getFormLayout()); table.setGeneratorType(configurationProperties.getGeneratorType()); table.setClassName(NamingCase.toPascalCase(tableName)); // 模块名称默认为 admin table.setModuleName(configurationProperties.getModuleName()); table.setFunctionName(GenKit.getFunctionName(tableName)); table.setCreateTime(LocalDateTime.now()); // 使用默认数据源 DynamicDataSourceContextHolder.clear(); this.save(table); // 获取原生字段数据 List tableFieldList = getGenTableColumnEntities(dsName, tableName, tableMetadata); // 初始化字段数据 columnService.initFieldList(tableFieldList); // 保存列数据 columnService.saveOrUpdateBatch(tableFieldList); table.setFieldList(tableFieldList); return table; } /** * 获取表字段信息 * @param dsName 数据源信息 * @param tableName 表名称 * @param tableMetadata 表的元数据 * @return list */ private static @NotNull List getGenTableColumnEntities(String dsName, String tableName, Table tableMetadata) { List tableFieldList = new ArrayList<>(); LinkedHashMap columns = tableMetadata.getColumns(); columns.forEach((columnName, column) -> { GenTableColumnEntity genTableColumnEntity = new GenTableColumnEntity(); genTableColumnEntity.setTableName(tableName); genTableColumnEntity.setDsName(dsName); genTableColumnEntity.setFieldName(column.getName()); genTableColumnEntity.setFieldComment(column.getComment()); genTableColumnEntity.setFieldType(column.getTypeName()); genTableColumnEntity.setPrimaryPk( column.isPrimaryKey() == 1 ? BoolFillEnum.TRUE.getValue() : BoolFillEnum.FALSE.getValue()); genTableColumnEntity.setAutoFill(AutoFillEnum.DEFAULT.name()); genTableColumnEntity.setFormItem(BoolFillEnum.TRUE.getValue()); genTableColumnEntity.setGridItem(BoolFillEnum.TRUE.getValue()); // 审计字段处理 if (EnumUtil.contains(CommonColumnFiledEnum.class, column.getName())) { CommonColumnFiledEnum commonColumnFiledEnum = CommonColumnFiledEnum.valueOf(column.getName()); genTableColumnEntity.setFormItem(commonColumnFiledEnum.getFormItem()); genTableColumnEntity.setGridItem(commonColumnFiledEnum.getGridItem()); genTableColumnEntity.setAutoFill(commonColumnFiledEnum.getAutoFill()); genTableColumnEntity.setSort(commonColumnFiledEnum.getSort()); } tableFieldList.add(genTableColumnEntity); }); return tableFieldList; } } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/impl/GenTemplateGroupServiceImpl.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.pig4cloud.pig.codegen.entity.GenTemplateGroupEntity; import com.pig4cloud.pig.codegen.mapper.GenTemplateGroupMapper; import com.pig4cloud.pig.codegen.service.GenTemplateGroupService; import org.springframework.stereotype.Service; /** * 模板分组关联表服务实现类 * * @author lengleng * @date 2025/05/31 */ @Service public class GenTemplateGroupServiceImpl extends ServiceImpl implements GenTemplateGroupService { } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/impl/GenTemplateServiceImpl.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.service.impl; import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpStatus; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import cn.smallbun.screw.core.constant.DefaultConstants; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.pig4cloud.pig.codegen.config.PigCodeGenDefaultProperties; import com.pig4cloud.pig.codegen.entity.GenGroupEntity; import com.pig4cloud.pig.codegen.entity.GenTemplateEntity; import com.pig4cloud.pig.codegen.entity.GenTemplateGroupEntity; import com.pig4cloud.pig.codegen.mapper.GenGroupMapper; import com.pig4cloud.pig.codegen.mapper.GenTemplateGroupMapper; import com.pig4cloud.pig.codegen.mapper.GenTemplateMapper; import com.pig4cloud.pig.codegen.service.GenTemplateService; import com.pig4cloud.pig.codegen.util.vo.GenTemplateFileVO; import com.pig4cloud.pig.common.core.exception.CheckedException; import com.pig4cloud.pig.common.core.util.R; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * 代码生成模板服务实现类 * * @author lengleng * @date 2025/05/31 */ @Slf4j @Service @RequiredArgsConstructor public class GenTemplateServiceImpl extends ServiceImpl implements GenTemplateService { private final GenTemplateGroupMapper genTemplateGroupMapper; private final GenGroupMapper genGroupMapper; private final PigCodeGenDefaultProperties defaultProperties; /** * 在线更新模板组 * @return 更新结果,包含成功或失败信息 * @throws Exception 事务执行过程中发生异常时抛出 */ @Override @Transactional(rollbackFor = Exception.class) public R onlineUpdate() { // 获取 config.json 和 version 文件 Map configAndVersion = getConfigAndVersion(); JSONObject configJsonObj = (JSONObject) configAndVersion.get("configJsonObj"); String versionFile = (String) configAndVersion.get("versionFile"); // 查询出全部的模板组名称 Set cgtmConfigGroupNames = configJsonObj.keySet(); String cgtmConfigGroupName = cgtmConfigGroupNames.iterator().next(); // 根据模板组名称+version 查询是否存在,不存在则新增,存在跳过 boolean exists = genGroupMapper.exists(Wrappers.lambdaQuery() .eq(GenGroupEntity::getGroupName, cgtmConfigGroupName + versionFile)); if (exists) { return R.failed("已是最新版本,无需更新!"); } // 插入新的模板组(名称 + VERSION), 再解析 config.json group 里面的所有模板 insertTemplateFiles(versionFile, configJsonObj, cgtmConfigGroupName); return R.ok("更新成功,版本号:" + versionFile); } /** * 检查版本 * @return 返回检查结果,包含版本是否存在信息 */ public R checkVersion() { // 关闭在线更新提示 if (!defaultProperties.isAutoCheckVersion()) { return R.ok(true); } // 获取 config.json 和 version 文件 Map configAndVersion = getConfigAndVersion(); JSONObject configJsonObj = (JSONObject) configAndVersion.get("configJsonObj"); String versionFile = (String) configAndVersion.get("versionFile"); // 查询出全部的模板组名称 Set cgtmConfigGroupNames = configJsonObj.keySet(); String cgtmConfigGroupName = cgtmConfigGroupNames.iterator().next(); // 根据模板组名称+version 查询是否存在,不存在则新增,存在跳过 boolean exists = genGroupMapper.exists(Wrappers.lambdaQuery() .eq(GenGroupEntity::getGroupName, cgtmConfigGroupName + versionFile)); return R.ok(exists); } /** * 获取配置和版本 * @return {@link Map }<{@link String }, {@link Object }> */ private Map getConfigAndVersion() { // 获取 config.json 和 version 文件 String configFile = getCGTMFile("config.json"); String versionFile = getCGTMFile("VERSION"); // 解析 config.json JSONObject configJsonObj = JSONUtil.parseObj(configFile); // 将 configJsonObj 和 versionFile 放入 Map 中 Map configAndVersion = new HashMap<>(); configAndVersion.put("configJsonObj", configJsonObj); configAndVersion.put("versionFile", versionFile); return configAndVersion; } /** * 插入模板文件 * @param version 版本 * @param configJsonObj config.json * @param groupName 组名称 */ private void insertTemplateFiles(String version, JSONObject configJsonObj, String groupName) { // 创建新的 group GenGroupEntity genGroupEntity = new GenGroupEntity(); genGroupEntity.setGroupName(groupName + version); genGroupMapper.insert(genGroupEntity); // 解析json配置文件 List templateFileVOList = configJsonObj.getBeanList(groupName, GenTemplateFileVO.class); for (GenTemplateFileVO genTemplateFileVO : templateFileVOList) { // 1. 获取模板文件 String templateFile = getCGTMFile(genTemplateFileVO.getTemplateFile()); // 2. 插入模板文件 GenTemplateEntity genTemplateEntity = new GenTemplateEntity(); genTemplateEntity.setTemplateName(genTemplateFileVO.getTemplateName() + version); genTemplateEntity.setTemplateDesc(genTemplateFileVO.getTemplateName() + version); genTemplateEntity.setTemplateCode(templateFile); genTemplateEntity.setGeneratorPath(genTemplateFileVO.getGeneratorPath()); baseMapper.insert(genTemplateEntity); // 3. 插入模板组关联 GenTemplateGroupEntity genTemplateGroupEntity = new GenTemplateGroupEntity(); genTemplateGroupEntity.setTemplateId(genTemplateEntity.getId()); genTemplateGroupEntity.setGroupId(genGroupEntity.getId()); genTemplateGroupMapper.insert(genTemplateGroupEntity); } } /** * 获取 cgtmfile * @param fileName 文件名 * @return {@link String } */ private String getCGTMFile(String fileName) { HttpResponse response = HttpRequest .get(String.format("%s/CGTM/raw/next/%s", DefaultConstants.CGTM_URL, fileName)) .execute(); if (response.getStatus() == HttpStatus.HTTP_OK || StrUtil.isNotBlank(response.body())) { return response.body(); } else { log.warn("在线更新模板失败:{} ,Http Code:{}", fileName, response.getStatus()); throw new CheckedException("在线更新模板失败,任务终止!"); } } } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/service/impl/GeneratorServiceImpl.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.codegen.service.impl; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.springframework.boot.SpringBootVersion; import org.springframework.stereotype.Service; import com.pig4cloud.pig.codegen.config.PigCodeGenDefaultProperties; import com.pig4cloud.pig.codegen.entity.GenTable; import com.pig4cloud.pig.codegen.entity.GenTableColumnEntity; import com.pig4cloud.pig.codegen.entity.GenTemplateEntity; import com.pig4cloud.pig.codegen.service.GenFieldTypeService; import com.pig4cloud.pig.codegen.service.GenGroupService; import com.pig4cloud.pig.codegen.service.GenTableColumnService; import com.pig4cloud.pig.codegen.service.GenTableService; import com.pig4cloud.pig.codegen.service.GeneratorService; import com.pig4cloud.pig.codegen.util.VelocityKit; import com.pig4cloud.pig.codegen.util.vo.GroupVO; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.text.NamingCase; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.StrUtil; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; /** * 代码生成器服务实现类 * * @author lengleng * @date 2025/05/31 */ @Service @RequiredArgsConstructor public class GeneratorServiceImpl implements GeneratorService { private final PigCodeGenDefaultProperties configurationProperties; private final GenTableColumnService columnService; private final GenFieldTypeService fieldTypeService; private final GenTableService tableService; private final GenGroupService genGroupService; /** * 生成代码zip写出 * @param tableId 表 * @param zip 输出流 */ @Override @SneakyThrows public void downloadCode(Long tableId, ZipOutputStream zip) { // 数据模型 Map dataModel = getDataModel(tableId); Long style = (Long) dataModel.get("style"); GroupVO groupVo = genGroupService.getGroupVoById(style); List templateList = groupVo.getTemplateList(); String frontendPath = configurationProperties.getFrontendPath(); String backendPath = configurationProperties.getBackendPath(); for (GenTemplateEntity template : templateList) { String templateCode = template.getTemplateCode(); String generatorPath = template.getGeneratorPath(); dataModel.put("frontendPath", frontendPath); dataModel.put("backendPath", backendPath); String content = VelocityKit.renderStr(templateCode, dataModel); String path = VelocityKit.renderStr(generatorPath, dataModel); // 添加到zip zip.putNextEntry(new ZipEntry(path)); IoUtil.writeUtf8(zip, false, content); zip.flush(); zip.closeEntry(); } } /** * 表达式优化的预览代码方法 * @param tableId 表 * @return [{模板名称:渲染结果}] */ @Override @SneakyThrows public List> preview(Long tableId) { // 数据模型 Map dataModel = getDataModel(tableId); Long style = (Long) dataModel.get("style"); // 获取模板列表,Lambda 表达式简化代码 List templateList = genGroupService.getGroupVoById(style).getTemplateList(); String frontendPath = configurationProperties.getFrontendPath(); String backendPath = configurationProperties.getBackendPath(); return templateList.stream().map(template -> { String templateCode = template.getTemplateCode(); String generatorPath = template.getGeneratorPath(); // 预览模式下, 使用相对路径展示 dataModel.put("frontendPath", frontendPath); dataModel.put("backendPath", backendPath); String content = VelocityKit.renderStr(templateCode, dataModel); String path = VelocityKit.renderStr(generatorPath, dataModel); // 使用 map 简化代码 return new HashMap(4) { private static final long serialVersionUID = 1L; { put("code", content); put("codePath", path); } }; }).collect(Collectors.toList()); } /** * 目标目录写入渲染结果方法 * @param tableId 表 */ @Override public void generatorCode(Long tableId) { // 数据模型 Map dataModel = getDataModel(tableId); Long style = (Long) dataModel.get("style"); // 获取模板列表,Lambda 表达式简化代码 List templateList = genGroupService.getGroupVoById(style).getTemplateList(); templateList.forEach(template -> { String templateCode = template.getTemplateCode(); String generatorPath = template.getGeneratorPath(); String content = VelocityKit.renderStr(templateCode, dataModel); String path = VelocityKit.renderStr(generatorPath, dataModel); FileUtil.writeUtf8String(content, path); }); } /** * 通过 Lambda 表达式优化的获取数据模型方法 * @param tableId 表格 ID * @return 数据模型 Map 对象 */ private Map getDataModel(Long tableId) { // 获取表格信息 GenTable table = tableService.getById(tableId); // 获取字段列表 List fieldList = columnService.lambdaQuery() .eq(GenTableColumnEntity::getDsName, table.getDsName()) .eq(GenTableColumnEntity::getTableName, table.getTableName()) .orderByAsc(GenTableColumnEntity::getSort) .list(); table.setFieldList(fieldList); // 创建数据模型对象 Map dataModel = new HashMap<>(); // 填充数据模型 dataModel.put("opensource", true); dataModel.put("isSpringBoot3", isSpringBoot3()); dataModel.put("dbType", table.getDbType()); dataModel.put("package", table.getPackageName()); dataModel.put("packagePath", table.getPackageName().replace(".", "/")); dataModel.put("version", table.getVersion()); dataModel.put("moduleName", table.getModuleName()); dataModel.put("ModuleName", StrUtil.upperFirst(table.getModuleName())); dataModel.put("functionName", table.getFunctionName()); dataModel.put("FunctionName", StrUtil.upperFirst(table.getFunctionName())); dataModel.put("formLayout", table.getFormLayout()); dataModel.put("style", table.getStyle()); dataModel.put("author", table.getAuthor()); dataModel.put("datetime", DateUtil.now()); dataModel.put("date", DateUtil.today()); setFieldTypeList(dataModel, table); // 获取导入的包列表 Set importList = fieldTypeService.getPackageByTableId(table.getDsName(), table.getTableName()); dataModel.put("importList", importList); dataModel.put("tableName", table.getTableName()); dataModel.put("tableComment", table.getTableComment()); dataModel.put("className", StrUtil.lowerFirst(table.getClassName())); dataModel.put("ClassName", table.getClassName()); dataModel.put("fieldList", table.getFieldList()); dataModel.put("backendPath", table.getBackendPath()); dataModel.put("frontendPath", table.getFrontendPath()); // 设置子表 String childTableName = table.getChildTableName(); if (StrUtil.isNotBlank(childTableName)) { List childFieldList = columnService.lambdaQuery() .eq(GenTableColumnEntity::getDsName, table.getDsName()) .eq(GenTableColumnEntity::getTableName, table.getChildTableName()) .list(); dataModel.put("childFieldList", childFieldList); dataModel.put("childTableName", childTableName); dataModel.put("mainField", NamingCase.toCamelCase(table.getMainField())); dataModel.put("childField", NamingCase.toCamelCase(table.getChildField())); dataModel.put("ChildClassName", NamingCase.toPascalCase(childTableName)); dataModel.put("childClassName", StrUtil.lowerFirst(NamingCase.toPascalCase(childTableName))); // 设置是否是多租户模式 (判断字段列表中是否包含 tenant_id 字段) childFieldList.stream() .filter(genTableColumnEntity -> genTableColumnEntity.getFieldName().equals("tenant_id")) .findFirst() .ifPresent(columnEntity -> dataModel.put("isChildTenant", true)); } // 设置是否是多租户模式 (判断字段列表中是否包含 tenant_id 字段) table.getFieldList() .stream() .filter(genTableColumnEntity -> genTableColumnEntity.getFieldName().equals("tenant_id")) .findFirst() .ifPresent(columnEntity -> dataModel.put("isTenant", true)); return dataModel; } /** * 判断当前是否是 SpringBoot3 版本 * @return true/fasle */ private boolean isSpringBoot3() { return StrUtil.startWith(SpringBootVersion.getVersion(), "3"); } /** * 将表字段按照类型分组并存储到数据模型中 * @param dataModel 存储数据的 Map 对象 * @param table 表信息对象 */ private void setFieldTypeList(Map dataModel, GenTable table) { // 按字段类型分组,使用 Map 存储不同类型的字段列表 Map> typeMap = table.getFieldList() .stream() .collect(Collectors.partitioningBy(columnEntity -> BooleanUtil.toBoolean(columnEntity.getPrimaryPk()))); // 从分组后的 Map 中获取不同类型的字段列表 List primaryList = typeMap.get(true); List formList = typeMap.get(false) .stream() .filter(columnEntity -> BooleanUtil.toBoolean(columnEntity.getFormItem())) .toList(); List gridList = typeMap.get(false) .stream() .filter(columnEntity -> BooleanUtil.toBoolean(columnEntity.getGridItem())) .toList(); List queryList = typeMap.get(false) .stream() .filter(columnEntity -> BooleanUtil.toBoolean(columnEntity.getQueryItem())) .toList(); if (CollUtil.isNotEmpty(primaryList)) { dataModel.put("pk", primaryList.get(0)); } dataModel.put("primaryList", primaryList); dataModel.put("formList", formList); dataModel.put("gridList", gridList); dataModel.put("queryList", queryList); } } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/util/AutoFillEnum.java ================================================ package com.pig4cloud.pig.codegen.util; /** * 字段自动填充 枚举 * * @author 阿沐 babamu@126.com */ public enum AutoFillEnum { DEFAULT, INSERT, UPDATE, INSERT_UPDATE, CREATE; } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/util/BoolFillEnum.java ================================================ package com.pig4cloud.pig.codegen.util; import lombok.Getter; import lombok.RequiredArgsConstructor; /** * boolean 类型枚举 * */ @Getter @RequiredArgsConstructor public enum BoolFillEnum { /** * true */ TRUE("1"), /** * false */ FALSE("0"); private final String value; } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/util/CommonColumnFiledEnum.java ================================================ package com.pig4cloud.pig.codegen.util; import lombok.AllArgsConstructor; import lombok.Getter; /** * @author lengleng * @date 2023/3/12 *

* 通用字段的填充策略和显示策略 */ @Getter @AllArgsConstructor public enum CommonColumnFiledEnum { /** * create_by 字段 */ create_by("0", "0", AutoFillEnum.INSERT.name(), 100), /** * create_time 字段 */ create_time("0", "0", AutoFillEnum.INSERT.name(), 101), /** * update_by 字段 */ update_by("0", "0", AutoFillEnum.INSERT_UPDATE.name(), 102), /** * update_time 字段 */ update_time("0", "0", AutoFillEnum.INSERT_UPDATE.name(), 103), /** * del_flag 字段 */ del_flag("0", "0", AutoFillEnum.DEFAULT.name(), 104), /** * tenant_id 字段 */ tenant_id("0", "0", AutoFillEnum.DEFAULT.name(), 105); /** * 表单是否默认显示 1/0 */ private String formItem; /** * 表格是否默认显示 1/0 */ private String gridItem; /** * 自动填充策略 */ private String autoFill; /** * 排序值 */ private Integer sort; } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/util/DictTool.java ================================================ package com.pig4cloud.pig.codegen.util; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import java.util.HashSet; import java.util.List; /** * 字典工具类:提供字典相关操作的工具方法 * * @author lengleng * @date 2025/05/31 */ public class DictTool { /** * 将字段列表转换为带有双引号的逗号分隔的字符串 * @return 带有双引号的逗号分隔的字符串 */ public static String quotation(List fields) { return CollUtil.join(new HashSet<>(fields), StrUtil.COMMA, s -> String.format("'%s'", s)); } /** * 将字段列表转换为逗号分隔的字符串 * @return 逗号分隔的字符串 */ public static String format(List fields) { return CollUtil.join(new HashSet<>(fields), StrUtil.COMMA); } } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/util/GenKit.java ================================================ package com.pig4cloud.pig.codegen.util; import cn.hutool.core.util.StrUtil; import lombok.experimental.UtilityClass; /** * 代码生成工具类 * * @author lengleng * @date 2025/05/31 */ @UtilityClass public class GenKit { /** * 获取功能名 sys_a_b sysAb * @param tableName 表名 * @return 功能名 */ public String getFunctionName(String tableName) { return StrUtil.toCamelCase(tableName); } /** * 获取模块名称 * @param packageName 包名 * @return 功能名 */ public String getModuleName(String packageName) { return StrUtil.subAfter(packageName, ".", true); } } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/util/GeneratorStyleEnum.java ================================================ package com.pig4cloud.pig.codegen.util; import lombok.AllArgsConstructor; import lombok.Getter; /** * 代码生成主题 * * @author 冷冷 */ @Getter @AllArgsConstructor public enum GeneratorStyleEnum { VFORM_JSON(1L, "element-plus 风格"), VFORM_FORM(2L, "uview 风格"); /** * 对应模板ID */ private Long templateId; /** * 描述 */ private String desc; } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/util/NamingCaseTool.java ================================================ package com.pig4cloud.pig.codegen.util; import cn.hutool.core.text.NamingCase; /** * 命名规则处理工具类,提供驼峰、下划线等命名格式转换功能 * * @author lengleng * @date 2025/05/31 */ public class NamingCaseTool { /** * 根据字段名生成对应的get方法名 * @param in 字段名称 * @return 生成的get方法名 */ public static String getProperty(String in) { return String.format("get%s", NamingCase.toPascalCase(in)); } /** * 根据输入字符串生成setter方法名 * @param in 输入字符串 * @return 生成的setter方法名 */ public static String setProperty(String in) { return String.format("set%s", NamingCase.toPascalCase(in)); } /** * 将字符串转换为帕斯卡命名格式(首字母大写) * @param in 输入字符串 * @return 首字母大写的字符串 */ public static String pascalCase(String in) { return String.format(NamingCase.toPascalCase(in)); } } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/util/VelocityKit.java ================================================ package com.pig4cloud.pig.codegen.util; import cn.hutool.core.util.CharsetUtil; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.Velocity; import org.apache.velocity.tools.generic.DateTool; import org.apache.velocity.tools.generic.MathTool; import org.springframework.stereotype.Service; import java.io.StringWriter; import java.util.Map; import java.util.Optional; import java.util.Properties; /** * Velocity模板引擎工具类,提供模板渲染和字符串渲染功能 * * @author lengleng * @date 2025/05/31 */ @Service public class VelocityKit { /** * Velocity 模板渲染方法 * @param template 模板路径 * @param map 数据模型 * @return 渲染后的字符串结果 */ public static String render(String template, Map map) { // 设置velocity资源加载器 Properties prop = new Properties(); prop.put("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); Velocity.init(prop); VelocityContext context = new VelocityContext(map); // 函数库,使用 Lambda 表达式简化代码 Optional.of(new MathTool()).ifPresent(mt -> context.put("math", mt)); Optional.of(new DateTool()).ifPresent(dt -> context.put("dateTool", dt)); Optional.of(new DictTool()).ifPresent(dt -> context.put("dict", dt)); Optional.of(new NamingCaseTool()).ifPresent(nct -> context.put("str", nct)); // 渲染模板,使用 Lambda 表达式简化代码 StringWriter sw = new StringWriter(); Optional.ofNullable(Velocity.getTemplate(template, CharsetUtil.UTF_8)).ifPresent(tpl -> tpl.merge(context, sw)); return sw.toString(); } /** * 渲染文本 * @param str 待渲染的字符串 * @param dataModel 数据模型 * @return 渲染后的字符串 */ public static String renderStr(String str, Map dataModel) { // 设置velocity资源加载器 Velocity.init(); StringWriter stringWriter = new StringWriter(); VelocityContext context = new VelocityContext(dataModel); // 函数库 context.put("math", new MathTool()); context.put("dateTool", new DateTool()); context.put("dict", new DictTool()); context.put("str", new NamingCaseTool()); Velocity.evaluate(context, stringWriter, "renderStr", str); return stringWriter.toString(); } } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/util/vo/GenCreateTableVO.java ================================================ package com.pig4cloud.pig.codegen.util.vo; /* * Copyright (c) 2018-2025, luolin All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: luolin (766488893@qq.com) */ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Data; import java.time.LocalDateTime; /** * 自动创建表管理 * * @author luolin * @date 2022-09-23 21:56:11 */ @Data @Schema(description = "自动创建表管理") public class GenCreateTableVO { /** * 主键ID */ @Schema(description = "主键ID") private Long id; /** * 表名称 */ @NotBlank(message = "表名称不能为空") @Schema(description = "表名称") private String tableName; /** * 表注释 */ @NotBlank(message = "表注释不能为空") @Schema(description = "表注释") private String comments; /** * 数据源名称 */ @NotBlank(message = "数据源名称不能为空") @Schema(description = "数据源名称") private String dsName; /** * 主键策略 */ @NotBlank(message = "主键策略不能为空") @Schema(description = "主键策略") private String pkPolicy; /** * 创建人 */ @Schema(description = "创建人") private Long createUser; /** * 创建时间 */ @Schema(description = "创建时间") private LocalDateTime createTime; /** * 表字段信息 */ @Schema(description = "表字段信息") private String columnsInfo; /** * 字段信息 */ @Schema(description = "字段信息") private String columnInfo; } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/util/vo/GenTemplateFileVO.java ================================================ package com.pig4cloud.pig.codegen.util.vo; import lombok.Data; /** * @author lengleng * @date 2024/7/13 *

* CGTM 文件路径 *

* { "templateName": "Controller", "generatorPath": * "${backendPath}/src/main/java/${packagePath}/${moduleName}/controller/${ClassName}Controller.java", * "templateDesc": "后台Controller", "templateFile": "temps/Controller" }, */ @Data public class GenTemplateFileVO { /** * 模板名称 */ private String templateName; /** * 路径 */ private String generatorPath; /** * 模板 desc */ private String templateDesc; /** * 模板文件 */ private String templateFile; } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/util/vo/GroupVO.java ================================================ package com.pig4cloud.pig.codegen.util.vo; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.pig4cloud.pig.codegen.entity.GenTemplateEntity; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.util.List; @Data public class GroupVO { /** * id */ @TableId(type = IdType.ASSIGN_ID) @Schema(description = "id") private Long id; /** * 分组名称 */ @Schema(description = "分组名称") private String groupName; /** * 分组描述 */ @Schema(description = "分组描述") private String groupDesc; /** * 模板ids */ @Schema(description = "拥有的模板列表") private Long[] templateId; /** * 模板列表 */ @Schema(description = "拥有的模板列表") private List templateList; } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/util/vo/SqlDto.java ================================================ package com.pig4cloud.pig.codegen.util.vo; import lombok.Data; /** * @author lengleng * @date 2022/5/2 */ @Data public class SqlDto { /** * 数据源ID */ private String dsName; /** * sql脚本 */ private String sql; } ================================================ FILE: pig-visual/pig-codegen/src/main/java/com/pig4cloud/pig/codegen/util/vo/TemplateGroupDTO.java ================================================ package com.pig4cloud.pig.codegen.util.vo; import java.io.Serial; import java.util.List; import com.pig4cloud.pig.codegen.entity.GenGroupEntity; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; /** * @author weimeilayer@gmail.com ✨ * @date 💓💕 2025年5月30日 🐬🐇 💓💕 */ @Data @Schema(description = "模板传输对象") @EqualsAndHashCode(callSuper = true) public class TemplateGroupDTO extends GenGroupEntity { @Serial private static final long serialVersionUID = 1L; /** * 模板id集合 */ @Schema(description = "模板id集合") private List templateId; } ================================================ FILE: pig-visual/pig-codegen/src/main/resources/application.yml ================================================ server: port: 5002 spring: application: name: @artifactId@ cloud: nacos: username: @nacos.username@ password: @nacos.password@ discovery: server-addr: ${NACOS_HOST:127.0.0.1}:${NACOS_PORT:8848} config: server-addr: ${spring.cloud.nacos.discovery.server-addr} config: import: - nacos:application-@profiles.active@.yml - nacos:${spring.application.name}-@profiles.active@.yml ================================================ FILE: pig-visual/pig-codegen/src/main/resources/logback-spring.xml ================================================ ${CONSOLE_LOG_PATTERN} ${log.path}/debug.log ${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz 50MB 30 ${FILE_LOG_PATTERN} ${log.path}/error.log ${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz 50MB 30 ${FILE_LOG_PATTERN} ERROR ================================================ FILE: pig-visual/pig-codegen/src/main/resources/mapper/GenFieldTypeMapper.xml ================================================ ================================================ FILE: pig-visual/pig-codegen/src/main/resources/mapper/GenGroupMapper.xml ================================================ ================================================ FILE: pig-visual/pig-codegen/src/main/resources/mapper/GenTableMapper.xml ================================================ ================================================ FILE: pig-visual/pig-codegen/src/main/resources/mapper/GenTemplateGroupMapper.xml ================================================ ================================================ FILE: pig-visual/pig-codegen/src/main/resources/mapper/GenTemplateMapper.xml ================================================ ================================================ FILE: pig-visual/pig-monitor/Dockerfile ================================================ FROM registry.cn-hangzhou.aliyuncs.com/dockerhub_mirror/java:21-anolis WORKDIR /pig-monitor ARG JAR_FILE=target/pig-monitor.jar COPY ${JAR_FILE} app.jar EXPOSE 5001 ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms128m -Xmx256m -Djava.security.egd=file:/dev/./urandom" CMD sleep 60; java $JAVA_OPTS -jar app.jar ================================================ FILE: pig-visual/pig-monitor/pom.xml ================================================ 4.0.0 com.pig4cloud pig-visual ${revision} pig-monitor jar pig 监控模块,基于 spring boot admin de.codecentric spring-boot-admin-starter-server ${spring-boot-admin.version} com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config org.springframework.boot spring-boot-starter-undertow org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-security org.springframework.security spring-security-config org.springframework.boot spring-boot-maven-plugin io.fabric8 docker-maven-plugin ================================================ FILE: pig-visual/pig-monitor/src/main/java/com/pig4cloud/pig/monitor/PigMonitorApplication.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.monitor; import de.codecentric.boot.admin.server.config.EnableAdminServer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; /** * 监控中心应用启动类 * * @author lengleng * @date 2018/06/21 */ @EnableAdminServer @EnableDiscoveryClient @SpringBootApplication public class PigMonitorApplication { public static void main(String[] args) { SpringApplication.run(PigMonitorApplication.class, args); } } ================================================ FILE: pig-visual/pig-monitor/src/main/java/com/pig4cloud/pig/monitor/config/CustomCsrfFilter.java ================================================ package com.pig4cloud.pig.monitor.config; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.web.csrf.CsrfToken; import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.util.WebUtils; import java.io.IOException; /** * 自定义CSRF过滤器,用于处理CSRF令牌相关逻辑 * * @author lengleng * @date 2025/05/31 */ public class CustomCsrfFilter extends OncePerRequestFilter { public static final String CSRF_COOKIE_NAME = "XSRF-TOKEN"; /** * 处理CSRF令牌的过滤器内部逻辑 * @param request HTTP请求 * @param response HTTP响应 * @param filterChain 过滤器链 * @throws ServletException 如果发生servlet相关异常 * @throws IOException 如果发生I/O异常 */ @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); if (csrf != null) { Cookie cookie = WebUtils.getCookie(request, CSRF_COOKIE_NAME); String token = csrf.getToken(); if (cookie == null || token != null && !token.equals(cookie.getValue())) { cookie = new Cookie(CSRF_COOKIE_NAME, token); cookie.setPath("/"); response.addCookie(cookie); } } filterChain.doFilter(request, response); } } ================================================ FILE: pig-visual/pig-monitor/src/main/java/com/pig4cloud/pig/monitor/config/SecuritySecureConfig.java ================================================ /* * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.pig4cloud.pig.monitor.config; import de.codecentric.boot.admin.server.config.AdminServerProperties; import jakarta.servlet.DispatcherType; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.security.web.csrf.CookieCsrfTokenRepository; import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler; import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; import java.util.UUID; /** * 安全配置类:用于配置Spring Security相关设置 * * @author lengleng * @date 2025/05/31 */ @Configuration(proxyBeanMethods = false) public class SecuritySecureConfig { private final AdminServerProperties adminServer; private final SecurityProperties security; /** * 构造函数,初始化安全管理配置 * @param adminServer 管理服务器配置属性 * @param security 安全配置属性 */ public SecuritySecureConfig(AdminServerProperties adminServer, SecurityProperties security) { this.adminServer = adminServer; this.security = security; } /** * 配置Spring Security过滤器链 * @param http HttpSecurity对象,用于配置安全策略 * @return 配置好的SecurityFilterChain实例 * @throws Exception 配置过程中可能抛出的异常 */ @Bean protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception { SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler(); successHandler.setTargetUrlParameter("redirectTo"); successHandler.setDefaultTargetUrl(this.adminServer.path("/")); http.authorizeHttpRequests((authorizeRequests) -> authorizeRequests // .requestMatchers(PathPatternRequestMatcher.withDefaults().matcher(this.adminServer.path("/assets/**"))) .permitAll() .requestMatchers(PathPatternRequestMatcher.withDefaults().matcher(this.adminServer.path("/actuator/info"))) .permitAll() .requestMatchers(PathPatternRequestMatcher.withDefaults().matcher(adminServer.path("/actuator/health"))) .permitAll() .requestMatchers(PathPatternRequestMatcher.withDefaults().matcher(this.adminServer.path("/login"))) .permitAll() .dispatcherTypeMatchers(DispatcherType.ASYNC) .permitAll() // https://github.com/spring-projects/spring-security/issues/11027 .anyRequest() .authenticated()) .formLogin( (formLogin) -> formLogin.loginPage(this.adminServer.path("/login")).successHandler(successHandler)) .logout((logout) -> logout.logoutUrl(this.adminServer.path("/logout"))) .httpBasic(Customizer.withDefaults()); http.addFilterAfter(new CustomCsrfFilter(), BasicAuthenticationFilter.class) // <5> .csrf((csrf) -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler()) .ignoringRequestMatchers( PathPatternRequestMatcher.withDefaults() .matcher(HttpMethod.POST, this.adminServer.path("/instances")), // <6> PathPatternRequestMatcher.withDefaults() .matcher(HttpMethod.DELETE, this.adminServer.path("/instances/*")), // <6> PathPatternRequestMatcher.withDefaults().matcher(this.adminServer.path("/actuator/**")) // <7> )); http.rememberMe((rememberMe) -> rememberMe.key(UUID.randomUUID().toString()).tokenValiditySeconds(1209600)); return http.build(); } /** * 创建内存用户详情服务 * @param passwordEncoder 密码编码器 * @return 包含配置用户的InMemoryUserDetailsManager实例 */ @Bean public InMemoryUserDetailsManager userDetailsService(PasswordEncoder passwordEncoder) { UserDetails user = User.withUsername(security.getUser().getName()) .password(passwordEncoder.encode(security.getUser().getPassword())) .roles("USER") .build(); return new InMemoryUserDetailsManager(user); } /** * 创建并返回一个BCrypt密码编码器实例 * @return BCryptPasswordEncoder实例 */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } ================================================ FILE: pig-visual/pig-monitor/src/main/java/com/pig4cloud/pig/monitor/converter/NacosServiceInstanceConverter.java ================================================ package com.pig4cloud.pig.monitor.converter; import de.codecentric.boot.admin.server.cloud.discovery.DefaultServiceInstanceConverter; import org.springframework.cloud.client.ServiceInstance; import org.springframework.context.annotation.Configuration; import java.util.Map; import java.util.stream.Collectors; import static java.util.Collections.emptyMap; /** * Nacos 2.x 服务注册转换器,用于处理服务实例元数据转换 * * @author lengleng * @date 2025/05/31 */ @Configuration(proxyBeanMethods = false) public class NacosServiceInstanceConverter extends DefaultServiceInstanceConverter { /** * 获取服务实例的元数据 * @param instance 服务实例 * @return 过滤后的元数据映射,不包含空键或空值的条目 */ @Override protected Map getMetadata(ServiceInstance instance) { return (instance.getMetadata() != null) ? instance.getMetadata() .entrySet() .stream() .filter((e) -> e.getKey() != null && e.getValue() != null) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)) : emptyMap(); } } ================================================ FILE: pig-visual/pig-monitor/src/main/resources/application.yml ================================================ server: port: 5001 spring: application: name: @artifactId@ cloud: nacos: username: @nacos.username@ password: @nacos.password@ discovery: server-addr: ${NACOS_HOST:127.0.0.1}:${NACOS_PORT:8848} config: server-addr: ${spring.cloud.nacos.discovery.server-addr} config: import: - nacos:application-@profiles.active@.yml - nacos:${spring.application.name}-@profiles.active@.yml ================================================ FILE: pig-visual/pig-monitor/src/main/resources/logback-spring.xml ================================================ ${CONSOLE_LOG_PATTERN} ${log.path}/debug.log ${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz 50MB 30 ${FILE_LOG_PATTERN} ${log.path}/error.log ${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz 50MB 30 ${FILE_LOG_PATTERN} ERROR ================================================ FILE: pig-visual/pig-quartz/Dockerfile ================================================ FROM registry.cn-hangzhou.aliyuncs.com/dockerhub_mirror/java:21-anolis WORKDIR /pig-quartz ARG JAR_FILE=target/pig-quartz.jar COPY ${JAR_FILE} app.jar EXPOSE 5007 ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms128m -Xmx256m -Djava.security.egd=file:/dev/./urandom" CMD sleep 60; java $JAVA_OPTS -jar app.jar ================================================ FILE: pig-visual/pig-quartz/SECURITY.md ================================================ # Quartz Module Security ## Remote Code Execution (RCE) Vulnerability Fix ### Issue Description In versions 3.8.2 and below, the Quartz management module allowed execution of arbitrary Java classes through reflection without proper validation. This could be exploited to execute malicious code using classes like `jakarta.el.ELProcessor`. ### Attack Vector Example An attacker with access to the Quartz management interface could create a task with: - **Class Name**: `jakarta.el.ELProcessor` - **Method Name**: `eval` - **Parameters**: `Runtime.getRuntime().exec("malicious command")` This would result in remote code execution on the server. ### Security Fix Implementation #### 1. Class Name Validator (`ClassNameValidator.java`) A comprehensive validator that maintains blacklists of dangerous classes, packages, and methods: **Blocked Classes:** - `jakarta.el.ELProcessor`, `javax.el.ELProcessor` - Expression Language processors - `javax.script.ScriptEngineManager`, `javax.script.ScriptEngine` - Script engines - `java.lang.Runtime`, `java.lang.ProcessBuilder` - System command execution - `java.lang.reflect.*` - Reflection API - `java.lang.ClassLoader`, `java.net.URLClassLoader` - Class loaders - `javax.naming.*` - JNDI classes - `java.rmi.*` - RMI classes - `java.io.ObjectInputStream`, `java.io.ObjectOutputStream` - Serialization **Blocked Packages:** - `sun.*`, `com.sun.*` - Internal JDK classes - `jdk.internal.*` - Internal JDK classes - `java.lang.reflect.*` - Reflection API - `java.security.*` - Security API - `javax.naming.*` - JNDI - `javax.script.*` - Scripting API - `java.rmi.*` - RMI API - `javax.management.*` - JMX - `org.springframework.context.support.FileSystemXmlApplicationContext` - Spring context manipulation - `org.springframework.expression.spel.*` - Spring Expression Language **Blocked Methods:** - `exec`, `eval`, `execute` - Code execution - `invoke`, `newInstance` - Reflection - `forName`, `loadClass`, `defineClass` - Class loading - `getRuntime`, `getMethod`, `getDeclaredMethod` - System access #### 2. Multi-Layer Defense **Layer 1: Controller Validation** (`SysJobController.java`) - Validates class and method names when creating tasks (POST `/sys-job`) - Validates class and method names when updating tasks (PUT `/sys-job`) - Returns HTTP 400 error with descriptive message if validation fails **Layer 2: Execution Validation** (`JavaClassTaskInvok.java`) - Validates class and method names before reflection execution - Throws `TaskException` if validation fails - Prevents execution even if controller validation is bypassed ### Testing The fix includes comprehensive test coverage: **Unit Tests** (`ClassNameValidatorTest.java`) - 8 test cases covering validation logic - Tests for dangerous classes, packages, methods - Tests for valid inputs and edge cases **Security Tests** (`JavaClassTaskInvokSecurityTest.java`) - 7 test cases specifically for RCE attack vectors - Tests blocking of: - `jakarta.el.ELProcessor` with `eval` method - `java.lang.Runtime` with `exec` method - `java.lang.ProcessBuilder` - `javax.script.ScriptEngineManager` - `java.lang.ClassLoader` - `javax.naming.InitialContext` (JNDI injection) - Dangerous method names All 15 tests pass successfully, confirming the vulnerability is fixed. ### Usage Guidelines #### For Administrators 1. **Update immediately** to a version containing this fix 2. **Review existing tasks** in the Quartz management interface for any suspicious entries 3. **Audit logs** for any attempts to create tasks with dangerous classes 4. **Restrict access** to the Quartz management interface to trusted administrators only #### For Developers When creating custom task classes: 1. Use application-specific package names (e.g., `com.yourcompany.tasks.*`) 2. Avoid using reflection, class loaders, or system commands in task methods 3. Implement proper input validation in your task classes 4. Use descriptive method names that don't match dangerous patterns ### Safe Task Example ```java package com.pig4cloud.pig.daemon.tasks; public class DataCleanupTask { // Safe method - no dangerous operations public String execute() { // Perform data cleanup logic return "0"; // Return "0" for success } // Safe method with parameters public String executeWithParams(String params) { // Validate input if (params == null || params.isEmpty()) { return "1"; // Return "1" for failure } // Perform task logic return "0"; } } ``` ### Configuration in Quartz Management **Safe Configuration:** - **Class Name**: `com.pig4cloud.pig.daemon.tasks.DataCleanupTask` - **Method Name**: `execute` or `executeWithParams` - **Parameters**: Simple string parameters only **Blocked Configuration:** - **Class Name**: `jakarta.el.ELProcessor` ❌ - **Method Name**: `eval` ❌ - **Class Name**: `java.lang.Runtime` ❌ - **Method Name**: `exec` ❌ ### Security Summary ✅ **Fixed**: Remote Code Execution vulnerability through reflection ✅ **Protection**: Multi-layer validation (controller + execution) ✅ **Coverage**: Comprehensive blacklist of dangerous classes and methods ✅ **Tested**: 15 test cases verify the fix ✅ **Impact**: Prevents arbitrary code execution via Quartz tasks ### References - CVE: Pending assignment - Affected Versions: pig <= 3.8.2 - Fixed in Version: 3.9.2+ - Severity: Critical ================================================ FILE: pig-visual/pig-quartz/pom.xml ================================================ com.pig4cloud pig-visual ${revision} 4.0.0 pig-quartz jar 基于quartz后台定时任务模块 com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config com.pig4cloud pig-common-log com.pig4cloud pig-common-feign com.pig4cloud pig-common-mybatis com.baomidou mybatis-plus-spring-boot3-starter com.mysql mysql-connector-j com.pig4cloud pig-common-swagger com.pig4cloud pig-common-security org.springframework.boot spring-boot-starter-quartz org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-undertow boot cloud true org.springframework.boot spring-boot-maven-plugin io.fabric8 docker-maven-plugin ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/PigQuartzApplication.java ================================================ package com.pig4cloud.pig.daemon.quartz; import com.pig4cloud.pig.common.feign.annotation.EnablePigFeignClients; import com.pig4cloud.pig.common.security.annotation.EnablePigResourceServer; import com.pig4cloud.pig.common.swagger.annotation.EnablePigDoc; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; /** * PigQuartz应用启动类 *

* 集成定时任务、Feign客户端、资源服务及服务发现功能 * * @author lengleng * @author frwcloud * @date 2025/05/31 */ @EnablePigDoc("job") @EnablePigFeignClients @EnablePigResourceServer @EnableDiscoveryClient @SpringBootApplication public class PigQuartzApplication { public static void main(String[] args) { SpringApplication.run(PigQuartzApplication.class, args); } } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/config/AutowireCapableBeanJobFactory.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.config; import org.quartz.JobKey; import org.quartz.spi.TriggerFiredBundle; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.scheduling.quartz.SpringBeanJobFactory; import org.springframework.util.Assert; /** * 自动装配能力的Bean任务工厂,继承自SpringBeanJobFactory,用于创建并自动装配Job实例 * * @author lengleng * @author 郑健楠 * @date 2025/05/31 */ class AutowireCapableBeanJobFactory extends SpringBeanJobFactory { private final AutowireCapableBeanFactory beanFactory; AutowireCapableBeanJobFactory(AutowireCapableBeanFactory beanFactory) { Assert.notNull(beanFactory, "Bean factory must not be null"); this.beanFactory = beanFactory; } /** * 创建并初始化Job实例 * @param bundle 触发器触发包,包含Job相关信息 * @return 初始化后的Job实例 * @throws Exception 创建或初始化过程中可能抛出的异常 */ @Override protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception { Object jobInstance = super.createJobInstance(bundle); this.beanFactory.autowireBean(jobInstance); // 此处必须注入 beanName 不然sentinel 报错 JobKey jobKey = bundle.getTrigger().getJobKey(); String beanName = jobKey + jobKey.getName(); this.beanFactory.initializeBean(jobInstance, beanName); return jobInstance; } } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/config/PigInitQuartzJob.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.config; import org.quartz.Scheduler; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.annotation.Configuration; import com.pig4cloud.pig.daemon.quartz.constants.PigQuartzEnum; import com.pig4cloud.pig.daemon.quartz.service.SysJobService; import com.pig4cloud.pig.daemon.quartz.util.TaskUtil; import lombok.AllArgsConstructor; /** * 初始化加载定时任务配置类 * * @author lengleng * @author 郑健楠 * @date 2025/05/31 */ @Configuration @AllArgsConstructor public class PigInitQuartzJob implements InitializingBean { private final SysJobService sysJobService; private final TaskUtil taskUtil; private final Scheduler scheduler; /** * 在属性设置完成后执行,根据任务状态进行相应操作 * @throws Exception 执行过程中可能抛出的异常 */ @Override public void afterPropertiesSet() throws Exception { sysJobService.list().forEach(sysjob -> { if (PigQuartzEnum.JOB_STATUS_RELEASE.getType().equals(sysjob.getJobStatus())) { taskUtil.removeJob(sysjob, scheduler); } else if (PigQuartzEnum.JOB_STATUS_RUNNING.getType().equals(sysjob.getJobStatus())) { taskUtil.resumeJob(sysjob, scheduler); } else if (PigQuartzEnum.JOB_STATUS_NOT_RUNNING.getType().equals(sysjob.getJobStatus())) { taskUtil.pauseJob(sysjob, scheduler); } else { taskUtil.removeJob(sysjob, scheduler); } }); } } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/config/PigQuartzConfig.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.config; import com.pig4cloud.pig.common.core.factory.YamlPropertySourceFactory; import org.quartz.Calendar; import org.quartz.JobDetail; import org.quartz.Scheduler; import org.quartz.Trigger; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.quartz.QuartzProperties; import org.springframework.boot.autoconfigure.quartz.SchedulerFactoryBeanCustomizer; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.quartz.SchedulerFactoryBean; import java.util.List; import java.util.Map; import java.util.Properties; /** * Quartz 定时任务配置类 * * @author lengleng * @author 郑健楠 * @date 2025/05/31 */ @EnableAsync @Configuration @PropertySource(value = "classpath:quartz-config.yml", factory = YamlPropertySourceFactory.class) @ConditionalOnClass({ Scheduler.class, SchedulerFactoryBean.class }) @EnableConfigurationProperties({ QuartzProperties.class }) public class PigQuartzConfig { private final QuartzProperties properties; private final List customizers; private final JobDetail[] jobDetails; private final Map calendars; private final Trigger[] triggers; private final ApplicationContext applicationContext; /** * 构造函数,初始化PigQuartzConfig配置 * @param properties Quartz配置属性 * @param customizers SchedulerFactoryBean自定义器列表 * @param jobDetails JobDetail数组 * @param calendars 日历Map * @param triggers 触发器数组 * @param applicationContext Spring应用上下文 */ public PigQuartzConfig(QuartzProperties properties, ObjectProvider> customizers, ObjectProvider jobDetails, ObjectProvider> calendars, ObjectProvider triggers, ApplicationContext applicationContext) { this.properties = properties; this.customizers = customizers.getIfAvailable(); this.jobDetails = jobDetails.getIfAvailable(); this.calendars = calendars.getIfAvailable(); this.triggers = triggers.getIfAvailable(); this.applicationContext = applicationContext; } /** * 创建并配置Quartz SchedulerFactoryBean * @return 配置完成的SchedulerFactoryBean实例 */ @Bean @ConditionalOnMissingBean public SchedulerFactoryBean quartzScheduler() { SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(); schedulerFactoryBean .setJobFactory(new AutowireCapableBeanJobFactory(this.applicationContext.getAutowireCapableBeanFactory())); if (!this.properties.getProperties().isEmpty()) { schedulerFactoryBean.setQuartzProperties(this.asProperties(this.properties.getProperties())); } if (this.jobDetails != null && this.jobDetails.length > 0) { schedulerFactoryBean.setJobDetails(this.jobDetails); } if (this.calendars != null && !this.calendars.isEmpty()) { schedulerFactoryBean.setCalendars(this.calendars); } if (this.triggers != null && this.triggers.length > 0) { schedulerFactoryBean.setTriggers(this.triggers); } this.customize(schedulerFactoryBean); return schedulerFactoryBean; } /** * 将Map转换为Properties对象 * @param source 源Map,键值对均为String类型 * @return 转换后的Properties对象 */ private Properties asProperties(Map source) { Properties properties = new Properties(); properties.putAll(source); return properties; } /** * 自定义SchedulerFactoryBean * @param schedulerFactoryBean 需要自定义的调度器工厂bean */ private void customize(SchedulerFactoryBean schedulerFactoryBean) { if (this.customizers != null) { for (SchedulerFactoryBeanCustomizer customizer : this.customizers) { customizer.customize(schedulerFactoryBean); } } } /** * 通过SchedulerFactoryBean获取Scheduler的实例 * @return */ @Bean public Scheduler scheduler() { return quartzScheduler().getScheduler(); } } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/config/PigQuartzCustomizerConfig.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.config; import org.springframework.boot.autoconfigure.quartz.SchedulerFactoryBeanCustomizer; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.quartz.SchedulerFactoryBean; /** * PigQuartz 自定义配置类,用于配置 SchedulerFactoryBean * * @author lengleng * @author 郑健楠 * @date 2025/05/31 */ @Configuration public class PigQuartzCustomizerConfig implements SchedulerFactoryBeanCustomizer { /** * 自定义SchedulerFactoryBean配置 * @param schedulerFactoryBean 调度器工厂bean */ @Override public void customize(SchedulerFactoryBean schedulerFactoryBean) { schedulerFactoryBean.setWaitForJobsToCompleteOnShutdown(true); } } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/config/PigQuartzFactory.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.config; import org.quartz.DisallowConcurrentExecution; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.springframework.beans.factory.annotation.Autowired; import com.pig4cloud.pig.daemon.quartz.constants.PigQuartzEnum; import com.pig4cloud.pig.daemon.quartz.entity.SysJob; import lombok.SneakyThrows; /** * 动态任务工厂:用于执行动态任务调度 * * @author lengleng * @author 郑健楠 * @date 2025/05/31 */ @DisallowConcurrentExecution public class PigQuartzFactory implements Job { /** * 定时任务调用工厂 */ @Autowired private PigQuartzInvokeFactory pigxQuartzInvokeFactory; /** * 执行定时任务 * @param jobExecutionContext 任务执行上下文 * @throws Exception 执行过程中可能抛出的异常 */ @Override @SneakyThrows public void execute(JobExecutionContext jobExecutionContext) { SysJob sysJob = (SysJob) jobExecutionContext.getMergedJobDataMap() .get(PigQuartzEnum.SCHEDULE_JOB_KEY.getType()); pigxQuartzInvokeFactory.init(sysJob, jobExecutionContext.getTrigger()); } } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/config/PigQuartzInvokeFactory.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.config; import org.aspectj.lang.annotation.Aspect; import org.quartz.Trigger; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import com.pig4cloud.pig.daemon.quartz.entity.SysJob; import com.pig4cloud.pig.daemon.quartz.event.SysJobEvent; import lombok.AllArgsConstructor; import lombok.SneakyThrows; /** * 定时任务调用工厂类 用于初始化并发布定时任务事件 * * @author lengleng * @date 2025/05/31 */ @Aspect @Service @AllArgsConstructor public class PigQuartzInvokeFactory { private final ApplicationEventPublisher publisher; /** * 初始化并发布定时任务事件 * @param sysJob 系统任务对象 * @param trigger 任务触发器 */ @SneakyThrows void init(SysJob sysJob, Trigger trigger) { publisher.publishEvent(new SysJobEvent(sysJob, trigger)); } } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/constants/JobTypeQuartzEnum.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.constants; import lombok.AllArgsConstructor; import lombok.Getter; /** * 任务类型枚举 * * @author lengleng * @date 2019/03/14 */ @Getter @AllArgsConstructor public enum JobTypeQuartzEnum { /** * 反射java类 */ JAVA("1", "反射java类"), /** * spring bean 的方式 */ SPRING_BEAN("2", "spring bean容器实例"), /** * rest 调用 */ REST("3", "rest调用"), /** * jar */ JAR("4", "jar调用"); /** * 类型 */ private final String type; /** * 描述 */ private final String description; } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/constants/PigQuartzEnum.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.constants; import lombok.AllArgsConstructor; import lombok.Getter; /** * 定时任务枚举类 *

* 定义定时任务相关的枚举常量,包括错失执行策略、任务状态等 * * @author lengleng * @author 郑健楠 * @date 2025/05/31 */ @Getter @AllArgsConstructor public enum PigQuartzEnum { /** * 错失执行策略默认 */ MISFIRE_DEFAULT("0", "默认"), /** * 错失执行策略-立即执行错失任务 */ MISFIRE_IGNORE_MISFIRES("1", "立即执行错失任务"), /** * 错失执行策略-触发一次执行周期执行 */ MISFIRE_FIRE_AND_PROCEED("2", "触发一次执行周期执行"), /** * 错失执行策略-不触发执行周期执行 */ MISFIRE_DO_NOTHING("3", "不触发周期执行"), /** * 任务详细信息的key */ SCHEDULE_JOB_KEY("scheduleJob", "获取任务详细信息的key"), /** * JOB执行状态:0执行成功 */ JOB_LOG_STATUS_SUCCESS("0", "执行成功"), /** * JOB执行状态:1执行失败 */ JOB_LOG_STATUS_FAIL("1", "执行失败"), /** * JOB状态:1已发布 */ JOB_STATUS_RELEASE("1", "已发布"), /** * JOB状态:2运行中 */ JOB_STATUS_RUNNING("2", "运行中"), /** * JOB状态:3暂停 */ JOB_STATUS_NOT_RUNNING("3", "暂停"), /** * JOB状态:4删除 */ JOB_STATUS_DEL("4", "删除"); /** * 类型 */ private final String type; /** * 描述 */ private final String description; } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/controller/SysJobController.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.controller; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.log.annotation.SysLog; import com.pig4cloud.pig.common.security.annotation.HasPermission; import com.pig4cloud.pig.common.security.util.SecurityUtils; import com.pig4cloud.pig.daemon.quartz.constants.JobTypeQuartzEnum; import com.pig4cloud.pig.daemon.quartz.constants.PigQuartzEnum; import com.pig4cloud.pig.daemon.quartz.entity.SysJob; import com.pig4cloud.pig.daemon.quartz.entity.SysJobLog; import com.pig4cloud.pig.daemon.quartz.service.SysJobLogService; import com.pig4cloud.pig.daemon.quartz.service.SysJobService; import com.pig4cloud.pig.daemon.quartz.util.ClassNameValidator; import com.pig4cloud.pig.daemon.quartz.util.TaskUtil; import com.pig4cloud.plugin.excel.annotation.ResponseExcel; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.springframework.http.HttpHeaders; import org.springframework.web.bind.annotation.*; import java.util.List; /** * 定时任务管理控制器 * * @author lengleng * @date 2025/05/31 */ @Slf4j @RestController @AllArgsConstructor @RequestMapping("/sys-job") @Tag(description = "sys-job", name = "定时任务管理模块") @SecurityRequirement(name = HttpHeaders.AUTHORIZATION) public class SysJobController { private final SysJobService sysJobService; private final SysJobLogService sysJobLogService; private final TaskUtil taskUtil; private final Scheduler scheduler; /** * 定时任务分页查询 * @param page 分页对象 * @param sysJob 定时任务调度表 * @return R */ @GetMapping("/page") @Operation(summary = "分页定时业务查询", description = "分页定时业务查询") public R getJobPage(Page page, SysJob sysJob) { LambdaQueryWrapper wrapper = Wrappers.lambdaQuery() .like(StrUtil.isNotBlank(sysJob.getJobName()), SysJob::getJobName, sysJob.getJobName()) .like(StrUtil.isNotBlank(sysJob.getJobGroup()), SysJob::getJobGroup, sysJob.getJobGroup()) .eq(StrUtil.isNotBlank(sysJob.getJobStatus()), SysJob::getJobStatus, sysJob.getJobGroup()) .eq(StrUtil.isNotBlank(sysJob.getJobExecuteStatus()), SysJob::getJobExecuteStatus, sysJob.getJobExecuteStatus()); return R.ok(sysJobService.page(page, wrapper)); } /** * 通过id查询定时任务 * @param id id * @return R */ @GetMapping("/{id}") @Operation(summary = "唯一标识查询定时任务", description = "唯一标识查询定时任务") public R getById(@PathVariable("id") Long id) { return R.ok(sysJobService.getById(id)); } /** * 新增定时任务,默认新增状态为1已发布 * @param sysJob 定时任务调度表 * @return R */ @SysLog("新增定时任务") @PostMapping @HasPermission("job_sys_job_add") @Operation(summary = "新增定时任务", description = "新增定时任务") public R saveJob(@RequestBody SysJob sysJob) { long count = sysJobService.count( Wrappers.query(SysJob.builder().jobName(sysJob.getJobName()).jobGroup(sysJob.getJobGroup()).build())); if (count > 0) { return R.failed("任务重复,请检查此组内是否已包含同名任务"); } // 安全验证:对于Java类类型的任务,验证类名和方法名 if (JobTypeQuartzEnum.JAVA.getType().equals(sysJob.getJobType())) { if (!ClassNameValidator.isValidClassName(sysJob.getClassName())) { log.warn("新增定时任务失败,类名验证不通过:{}", sysJob.getClassName()); return R.failed("类名验证失败,该类在黑名单中或包含危险特征,拒绝创建"); } if (!ClassNameValidator.isValidMethodName(sysJob.getMethodName())) { log.warn("新增定时任务失败,方法名验证不通过:{}", sysJob.getMethodName()); return R.failed("方法名验证失败,该方法在黑名单中或包含危险特征,拒绝创建"); } } sysJob.setJobStatus(PigQuartzEnum.JOB_STATUS_RELEASE.getType()); sysJob.setCreateBy(SecurityUtils.getUser().getUsername()); return R.ok(sysJobService.save(sysJob)); } /** * 修改定时任务 * @param sysJob 定时任务调度表 * @return R */ @SysLog("修改定时任务") @PutMapping @HasPermission("job_sys_job_edit") @Operation(summary = "修改定时任务", description = "修改定时任务") public R updateJob(@RequestBody SysJob sysJob) { // 安全验证:对于Java类类型的任务,验证类名和方法名 if (JobTypeQuartzEnum.JAVA.getType().equals(sysJob.getJobType())) { if (!ClassNameValidator.isValidClassName(sysJob.getClassName())) { log.warn("修改定时任务失败,类名验证不通过:{}", sysJob.getClassName()); return R.failed("类名验证失败,该类在黑名单中或包含危险特征,拒绝修改"); } if (!ClassNameValidator.isValidMethodName(sysJob.getMethodName())) { log.warn("修改定时任务失败,方法名验证不通过:{}", sysJob.getMethodName()); return R.failed("方法名验证失败,该方法在黑名单中或包含危险特征,拒绝修改"); } } sysJob.setUpdateBy(SecurityUtils.getUser().getUsername()); SysJob querySysJob = this.sysJobService.getById(sysJob.getJobId()); if (PigQuartzEnum.JOB_STATUS_NOT_RUNNING.getType().equals(querySysJob.getJobStatus())) { // 如修改暂停的需更新调度器 this.taskUtil.addOrUpateJob(sysJob, scheduler); sysJobService.updateById(sysJob); } else if (PigQuartzEnum.JOB_STATUS_RELEASE.getType().equals(querySysJob.getJobStatus())) { sysJobService.updateById(sysJob); } return R.ok(); } /** * 通过id删除定时任务 * @param id 定时任务唯一标识 * @return 操作结果 * @throws IllegalArgumentException 当任务未暂停时尝试删除会抛出异常 */ @SysLog("删除定时任务") @DeleteMapping("/{id}") @HasPermission("job_sys_job_del") @Operation(summary = "唯一标识查询定时任务,暂停任务才能删除", description = "唯一标识查询定时任务,暂停任务才能删除") public R removeById(@PathVariable Long id) { SysJob querySysJob = this.sysJobService.getById(id); if (PigQuartzEnum.JOB_STATUS_NOT_RUNNING.getType().equals(querySysJob.getJobStatus())) { this.taskUtil.removeJob(querySysJob, scheduler); this.sysJobService.removeById(id); } else if (PigQuartzEnum.JOB_STATUS_RELEASE.getType().equals(querySysJob.getJobStatus())) { this.sysJobService.removeById(id); } return R.ok(); } /** * 暂停全部定时任务 * @return R */ @SysLog("暂停全部定时任务") @PostMapping("/shutdown-jobs") @HasPermission("job_sys_job_shutdown_job") @Operation(summary = "暂停全部定时任务", description = "暂停全部定时任务") public R shutdownJobs() { taskUtil.pauseJobs(scheduler); long count = this.sysJobService.count( new LambdaQueryWrapper().eq(SysJob::getJobStatus, PigQuartzEnum.JOB_STATUS_RUNNING.getType())); if (count <= 0) { return R.ok("无正在运行定时任务"); } else { // 更新定时任务状态条件,运行状态2更新为暂停状态3 this.sysJobService.update( SysJob.builder().jobStatus(PigQuartzEnum.JOB_STATUS_NOT_RUNNING.getType()).build(), new UpdateWrapper().lambda() .eq(SysJob::getJobStatus, PigQuartzEnum.JOB_STATUS_RUNNING.getType())); return R.ok("暂停成功"); } } /** * 启动全部定时任务 * @return */ @SysLog("启动全部暂停的定时任务") @PostMapping("/start-jobs") @HasPermission("job_sys_job_start_job") @Operation(summary = "启动全部暂停的定时任务", description = "启动全部暂停的定时任务") public R startJobs() { // 更新定时任务状态条件,暂停状态3更新为运行状态2 this.sysJobService.update(SysJob.builder().jobStatus(PigQuartzEnum.JOB_STATUS_RUNNING.getType()).build(), new UpdateWrapper().lambda() .eq(SysJob::getJobStatus, PigQuartzEnum.JOB_STATUS_NOT_RUNNING.getType())); taskUtil.startJobs(scheduler); return R.ok(); } /** * 刷新全部定时任务 暂停和运行的添加到调度器其他状态从调度器移除 * @return R */ @SysLog("刷新全部定时任务") @PostMapping("/refresh-jobs") @HasPermission("job_sys_job_refresh_job") @Operation(summary = "刷新全部定时任务", description = "刷新全部定时任务") public R refreshJobs() { sysJobService.list().forEach(sysjob -> { if (PigQuartzEnum.JOB_STATUS_RUNNING.getType().equals(sysjob.getJobStatus()) || PigQuartzEnum.JOB_STATUS_NOT_RUNNING.getType().equals(sysjob.getJobStatus())) { taskUtil.addOrUpateJob(sysjob, scheduler); } else { taskUtil.removeJob(sysjob, scheduler); } }); return R.ok(); } /** * 启动定时任务 * @param jobId 任务id * @return R */ @SysLog("启动定时任务") @PostMapping("/start-job/{id}") @HasPermission("job_sys_job_start_job") @Operation(summary = "启动定时任务", description = "启动定时任务") public R startJob(@PathVariable("id") Long jobId) throws SchedulerException { SysJob querySysJob = this.sysJobService.getById(jobId); if (querySysJob == null) { return R.failed("无此定时任务,请确认"); } // 如果定时任务不存在,强制状态为1已发布 if (!scheduler.checkExists(TaskUtil.getJobKey(querySysJob))) { querySysJob.setJobStatus(PigQuartzEnum.JOB_STATUS_RELEASE.getType()); log.warn("定时任务不在quartz中,任务id:{},强制状态为已发布并加入调度器", jobId); } if (PigQuartzEnum.JOB_STATUS_RELEASE.getType().equals(querySysJob.getJobStatus())) { taskUtil.addOrUpateJob(querySysJob, scheduler); } else { taskUtil.resumeJob(querySysJob, scheduler); } // 更新定时任务状态为运行状态2 this.sysJobService .updateById(SysJob.builder().jobId(jobId).jobStatus(PigQuartzEnum.JOB_STATUS_RUNNING.getType()).build()); return R.ok(); } /** * 启动定时任务 * @param jobId 任务id * @return R */ @SysLog("立刻执行定时任务") @PostMapping("/run-job/{id}") @HasPermission("job_sys_job_run_job") @Operation(summary = "立刻执行定时任务", description = "立刻执行定时任务") public R runJob(@PathVariable("id") Long jobId) throws SchedulerException { SysJob querySysJob = this.sysJobService.getById(jobId); // 执行定时任务前判定任务是否在quartz中 if (!scheduler.checkExists(TaskUtil.getJobKey(querySysJob))) { querySysJob.setJobStatus(PigQuartzEnum.JOB_STATUS_NOT_RUNNING.getType()); log.warn("立刻执行定时任务-定时任务不在quartz中,任务id:{},强制状态为暂停并加入调度器", jobId); taskUtil.addOrUpateJob(querySysJob, scheduler); } return TaskUtil.runOnce(scheduler, querySysJob) ? R.ok() : R.failed(); } /** * 暂停定时任务 * @return */ @SysLog("暂停定时任务") @PostMapping("/shutdown-job/{id}") @HasPermission("job_sys_job_shutdown_job") @Operation(summary = "暂停定时任务", description = "暂停定时任务") public R shutdownJob(@PathVariable("id") Long id) { SysJob querySysJob = this.sysJobService.getById(id); // 更新定时任务状态条件,运行状态2更新为暂停状态3 this.sysJobService.updateById(SysJob.builder() .jobId(querySysJob.getJobId()) .jobStatus(PigQuartzEnum.JOB_STATUS_NOT_RUNNING.getType()) .build()); taskUtil.pauseJob(querySysJob, scheduler); return R.ok(); } /** * 分页查询定时执行日志 * @param page 分页参数 * @param sysJobLog 查询条件 * @return 分页结果 */ @GetMapping("/job-log") @Operation(summary = "唯一标识查询定时执行日志", description = "唯一标识查询定时执行日志") public R getJobLogPage(Page page, SysJobLog sysJobLog) { return R.ok(sysJobLogService.page(page, Wrappers.query(sysJobLog))); } /** * 校验任务名称和任务组组合是否唯一 * @param jobName 任务名称 * @param jobGroup 任务组 * @return 校验结果,若已存在返回失败信息,否则返回成功 */ @GetMapping("/is-valid-task-name") @Operation(summary = "检验任务名称和任务组联合是否唯一", description = "检验任务名称和任务组联合是否唯一") public R isValidTaskName(@RequestParam String jobName, @RequestParam String jobGroup) { return this.sysJobService .count(Wrappers.query(SysJob.builder().jobName(jobName).jobGroup(jobGroup).build())) > 0 ? R.failed("任务重复,请检查此组内是否已包含同名任务") : R.ok(); } /** * 导出任务数据 * @param sysJob 查询条件对象 * @return 符合条件的任务列表 */ @ResponseExcel @GetMapping("/export") @Operation(summary = "导出任务", description = "导出任务") public List exportJobs(SysJob sysJob) { return sysJobService.list(Wrappers.query(sysJob)); } } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/controller/SysJobLogController.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.controller; import cn.hutool.core.collection.CollUtil; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.daemon.quartz.entity.SysJobLog; import com.pig4cloud.pig.daemon.quartz.service.SysJobLogService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.AllArgsConstructor; import org.springframework.http.HttpHeaders; import org.springframework.web.bind.annotation.*; /** * 定时任务日志控制器 * * @author frwcloud * @author lengleng * @date 2025/05/31 */ @RestController @AllArgsConstructor @RequestMapping("/sys-job-log") @Tag(description = "sys-job-log", name = "定时任务日志管理模块") @SecurityRequirement(name = HttpHeaders.AUTHORIZATION) public class SysJobLogController { private final SysJobLogService sysJobLogService; /** * 分页查询定时任务日志 * @param page 分页对象 * @param sysJobLog 查询条件对象 * @return 分页查询结果 */ @GetMapping("/page") @Operation(summary = "分页定时任务日志查询", description = "分页定时任务日志查询") public R getJobLogPage(Page page, SysJobLog sysJobLog) { return R.ok(sysJobLogService.page(page, Wrappers.query(sysJobLog))); } /** * 批量删除日志 * @param ids 要删除的日志ID数组 * @return 操作结果 */ @DeleteMapping @Operation(summary = "批量删除日志", description = "批量删除日志") public R removeBatchByIds(@RequestBody Long[] ids) { return R.ok(sysJobLogService.removeBatchByIds(CollUtil.toList(ids))); } } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/entity/SysJob.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.entity; import java.io.Serial; import java.time.LocalDateTime; import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.extension.activerecord.Model; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; /** * 定时任务调度表 * * @author frwcloud */ @Data @Builder @NoArgsConstructor @AllArgsConstructor @Schema(description = "定时任务") @EqualsAndHashCode(callSuper = false) public class SysJob extends Model { @Serial private static final long serialVersionUID = 1L; /** * 任务id */ @TableId(value = "job_id", type = IdType.ASSIGN_ID) private Long jobId; /** * 任务名称 */ private String jobName; /** * 任务组名 */ private String jobGroup; /** * 组内执行顺利,值越大执行优先级越高,最大值9,最小值1 */ private String jobOrder; /** * 1、java类;2、spring bean名称;3、rest调用;4、jar调用;9其他 */ private String jobType; /** * job_type=3时,rest调用地址,仅支持rest get协议,需要增加String返回值,0成功,1失败;job_type=4时,jar路径;其它值为空 */ private String executePath; /** * job_type=1时,类完整路径;job_type=2时,spring bean名称;其它值为空 */ private String className; /** * 任务方法 */ private String methodName; /** * 参数值 */ private String methodParamsValue; /** * cron执行表达式 */ private String cronExpression; /** * 错失执行策略(1错失周期立即执行 2错失周期执行一次 3下周期执行) */ private String misfirePolicy; /** * 1、多租户任务;2、非多租户任务 */ private String jobTenantType; /** * 状态(0、未发布;1、已发布;2、运行中;3、暂停;4、删除;) */ private String jobStatus; /** * 状态(0正常 1异常) */ private String jobExecuteStatus; /** * 创建者 */ @TableField(fill = FieldFill.INSERT) private String createBy; /** * 更新者 */ @TableField(fill = FieldFill.UPDATE) private String updateBy; /** * 创建时间 */ @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; /** * 修改时间 */ @TableField(fill = FieldFill.UPDATE) private LocalDateTime updateTime; /** * 首次执行时间 */ private LocalDateTime startTime; /** * 上次执行时间 */ private LocalDateTime previousTime; /** * 下次执行时间 */ private LocalDateTime nextTime; /** * 备注信息 */ private String remark; } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/entity/SysJobLog.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.entity; import java.io.Serial; import java.time.LocalDateTime; import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.extension.activerecord.Model; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; /** * 定时任务执行日志表 * * @author frwcloud * @date 2019-01-27 13:40:20 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode(callSuper = false) @Schema(description = "定时任务日志") public class SysJobLog extends Model { @Serial private static final long serialVersionUID = 1L; /** * 任务日志ID */ @TableId(value = "job_log_id", type = IdType.ASSIGN_ID) private Long jobLogId; /** * 任务id */ private Long jobId; /** * 任务名称 */ private String jobName; /** * 任务组名 */ private String jobGroup; /** * 组内执行顺利,值越大执行优先级越高,最大值9,最小值1 */ private String jobOrder; /** * 1、java类;2、spring bean名称;3、rest调用;4、jar调用;9其他 */ private String jobType; /** * job_type=3时,rest调用地址,仅支持post协议;job_type=4时,jar路径;其它值为空 */ private String executePath; /** * job_type=1时,类完整路径;job_type=2时,spring bean名称;其它值为空 */ private String className; /** * 任务方法 */ private String methodName; /** * 参数值 */ private String methodParamsValue; /** * cron执行表达式 */ private String cronExpression; /** * 日志信息 */ private String jobMessage; /** * 执行状态(0正常 1失败) */ private String jobLogStatus; /** * 执行时间 */ private String executeTime; /** * 异常信息 */ private String exceptionInfo; /** * 创建时间 */ @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/event/SysJobEvent.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.event; import com.pig4cloud.pig.daemon.quartz.entity.SysJob; import lombok.AllArgsConstructor; import lombok.Getter; import org.quartz.Trigger; /** * 系统任务事件类,用于封装定时任务及其触发器 * * @author frwcloud * @author lengleng * @date 2025/05/31 */ @Getter @AllArgsConstructor public class SysJobEvent { private final SysJob sysJob; private final Trigger trigger; } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/event/SysJobListener.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.event; import org.quartz.Trigger; import org.springframework.context.event.EventListener; import org.springframework.core.annotation.Order; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import com.pig4cloud.pig.daemon.quartz.entity.SysJob; import com.pig4cloud.pig.daemon.quartz.util.TaskInvokUtil; import lombok.RequiredArgsConstructor; /** * 系统任务监听器:用于异步监听并处理定时任务事件 * * @author lengleng * @date 2025/05/31 */ @Service @RequiredArgsConstructor public class SysJobListener { private final TaskInvokUtil taskInvokUtil; @Async @Order @EventListener(SysJobEvent.class) public void comSysJob(SysJobEvent event) { SysJob sysJob = event.getSysJob(); Trigger trigger = event.getTrigger(); taskInvokUtil.invokMethod(sysJob, trigger); } } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/event/SysJobLogEvent.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.event; import com.pig4cloud.pig.daemon.quartz.entity.SysJobLog; import lombok.AllArgsConstructor; import lombok.Getter; /** * 定时任务日志多线程事件 * * @author frwcloud * @author lengleng * @date 2025/05/31 */ @Getter @AllArgsConstructor public class SysJobLogEvent { private final SysJobLog sysJobLog; } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/event/SysJobLogListener.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.event; import com.pig4cloud.pig.daemon.quartz.entity.SysJobLog; import com.pig4cloud.pig.daemon.quartz.service.SysJobLogService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.event.EventListener; import org.springframework.core.annotation.Order; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; /** * 系统任务日志监听器:用于异步监听并处理定时任务日志事件 * * @author frwcloud * @author lengleng * @date 2025/05/31 */ @Slf4j @Service @RequiredArgsConstructor public class SysJobLogListener { private final SysJobLogService sysJobLogService; /** * 异步保存系统任务日志 * @param event 系统任务日志事件 */ @Async @Order @EventListener(SysJobLogEvent.class) public void saveSysJobLog(SysJobLogEvent event) { SysJobLog sysJobLog = event.getSysJobLog(); sysJobLogService.save(sysJobLog); log.info("执行定时任务日志"); } } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/exception/TaskException.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.exception; import java.io.Serial; /** * 定时任务异常类 * * @author lengleng * @date 2025/05/31 */ public class TaskException extends Exception { @Serial private static final long serialVersionUID = 1L; /** * 无参构造方法,创建一个TaskException实例 */ public TaskException() { super(); } /** * 构造方法,使用指定消息创建TaskException实例 * @param msg 异常信息 */ public TaskException(String msg) { super(msg); } } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/mapper/SysJobLogMapper.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.pig4cloud.pig.daemon.quartz.entity.SysJobLog; import org.apache.ibatis.annotations.Mapper; /** * 定时任务执行日志表 Mapper 接口 * * @author lengleng * @date 2025/05/31 */ @Mapper public interface SysJobLogMapper extends BaseMapper { } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/mapper/SysJobMapper.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.pig4cloud.pig.daemon.quartz.entity.SysJob; import org.apache.ibatis.annotations.Mapper; /** * 定时任务调度表 Mapper 接口 * * @author lengleng * @date 2025/05/31 */ @Mapper public interface SysJobMapper extends BaseMapper { } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/service/SysJobLogService.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.service; import com.baomidou.mybatisplus.extension.service.IService; import com.pig4cloud.pig.daemon.quartz.entity.SysJobLog; /** * 定时任务执行日志服务接口 * * @author lengleng * @date 2025/05/31 */ public interface SysJobLogService extends IService { } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/service/SysJobService.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.service; import com.baomidou.mybatisplus.extension.service.IService; import com.pig4cloud.pig.daemon.quartz.entity.SysJob; /** * 定时任务调度服务接口 * * @author lengleng * @date 2025/05/31 */ public interface SysJobService extends IService { } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/service/impl/SysJobLogServiceImpl.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.service.impl; import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.pig4cloud.pig.daemon.quartz.entity.SysJobLog; import com.pig4cloud.pig.daemon.quartz.mapper.SysJobLogMapper; import com.pig4cloud.pig.daemon.quartz.service.SysJobLogService; import lombok.AllArgsConstructor; /** * 定时任务执行日志服务实现类 * * @author lengleng * @date 2025/05/31 */ @Service @AllArgsConstructor public class SysJobLogServiceImpl extends ServiceImpl implements SysJobLogService { } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/service/impl/SysJobServiceImpl.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.service.impl; import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.pig4cloud.pig.daemon.quartz.entity.SysJob; import com.pig4cloud.pig.daemon.quartz.mapper.SysJobMapper; import com.pig4cloud.pig.daemon.quartz.service.SysJobService; import lombok.AllArgsConstructor; /** * 定时任务调度服务实现类 * * @author lengleng * @date 2025/05/31 */ @Service @AllArgsConstructor public class SysJobServiceImpl extends ServiceImpl implements SysJobService { } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/task/RestTaskDemo.java ================================================ package com.pig4cloud.pig.daemon.quartz.task; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.security.annotation.Inner; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; 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 java.time.LocalDateTime; /** * 用于测试REST风格调用的演示类 * * @author lengleng * @date 2025/05/31 */ @Slf4j @RestController @RequestMapping("/inner-job") @Tag(description = "REST风格", name = "REST风格调用管理模块") public class RestTaskDemo { /** * REST风格调用定时任务的演示方法 * @param param 路径参数 * @return 统一响应结果 */ @Inner(value = false) @GetMapping("/{param}") @Operation(summary = "REST风格调用定时任务的演示方法", description = "REST风格调用定时任务的演示方法") public R demoMethod(@PathVariable("param") String param) { log.info("测试于:{},传入参数{}", LocalDateTime.now(), param); return R.ok(); } } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/task/SpringBeanTaskDemo.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.task; import com.pig4cloud.pig.daemon.quartz.constants.PigQuartzEnum; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.time.LocalDateTime; /** * Spring Bean任务演示类 * * @author lengleng * @author 郑健楠 * @date 2025/05/31 */ @Slf4j @Component("demo") public class SpringBeanTaskDemo { /** * 演示方法,用于测试Spring Bean * @param para 输入参数 * @return 返回任务日志状态成功类型 * @throws Exception 可能抛出的异常 */ @SneakyThrows public String demoMethod(String para) { log.info("测试于:{},输入参数{}", LocalDateTime.now(), para); return PigQuartzEnum.JOB_LOG_STATUS_SUCCESS.getType(); } } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/util/ClassNameValidator.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.util; import cn.hutool.core.util.StrUtil; import lombok.extern.slf4j.Slf4j; import java.util.Arrays; import java.util.HashSet; import java.util.Set; /** * 类名验证器,用于防止反射执行恶意类 * * @author lengleng * @date 2025/10/22 */ @Slf4j public class ClassNameValidator { /** * 危险类的黑名单 - 禁止使用的类和包 */ private static final Set DANGEROUS_CLASSES = new HashSet<>(Arrays.asList( // EL表达式处理器 - 可执行任意代码 "jakarta.el.ELProcessor", "javax.el.ELProcessor", // 脚本引擎 - 可执行任意脚本 "javax.script.ScriptEngineManager", "javax.script.ScriptEngine", // 反射相关 - 可绕过安全限制 "java.lang.reflect.Method", "java.lang.reflect.Constructor", "java.lang.reflect.Field", // 类加载器 - 可加载恶意类 "java.lang.ClassLoader", "java.net.URLClassLoader", // 运行时 - 可执行系统命令 "java.lang.Runtime", "java.lang.ProcessBuilder", "java.lang.Process", // JNDI - 可进行远程代码执行 "javax.naming.InitialContext", "javax.naming.Context", // RMI - 可进行远程代码执行 "java.rmi.registry.Registry", "java.rmi.server.UnicastRemoteObject", // 序列化 - 可导致反序列化漏洞 "java.io.ObjectInputStream", "java.io.ObjectOutputStream")); /** * 危险包前缀黑名单 */ private static final Set DANGEROUS_PACKAGES = new HashSet<>(Arrays.asList("sun.", "com.sun.", "jdk.internal.", "java.lang.reflect.", "java.security.", "javax.naming.", "javax.script.", "java.rmi.", "javax.management.", "org.springframework.context.support.FileSystemXmlApplicationContext", "org.springframework.expression.spel.")); /** * 验证类名是否安全 * @param className 要验证的类名 * @return 如果类名安全返回true,否则返回false */ public static boolean isValidClassName(String className) { if (StrUtil.isBlank(className)) { log.warn("类名为空,验证失败"); return false; } // 检查是否在危险类黑名单中 if (DANGEROUS_CLASSES.contains(className)) { log.warn("类名 [{}] 在危险类黑名单中,拒绝执行", className); return false; } // 检查是否以危险包前缀开头 for (String dangerousPackage : DANGEROUS_PACKAGES) { if (className.startsWith(dangerousPackage)) { log.warn("类名 [{}] 以危险包前缀 [{}] 开头,拒绝执行", className, dangerousPackage); return false; } } // 检查是否包含危险特征 if (className.toLowerCase().contains("runtime") || className.toLowerCase().contains("processbuilder") || className.toLowerCase().contains("elprocessor") || className.toLowerCase().contains("scriptengine") || className.toLowerCase().contains("classloader")) { log.warn("类名 [{}] 包含危险特征,拒绝执行", className); return false; } return true; } /** * 验证方法名是否安全 * @param methodName 要验证的方法名 * @return 如果方法名安全返回true,否则返回false */ public static boolean isValidMethodName(String methodName) { if (StrUtil.isBlank(methodName)) { log.warn("方法名为空,验证失败"); return false; } // 检查方法名是否包含危险特征 Set dangerousMethods = new HashSet<>(Arrays.asList("exec", "eval", "execute", "invoke", "newInstance", "forName", "getRuntime", "loadClass", "defineClass", "getMethod", "getDeclaredMethod")); if (dangerousMethods.contains(methodName)) { log.warn("方法名 [{}] 在危险方法黑名单中,拒绝执行", methodName); return false; } return true; } } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/util/ITaskInvok.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.util; import com.pig4cloud.pig.daemon.quartz.entity.SysJob; import com.pig4cloud.pig.daemon.quartz.exception.TaskException; /** * 定时任务反射实现接口 * * @author lengleng * @date 2025/05/31 */ public interface ITaskInvok { /** * 执行反射方法 * @param sysJob 任务配置类 * @throws TaskException 执行任务时可能抛出的异常 */ void invokMethod(SysJob sysJob) throws TaskException; } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/util/JarTaskInvok.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.util; import cn.hutool.core.util.StrUtil; import com.pig4cloud.pig.daemon.quartz.entity.SysJob; import com.pig4cloud.pig.daemon.quartz.exception.TaskException; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * 定时任务可执行jar反射实现类 * * @author lengleng * @date 2025/05/31 */ @Slf4j @Component("jarTaskInvok") public class JarTaskInvok implements ITaskInvok { /** * 调用方法执行定时任务jar * @param sysJob 定时任务信息 * @throws TaskException 执行任务时发生异常抛出 */ @Override public void invokMethod(SysJob sysJob) throws TaskException { ProcessBuilder processBuilder = new ProcessBuilder(); File jar = new File(sysJob.getExecutePath()); processBuilder.directory(jar.getParentFile()); List commands = new ArrayList<>(); commands.add("java"); commands.add("-jar"); commands.add(sysJob.getExecutePath()); if (StrUtil.isNotEmpty(sysJob.getMethodParamsValue())) { commands.add(sysJob.getMethodParamsValue()); } processBuilder.command(commands); try { processBuilder.start(); } catch (IOException e) { log.error("定时任务jar反射执行异常,执行任务:{}", sysJob.getExecutePath()); throw new TaskException("定时任务jar反射执行异常,执行任务:" + sysJob.getExecutePath()); } } } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/util/JavaClassTaskInvok.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.util; import cn.hutool.core.util.StrUtil; import com.pig4cloud.pig.daemon.quartz.constants.PigQuartzEnum; import com.pig4cloud.pig.daemon.quartz.entity.SysJob; import com.pig4cloud.pig.daemon.quartz.exception.TaskException; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * 基于Java反射实现的定时任务调用类 * * @author lengleng * @date 2025/05/31 */ @Component("javaClassTaskInvok") @Slf4j public class JavaClassTaskInvok implements ITaskInvok { /** * 调用定时任务方法 * @param sysJob 定时任务信息 * @throws TaskException 执行任务过程中出现异常时抛出 */ @Override public void invokMethod(SysJob sysJob) throws TaskException { // 安全验证:检查类名和方法名是否合法 if (!ClassNameValidator.isValidClassName(sysJob.getClassName())) { log.error("定时任务类名验证失败,类名包含危险特征或在黑名单中:{}", sysJob.getClassName()); throw new TaskException("定时任务类名验证失败,拒绝执行危险类:" + sysJob.getClassName()); } if (!ClassNameValidator.isValidMethodName(sysJob.getMethodName())) { log.error("定时任务方法名验证失败,方法名包含危险特征:{}", sysJob.getMethodName()); throw new TaskException("定时任务方法名验证失败,拒绝执行危险方法:" + sysJob.getMethodName()); } Object obj; Class clazz; Method method; Object returnValue; try { if (StrUtil.isNotEmpty(sysJob.getMethodParamsValue())) { clazz = Class.forName(sysJob.getClassName()); obj = clazz.getDeclaredConstructor().newInstance(); method = clazz.getDeclaredMethod(sysJob.getMethodName(), String.class); returnValue = method.invoke(obj, sysJob.getMethodParamsValue()); } else { clazz = Class.forName(sysJob.getClassName()); obj = clazz.getDeclaredConstructor().newInstance(); method = clazz.getDeclaredMethod(sysJob.getMethodName()); returnValue = method.invoke(obj); } if (StrUtil.isEmpty(returnValue.toString()) || PigQuartzEnum.JOB_LOG_STATUS_FAIL.getType().equals(returnValue.toString())) { log.error("定时任务javaClassTaskInvok异常,执行任务:{}", sysJob.getClassName()); throw new TaskException("定时任务javaClassTaskInvok业务执行失败,任务:" + sysJob.getClassName()); } } catch (ClassNotFoundException e) { log.error("定时任务java反射类没有找到,执行任务:{}", sysJob.getClassName()); throw new TaskException("定时任务java反射类没有找到,执行任务:" + sysJob.getClassName()); } catch (IllegalAccessException e) { log.error("定时任务java反射类异常,执行任务:{}", sysJob.getClassName()); throw new TaskException("定时任务java反射类异常,执行任务:" + sysJob.getClassName()); } catch (InstantiationException e) { log.error("定时任务java反射类异常,执行任务:{}", sysJob.getClassName()); throw new TaskException("定时任务java反射类异常,执行任务:" + sysJob.getClassName()); } catch (NoSuchMethodException e) { log.error("定时任务java反射执行方法名异常,执行任务:{}", sysJob.getClassName()); throw new TaskException("定时任务java反射执行方法名异常,执行任务:" + sysJob.getClassName()); } catch (InvocationTargetException e) { log.error("定时任务java反射执行异常,执行任务:{}", sysJob.getClassName()); throw new TaskException("定时任务java反射执行异常,执行任务:" + sysJob.getClassName()); } } } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/util/RestTaskInvok.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.util; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpUtil; import com.pig4cloud.pig.daemon.quartz.entity.SysJob; import com.pig4cloud.pig.daemon.quartz.exception.TaskException; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; /** * REST定时任务反射实现类 * * @author lengleng * @date 2025/05/31 */ @Slf4j @AllArgsConstructor @Component("restTaskInvok") public class RestTaskInvok implements ITaskInvok { /** * 调用方法执行定时任务 * @param sysJob 定时任务信息 * @throws TaskException 任务执行失败时抛出异常 */ @Override public void invokMethod(SysJob sysJob) throws TaskException { try { HttpRequest request = HttpUtil.createGet(sysJob.getExecutePath()); request.execute(); } catch (Exception e) { log.error("定时任务restTaskInvok异常,执行任务:{}", sysJob.getExecutePath()); throw new TaskException("定时任务restTaskInvok业务执行失败,任务:" + sysJob.getExecutePath()); } } } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/util/SpringBeanTaskInvok.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.util; import cn.hutool.core.util.StrUtil; import com.pig4cloud.pig.common.core.util.SpringContextHolder; import com.pig4cloud.pig.daemon.quartz.constants.PigQuartzEnum; import com.pig4cloud.pig.daemon.quartz.entity.SysJob; import com.pig4cloud.pig.daemon.quartz.exception.TaskException; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.util.ReflectionUtils; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * 基于Spring Bean的定时任务反射执行器 * * @author lengleng * @date 2025/05/31 */ @Component("springBeanTaskInvok") @Slf4j public class SpringBeanTaskInvok implements ITaskInvok { /** * 调用定时任务方法 * @param sysJob 定时任务信息 * @throws TaskException 当任务执行失败或反射调用异常时抛出 */ @Override public void invokMethod(SysJob sysJob) throws TaskException { Object target; Method method; Object returnValue; // 通过Spring上下文去找 也有可能找不到 target = SpringContextHolder.getBean(sysJob.getClassName()); try { if (StrUtil.isNotEmpty(sysJob.getMethodParamsValue())) { method = target.getClass().getDeclaredMethod(sysJob.getMethodName(), String.class); ReflectionUtils.makeAccessible(method); returnValue = method.invoke(target, sysJob.getMethodParamsValue()); } else { method = target.getClass().getDeclaredMethod(sysJob.getMethodName()); ReflectionUtils.makeAccessible(method); returnValue = method.invoke(target); } if (StrUtil.isEmpty(returnValue.toString()) || PigQuartzEnum.JOB_LOG_STATUS_FAIL.getType().equals(returnValue.toString())) { log.error("定时任务springBeanTaskInvok异常,执行任务:{}", sysJob.getClassName()); throw new TaskException("定时任务springBeanTaskInvok业务执行失败,任务:" + sysJob.getClassName()); } } catch (NoSuchMethodException e) { log.error("定时任务spring bean反射异常方法未找到,执行任务:{}", sysJob.getClassName()); throw new TaskException("定时任务spring bean反射异常方法未找到,执行任务:" + sysJob.getClassName()); } catch (IllegalAccessException e) { log.error("定时任务spring bean反射异常,执行任务:{}", sysJob.getClassName()); throw new TaskException("定时任务spring bean反射异常,执行任务:" + sysJob.getClassName()); } catch (InvocationTargetException e) { log.error("定时任务spring bean反射执行异常,执行任务:{}", sysJob.getClassName()); throw new TaskException("定时任务spring bean反射执行异常,执行任务:" + sysJob.getClassName()); } } } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/util/TaskInvokFactory.java ================================================ package com.pig4cloud.pig.daemon.quartz.util; import cn.hutool.core.util.StrUtil; import com.pig4cloud.pig.common.core.util.SpringContextHolder; import com.pig4cloud.pig.daemon.quartz.constants.JobTypeQuartzEnum; import com.pig4cloud.pig.daemon.quartz.exception.TaskException; import lombok.extern.slf4j.Slf4j; /** * 任务调用工厂类:根据任务类型获取对应的任务调用器 * * @author lengleng * @version 1.0 * @date 2025/05/31 */ @Slf4j public class TaskInvokFactory { /** * 根据任务类型获取对应的任务执行器 * @param jobType 任务类型 * @return 任务执行器实例 * @throws TaskException 当任务类型为空或不支持时抛出异常 */ public static ITaskInvok getInvoker(String jobType) throws TaskException { if (StrUtil.isBlank(jobType)) { log.info("获取TaskInvok传递参数有误,jobType:{}", jobType); throw new TaskException(""); } ITaskInvok iTaskInvok = null; if (JobTypeQuartzEnum.JAVA.getType().equals(jobType)) { iTaskInvok = SpringContextHolder.getBean("javaClassTaskInvok"); } else if (JobTypeQuartzEnum.SPRING_BEAN.getType().equals(jobType)) { iTaskInvok = SpringContextHolder.getBean("springBeanTaskInvok"); } else if (JobTypeQuartzEnum.REST.getType().equals(jobType)) { iTaskInvok = SpringContextHolder.getBean("restTaskInvok"); } else if (JobTypeQuartzEnum.JAR.getType().equals(jobType)) { iTaskInvok = SpringContextHolder.getBean("jarTaskInvok"); } else if (StrUtil.isBlank(jobType)) { log.info("定时任务类型无对应反射方式,反射类型:{}", jobType); throw new TaskException(""); } return iTaskInvok; } } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/util/TaskInvokUtil.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.util; import cn.hutool.core.util.StrUtil; import com.pig4cloud.pig.daemon.quartz.constants.PigQuartzEnum; import com.pig4cloud.pig.daemon.quartz.entity.SysJob; import com.pig4cloud.pig.daemon.quartz.entity.SysJobLog; import com.pig4cloud.pig.daemon.quartz.event.SysJobLogEvent; import com.pig4cloud.pig.daemon.quartz.service.SysJobService; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.quartz.CronTrigger; import org.quartz.Trigger; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; import java.time.ZoneId; import java.util.Date; /** * 定时任务反射工具类,用于执行和管理定时任务的反射调用 * * @author lengleng * @date 2025/05/31 */ @Slf4j @Component @RequiredArgsConstructor public class TaskInvokUtil { private final ApplicationEventPublisher publisher; private final SysJobService sysJobService; /** * 执行定时任务方法 * @param sysJob 定时任务信息 * @param trigger 触发器 */ @SneakyThrows public void invokMethod(SysJob sysJob, Trigger trigger) { // 执行开始时间 long startTime; // 执行结束时间 long endTime; // 获取执行开始时间 startTime = System.currentTimeMillis(); // 更新定时任务表内的状态、执行时间、上次执行时间、下次执行时间等信息 SysJob updateSysjob = new SysJob(); updateSysjob.setJobId(sysJob.getJobId()); // 日志 SysJobLog sysJobLog = new SysJobLog(); sysJobLog.setJobId(sysJob.getJobId()); sysJobLog.setJobName(sysJob.getJobName()); sysJobLog.setJobGroup(sysJob.getJobGroup()); sysJobLog.setJobOrder(sysJob.getJobOrder()); sysJobLog.setJobType(sysJob.getJobType()); sysJobLog.setExecutePath(sysJob.getExecutePath()); sysJobLog.setClassName(sysJob.getClassName()); sysJobLog.setMethodName(sysJob.getMethodName()); sysJobLog.setMethodParamsValue(sysJob.getMethodParamsValue()); sysJobLog.setCronExpression(sysJob.getCronExpression()); try { // 执行任务 ITaskInvok iTaskInvok = TaskInvokFactory.getInvoker(sysJob.getJobType()); // 确保租户上下文有值,使得当前线程中的多租户特性生效。 iTaskInvok.invokMethod(sysJob); // 记录成功状态 sysJobLog.setJobMessage(PigQuartzEnum.JOB_LOG_STATUS_SUCCESS.getDescription()); sysJobLog.setJobLogStatus(PigQuartzEnum.JOB_LOG_STATUS_SUCCESS.getType()); // 任务表信息更新 updateSysjob.setJobExecuteStatus(PigQuartzEnum.JOB_LOG_STATUS_SUCCESS.getType()); } catch (Throwable e) { log.error("定时任务执行失败,任务名称:{};任务组名:{},cron执行表达式:{},执行时间:{}", sysJob.getJobName(), sysJob.getJobGroup(), sysJob.getCronExpression(), new Date()); // 记录失败状态 sysJobLog.setJobMessage(PigQuartzEnum.JOB_LOG_STATUS_FAIL.getDescription()); sysJobLog.setJobLogStatus(PigQuartzEnum.JOB_LOG_STATUS_FAIL.getType()); sysJobLog.setExceptionInfo(StrUtil.sub(e.getMessage(), 0, 2000)); // 任务表信息更新 updateSysjob.setJobExecuteStatus(PigQuartzEnum.JOB_LOG_STATUS_FAIL.getType()); } finally { // 记录执行时间 立刻执行使用的是simpleTeigger if (trigger instanceof CronTrigger) { updateSysjob .setStartTime(trigger.getStartTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()); updateSysjob.setPreviousTime( trigger.getPreviousFireTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()); updateSysjob.setNextTime( trigger.getNextFireTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()); } // 记录执行时长 endTime = System.currentTimeMillis(); sysJobLog.setExecuteTime(String.valueOf(endTime - startTime)); publisher.publishEvent(new SysJobLogEvent(sysJobLog)); sysJobService.updateById(updateSysjob); } } } ================================================ FILE: pig-visual/pig-quartz/src/main/java/com/pig4cloud/pig/daemon/quartz/util/TaskUtil.java ================================================ /* * Copyright (c) 2018-2025, lengleng All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the pig4cloud.com developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: lengleng (wangiegie@gmail.com) */ package com.pig4cloud.pig.daemon.quartz.util; import com.pig4cloud.pig.daemon.quartz.config.PigQuartzFactory; import com.pig4cloud.pig.daemon.quartz.constants.PigQuartzEnum; import com.pig4cloud.pig.daemon.quartz.entity.SysJob; import lombok.extern.slf4j.Slf4j; import org.quartz.*; import org.springframework.stereotype.Component; /** * 定时任务工具类,提供定时任务的增删改查及状态管理功能 * * @author lengleng * @date 2025/05/31 */ @Slf4j @Component public class TaskUtil { /** * 获取定时任务的唯一key * @param sysjob 定时任务信息 * @return 定时任务的唯一key */ public static JobKey getJobKey(SysJob sysjob) { return JobKey.jobKey(sysjob.getJobName(), sysjob.getJobGroup()); } /** * 获取定时任务触发器的唯一键 * @param sysjob 定时任务信息 * @return 定时任务触发器键 */ public static TriggerKey getTriggerKey(SysJob sysjob) { return TriggerKey.triggerKey(sysjob.getJobName(), sysjob.getJobGroup()); } /** * 添加或更新定时任务 * @param sysjob 任务信息 * @param scheduler 调度器 */ public void addOrUpateJob(SysJob sysjob, Scheduler scheduler) { CronTrigger trigger = null; try { JobKey jobKey = getJobKey(sysjob); // 获得触发器 TriggerKey triggerKey = getTriggerKey(sysjob); trigger = (CronTrigger) scheduler.getTrigger(triggerKey); // 判断触发器是否存在(如果存在说明之前运行过但是在当前被禁用了,如果不存在说明一次都没运行过) if (trigger == null) { // 新建一个工作任务 指定任务类型为串接进行的 JobDetail jobDetail = JobBuilder.newJob(PigQuartzFactory.class).withIdentity(jobKey).build(); // 将任务信息添加到任务信息中 jobDetail.getJobDataMap().put(PigQuartzEnum.SCHEDULE_JOB_KEY.getType(), sysjob); // 将cron表达式进行转换 CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(sysjob.getCronExpression()); cronScheduleBuilder = this.handleCronScheduleMisfirePolicy(sysjob, cronScheduleBuilder); // 创建触发器并将cron表达式对象给塞入 trigger = TriggerBuilder.newTrigger() .withIdentity(triggerKey) .withSchedule(cronScheduleBuilder) .build(); // 在调度器中将触发器和任务进行组合 scheduler.scheduleJob(jobDetail, trigger); } else { CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(sysjob.getCronExpression()); cronScheduleBuilder = this.handleCronScheduleMisfirePolicy(sysjob, cronScheduleBuilder); // 按照新的规则进行 trigger = trigger.getTriggerBuilder() .withIdentity(triggerKey) .withSchedule(cronScheduleBuilder) .build(); // 将任务信息更新到任务信息中 trigger.getJobDataMap().put(PigQuartzEnum.SCHEDULE_JOB_KEY.getType(), sysjob); // 重启 scheduler.rescheduleJob(triggerKey, trigger); } // 如任务状态为暂停 if (sysjob.getJobStatus().equals(PigQuartzEnum.JOB_STATUS_NOT_RUNNING.getType())) { this.pauseJob(sysjob, scheduler); } } catch (SchedulerException e) { log.error("添加或更新定时任务,失败信息:{}", e.getMessage()); } } /** * 立即执行一次任务 * @param scheduler 调度器 * @param sysJob 任务信息 * @return 任务是否执行成功 */ public static boolean runOnce(Scheduler scheduler, SysJob sysJob) { try { // 参数 JobDataMap dataMap = new JobDataMap(); dataMap.put(PigQuartzEnum.SCHEDULE_JOB_KEY.getType(), sysJob); scheduler.triggerJob(getJobKey(sysJob), dataMap); } catch (SchedulerException e) { log.error("立刻执行定时任务,失败信息:{}", e.getMessage()); return false; } return true; } /** * 暂停定时任务 * @param sysjob 任务信息 * @param scheduler 任务调度器 */ public void pauseJob(SysJob sysjob, Scheduler scheduler) { try { if (scheduler != null) { scheduler.pauseJob(getJobKey(sysjob)); } } catch (SchedulerException e) { log.error("暂停任务失败,失败信息:{}", e.getMessage()); } } /** * 恢复定时任务 * @param sysjob 任务信息 * @param scheduler 任务调度器 */ public void resumeJob(SysJob sysjob, Scheduler scheduler) { try { if (scheduler != null) { scheduler.resumeJob(getJobKey(sysjob)); } } catch (SchedulerException e) { log.error("恢复任务失败,失败信息:{}", e.getMessage()); } } /** * 移除定时任务 * @param sysjob 定时任务信息 * @param scheduler 任务调度器 */ public void removeJob(SysJob sysjob, Scheduler scheduler) { try { if (scheduler != null) { // 停止触发器 scheduler.pauseTrigger(getTriggerKey(sysjob)); // 移除触发器 scheduler.unscheduleJob(getTriggerKey(sysjob)); // 删除任务 scheduler.deleteJob(getJobKey(sysjob)); } } catch (Exception e) { log.error("移除定时任务失败,失败信息:{}", e.getMessage()); } } /** * 启动所有运行中的定时任务 * @param scheduler 调度器实例 * @throws SchedulerException 当启动任务失败时抛出异常 */ public void startJobs(Scheduler scheduler) { try { if (scheduler != null) { scheduler.resumeAll(); } } catch (SchedulerException e) { log.error("启动所有运行定时任务失败,失败信息:{}", e.getMessage()); } } /** * 暂停所有运行中的定时任务 * @param scheduler 调度器实例 * @throws Exception 暂停任务过程中可能抛出的异常 */ public void pauseJobs(Scheduler scheduler) { try { if (scheduler != null) { scheduler.pauseAll(); } } catch (Exception e) { log.error("暂停所有运行定时任务失败,失败信息:{}", e.getMessage()); } } /** * 根据任务配置处理错失执行策略 * @param sysJob 任务信息 * @param cronScheduleBuilder 原始Cron调度构建器 * @return 处理后的Cron调度构建器 */ private CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob sysJob, CronScheduleBuilder cronScheduleBuilder) { if (PigQuartzEnum.MISFIRE_DEFAULT.getType().equals(sysJob.getMisfirePolicy())) { return cronScheduleBuilder; } else if (PigQuartzEnum.MISFIRE_IGNORE_MISFIRES.getType().equals(sysJob.getMisfirePolicy())) { return cronScheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires(); } else if (PigQuartzEnum.MISFIRE_FIRE_AND_PROCEED.getType().equals(sysJob.getMisfirePolicy())) { return cronScheduleBuilder.withMisfireHandlingInstructionFireAndProceed(); } else if (PigQuartzEnum.MISFIRE_DO_NOTHING.getType().equals(sysJob.getMisfirePolicy())) { return cronScheduleBuilder.withMisfireHandlingInstructionDoNothing(); } else { return cronScheduleBuilder; } } /** * 校验cron表达式是否合法 * @param cronExpression 待校验的cron表达式 * @return true表示合法,false表示不合法 */ public boolean isValidCron(String cronExpression) { return CronExpression.isValidExpression(cronExpression); } } ================================================ FILE: pig-visual/pig-quartz/src/main/resources/application.yml ================================================ server: port: 5007 spring: application: name: @artifactId@ cloud: nacos: username: @nacos.username@ password: @nacos.password@ discovery: server-addr: ${NACOS_HOST:127.0.0.1}:${NACOS_PORT:8848} config: server-addr: ${spring.cloud.nacos.discovery.server-addr} config: import: - optional:nacos:application-@profiles.active@.yml - optional:nacos:${spring.application.name}-@profiles.active@.yml ================================================ FILE: pig-visual/pig-quartz/src/main/resources/logback-spring.xml ================================================ ${CONSOLE_LOG_PATTERN} ${log.path}/debug.log ${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz 50MB 30 ${FILE_LOG_PATTERN} ${log.path}/error.log ${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz 50MB 30 ${FILE_LOG_PATTERN} ERROR ================================================ FILE: pig-visual/pig-quartz/src/main/resources/quartz-config.yml ================================================ # 如下配置为使用数据库存储定时任务,属性不经常修改,所以不放在 application.yml 中统一管理 spring: quartz: properties: org: quartz: scheduler: instanceName: clusteredScheduler instanceId: AUTO jobStore: class: org.springframework.scheduling.quartz.LocalDataSourceJobStore # 数据库存储 driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate # 数据库代理 tablePrefix: QRTZ_ # 表前缀 isClustered: true # 集群 clusterCheckinInterval: 30000 # 集群检查间隔 useProperties: false threadPool: class: org.quartz.simpl.SimpleThreadPool # 线程池 threadCount: 50 # 线程数量 threadPriority: 5 # 线程优先级 threadsInheritContextClassLoaderOfInitializingThread: true # 是否继承类加载器 job-store-type: jdbc # 持久化到数据库 jdbc: initialize-schema: never # 不初始化表结构 ================================================ FILE: pig-visual/pom.xml ================================================ 4.0.0 com.pig4cloud pig ${revision} pig-visual pig 图形化相关功能 pom pig-codegen pig-monitor pig-quartz ================================================ FILE: pom.xml ================================================ 4.0.0 com.pig4cloud pig ${project.artifactId} ${revision} pom https://www.pig4cloud.com 3.9.2 17 3.5.11 2025.0.1 2025.0.0.0 3.0.5 9.0.1 pig4cloud 17 http://192.168.0.100:2375 3.20.0 0.45.1 0.0.47 3.5.6 1.6.0 username password registry.cn-shanghai.aliyuncs.com UTF-8 3.14.0 org.springframework.boot spring-boot-configuration-processor true com.github.ulisesbocchio jasypt-spring-boot-starter ${jasypt.version} org.springframework.boot spring-boot-starter-actuator de.codecentric spring-boot-admin-starter-client ${spring-boot-admin.version} org.projectlombok lombok provided com.sun.xml.bind jaxb-impl org.springframework.boot spring-boot-starter-test test pig-register pig-gateway pig-auth pig-upms pig-common pig-visual com.pig4cloud pig-common-bom ${project.version} pom import org.springframework.boot spring-boot-dependencies ${spring-boot.version} pom import org.springframework.cloud spring-cloud-dependencies ${spring-cloud.version} pom import org.apache.commons commons-lang3 ${commons-lang3.version} com.alibaba.cloud spring-cloud-alibaba-dependencies ${spring-cloud-alibaba.version} pom import org.springframework.boot spring-boot-starter-web ${spring-boot.version} spring-boot-starter-tomcat org.springframework.boot com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery ${spring-cloud-alibaba.version} ${project.name} src/main/resources true org.springframework.boot spring-boot-maven-plugin ${spring-boot.version} ${project.build.finalName} true repackage io.fabric8 docker-maven-plugin ${docker.plugin.version} ${docker.host} ${docker.registry} ${docker.username} ${docker.password} ${docker.registry}/${docker.namespace}/${project.name}:${project.version} ${project.basedir}/Dockerfile org.codehaus.mojo flatten-maven-plugin ${flatten-maven-plugin.version} resolveCiFriendliesOnly true flatten process-resources flatten flatten.clean clean clean org.apache.maven.plugins maven-compiler-plugin ${maven-compiler-plugin.version} ${java.version} UTF-8 true io.github.git-commit-id git-commit-id-maven-plugin ${git.commit.plugin} get-the-git-infos initialize false true yyyy-MM-dd HH:mm:ss ^git.build.(time|version)$ ^git.commit.(id|message|time).*$ io.spring.javaformat spring-javaformat-maven-plugin ${spring.checkstyle.plugin} validate true cloud dev nacos nacos true boot pig-boot jdk25-annotation-processor [25,) org.apache.maven.plugins maven-compiler-plugin org.projectlombok lombok