From e61e42e56b2dcede08cd09acd86399cb04bb3c4a Mon Sep 17 00:00:00 2001
From: xuekang <914468783@qq.com>
Date: 星期五, 10 五月 2024 20:41:45 +0800
Subject: [PATCH] 初始化

---
 ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java                   |  129 
 ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/UserNameTranslationImpl.java                   |   26 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginBody.java                                            |   43 
 ruoyi-common/ruoyi-common-web/src/main/resources/logback-common.xml                                                                         |   97 
 ruoyi-common/ruoyi-common-seata/src/main/java/org/dromara/common/seata/config/SeataConfiguration.java                                       |   16 
 ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/ApiDecryptProperties.java                             |   34 
 ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/properties/TenantProperties.java                                   |   27 
 ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/annotation/Log.java                                                      |   48 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java                                               |   68 
 ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/I18nConfig.java                                                   |   22 
 ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DeptNameTranslationImpl.java                   |   26 
 ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/exception/TenantException.java                                     |   20 
 ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/enums/LimitType.java                                     |   24 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/xss/XssValidator.java                                                  |   21 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ServletUtils.java                                                |  228 
 ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/properties/MailProperties.java                                  |   69 
 ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/BusinessType.java                                                  |   58 
 ruoyi-common/ruoyi-common-loadbalancer/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports  |    1 
 ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataScopeType.java                                         |   73 
 ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DefaultExcelResult.java                                         |   73 
 ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/config/JacksonConfig.java                                              |   47 
 ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/config/SaTokenConfiguration.java                                 |   44 
 ruoyi-common/ruoyi-common-mail/pom.xml                                                                                                      |   34 
 ruoyi-common/ruoyi-common-loadbalancer/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.cluster.LoadBalance                           |    1 
 ruoyi-common/ruoyi-common-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports         |    1 
 ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/LogEventListener.java                                              |  103 
 ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/domain/BaseEntity.java                                      |   71 
 ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/handler/PlusWebSocketHandler.java                            |  102 
 ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/OperLogEvent.java                                                  |  115 
 ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/annotation/TranslationType.java                          |   23 
 ruoyi-common/ruoyi-common-redis/pom.xml                                                                                                     |   37 
 ruoyi-common/ruoyi-common-elasticsearch/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports |    1 
 ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelBigNumberConvert.java                                   |   52 
 ruoyi-common/ruoyi-common-dubbo/pom.xml                                                                                                     |   63 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/xss/Xss.java                                                           |   26 
 ruoyi-common/ruoyi-common-oss/pom.xml                                                                                                       |   35 
 ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/handler/SensitiveHandler.java                                |   58 
 ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/UndertowConfig.java                                               |   30 
 ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages.properties                                                                  |   61 
 ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/properties/SpringDocProperties.java                               |   94 
 ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/config/TranslationConfig.java                            |   50 
 ruoyi-common/ruoyi-common-tenant/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports        |    1 
 ruoyi-common/ruoyi-common-elasticsearch/src/main/java/org/dromara/common/elasticsearch/config/ActuatorEnvironmentPostProcessor.java         |   25 
 ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/GlobalMailAccount.java                                           |   46 
 ruoyi-common/ruoyi-common-dubbo/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter                                               |    1 
 ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/AbstractEncryptor.java                            |   18 
 ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java                                             |   70 
 ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/EncryptorAutoConfiguration.java                           |   41 
 ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/core/CustomSpringCloudLoadBalancer.java                 |   64 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/EditGroup.java                                                |    9 
 ruoyi-common/ruoyi-common-encrypt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports       |    2 
 ruoyi-common/ruoyi-common-seata/pom.xml                                                                                                     |   53 
 ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/config/SecurityConfiguration.java                              |   47 
 ruoyi-common/ruoyi-common-translation/pom.xml                                                                                               |   42 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/QueryGroup.java                                               |    9 
 ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailAccount.java                                                 |  659 ++
 ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeyRequest.java                                      |   80 
 ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/constant/WebSocketConstants.java                             |   28 
 ruoyi-common/ruoyi-common-mail/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports          |    1 
 ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Sm2Encryptor.java                                 |   61 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/GlobalConstants.java                                          |   40 
 ruoyi-common/ruoyi-common-mybatis/pom.xml                                                                                                   |   82 
 ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/RedisConfiguration.java                                       |  144 
 ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/PageQuery.java                                         |  114 
 ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/properties/PowerJobProperties.java                                |  109 
 ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/PowerJobConfig.java                                               |  107 
 ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/config/CustomLoadBalanceAutoConfiguration.java          |   13 
 ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enumd/PolicyType.java                                                    |   35 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/DictService.java                                               |   63 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ValidatorUtils.java                                              |   28 
 ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/enumd/AlgorithmType.java                                         |   48 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/CaptchaException.java                                   |   21 
 ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/MailConfiguration.java                                          |   37 
 ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelDictConvert.java                                        |   73 
 ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/aspect/LogAspect.java                                                    |  222 
 ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/OssUrlTranslationImpl.java                     |   26 
 ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelListener.java                                              |   14 
 ruoyi-common/ruoyi-common-logstash/src/main/resources/logback-logstash.xml                                                                  |   19 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/UserType.java                                                    |   37 
 ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/handler/OpenApiHandler.java                                              |  252 
 ruoyi-common/ruoyi-common-sensitive/pom.xml                                                                                                 |   25 
 ruoyi-common/ruoyi-common-excel/pom.xml                                                                                                     |   30 
 ruoyi-common/ruoyi-common-satoken/pom.xml                                                                                                   |   57 
 ruoyi-common/ruoyi-common-social/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports        |    1 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ValidatorConfig.java                                            |   40 
 ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/exception/OssException.java                                              |   19 
 ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/service/SysDataScopeService.java                                 |   28 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/R.java                                                          |  120 
 ruoyi-common/ruoyi-common-dict/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports          |    1 
 ruoyi-common/ruoyi-common-elasticsearch/src/main/resources/META-INF/spring.factories                                                        |    2 
 ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/RedisUtils.java                                                |  538 +
 ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/config/RateLimiterConfig.java                            |   20 
 ruoyi-common/ruoyi-common-translation/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports   |    6 
 ruoyi-common/ruoyi-common-dubbo/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports         |    1 
 ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/config/IdempotentAutoConfiguration.java                    |   21 
 ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/helper/TenantHelper.java                                           |  189 
 ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveStrategy.java                                  |   49 
 ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationHandler.java                     |   65 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/MessageUtils.java                                                |   33 
 ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/constant/TransConstant.java                              |   36 
 ruoyi-common/ruoyi-common-encrypt/pom.xml                                                                                                   |   47 
 ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataBaseType.java                                          |   49 
 ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/TableDataInfo.java                                     |   81 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StreamUtils.java                                                 |  254 
 ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisDecryptInterceptor.java                       |  116 
 ruoyi-common/ruoyi-common-prometheus/pom.xml                                                                                                |   28 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/LoginType.java                                                   |   44 
 ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionHelper.java                                 |   93 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/UserException.java                                      |   20 
 ruoyi-common/ruoyi-common-sentinel/src/main/java/org/dromara/common/sentinel/config/properties/SentinelCustomProperties.java                |   17 
 ruoyi-common/ruoyi-common-bom/pom.xml                                                                                                       |  223 
 ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/config/DubboConfiguration.java                                       |   17 
 ruoyi-common/ruoyi-common-skylog/src/main/resources/logback-skylog.xml                                                                      |   29 
 ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java                           |  186 
 ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_zh_CN.properties                                                            |   61 
 ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java                                                 |  436 +
 ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/filter/DubboDataPermissionFilter.java                            |   28 
 ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/annotation/RepeatSubmit.java                               |   29 
 ruoyi-common/ruoyi-common-dict/pom.xml                                                                                                      |   42 
 ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/LogininforEvent.java                                               |   52 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/TenantConstants.java                                          |   45 
 ruoyi-common/ruoyi-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports          |    4 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/Threads.java                                                     |   75 
 ruoyi-common/ruoyi-common-skylog/pom.xml                                                                                                    |   29 
 ruoyi-common/ruoyi-common-job/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports           |    1 
 ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/UserPassAuthenticator.java                                       |   33 
 ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/factory/OssFactory.java                                                  |   65 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java                                                 |  321 
 ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/SocialAutoConfiguration.java                                |   23 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/sql/SqlUtil.java                                                 |   56 
 ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/IEncryptor.java                                             |   35 
 ruoyi-common/ruoyi-common-log/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports           |    2 
 ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DictTypeTranslationImpl.java                   |   28 
 ruoyi-common/ruoyi-common-mybatis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports       |    3 
 ruoyi-common/ruoyi-common-dict/src/main/java/org/dromara/common/dict/service/impl/DictServiceImpl.java                                      |   92 
 ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Sm4Encryptor.java                                 |   55 
 ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/manager/PlusSpringCacheManager.java                                  |  192 
 ruoyi-common/ruoyi-common-sentinel/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports      |    1 
 ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/core/TenantSaTokenDao.java                                         |  148 
 ruoyi-common/ruoyi-common-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports           |    2 
 ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_en_US.properties                                                            |   61 
 ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/CacheUtils.java                                                |   75 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/DeviceType.java                                                  |   32 
 ruoyi-common/ruoyi-common-elasticsearch/pom.xml                                                                                             |   24 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheConstants.java                                           |   25 
 ruoyi-common/ruoyi-common-websocket/pom.xml                                                                                                 |   40 
 ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java                                       |   82 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/reflect/ReflectUtils.java                                        |   56 
 ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DefaultExcelListener.java                                       |  104 
 ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/handler/GlobalExceptionHandler.java                            |  175 
 ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/TranslationInterface.java                           |   20 
 ruoyi-common/ruoyi-common-ratelimiter/pom.xml                                                                                               |   30 
 ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/enumd/EncodeType.java                                            |   26 
 ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/AuthRedisStateCache.java                                     |   61 
 ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataPermission.java                                   |   18 
 ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/Mail.java                                                        |  483 +
 ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/EncryptorProperties.java                              |   48 
 ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/constant/OssConstant.java                                                |   40 
 ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/TenantKeyPrefixHandler.java                                 |   66 
 ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/NicknameTranslationImpl.java                   |   26 
 ruoyi-common/ruoyi-common-web/pom.xml                                                                                                       |   74 
 ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/OperatorType.java                                                  |   23 
 ruoyi-common/ruoyi-common-loadbalancer/pom.xml                                                                                              |   40 
 ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisEncryptInterceptor.java                       |  120 
 ruoyi-common/ruoyi-common-mybatis/src/main/resources/common-mybatis.yml                                                                     |   33 
 ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/annotation/Translation.java                              |   39 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DateUtils.java                                                   |  168 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/ServiceException.java                                        |   70 
 ruoyi-common/ruoyi-common-idempotent/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports    |    1 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileSizeLimitExceededException.java                     |   18 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/MimeTypeUtils.java                                          |   40 
 ruoyi-common/ruoyi-common-json/pom.xml                                                                                                      |   37 
 ruoyi-common/ruoyi-common-social/pom.xml                                                                                                    |   35 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/AsyncConfig.java                                                |  111 
 ruoyi-common/ruoyi-common-alibaba-bom/pom.xml                                                                                               |  176 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/factory/YmlPropertySourceFactory.java                                  |   31 
 ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/InjectionMetaObjectHandler.java                          |   82 
 ruoyi-common/ruoyi-common-doc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports           |    1 
 ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationBeanSerializerModifier.java      |   29 
 ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/SpringDocAutoConfiguration.java                                   |  117 
 ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java                                          |  142 
 ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/enumd/RequestLogEnum.java                                            |   18 
 ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/config/TenantConfiguration.java                                    |  106 
 ruoyi-common/ruoyi-common-json/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports          |    1 
 ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelResult.java                                                |   26 
 ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailException.java                                               |   40 
 ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java                                  |  198 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/Constants.java                                                |   81 
 ruoyi-common/ruoyi-common-seata/src/main/resources/common-seata.yml                                                                         |   19 
 ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/RsaEncryptor.java                                 |   62 
 ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/EncryptResponseBodyWrapper.java                           |  123 
 ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/config/CustomEnvironmentPostProcessor.java              |   25 
 ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/utils/WebSocketUtils.java                                    |  110 
 ruoyi-common/ruoyi-common-tenant/pom.xml                                                                                                    |   36 
 ruoyi-common/ruoyi-common-sms/pom.xml                                                                                                       |   33 
 ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/core/I18nLocaleResolver.java                                             |   31 
 ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/CryptoFilter.java                                         |  115 
 ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialProperties.java                            |   29 
 ruoyi-common/ruoyi-common-doc/pom.xml                                                                                                       |   42 
 ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfiguration.java                             |  116 
 ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/annotation/RateLimiter.java                              |   41 
 ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/config/WebSocketConfig.java                                  |   60 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ReUtil.java                                                      |  148 
 ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/CellMerge.java                                            |   24 
 ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/utils/JsonUtils.java                                                   |  113 
 ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/aspectj/RateLimiterAspect.java                           |  127 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/AddGroup.java                                                 |    9 
 ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/annotation/EncryptField.java                                     |   44 
 ruoyi-common/ruoyi-common-logstash/pom.xml                                                                                                  |   24 
 ruoyi-common/ruoyi-common-core/src/main/resources/ip2region.xdb                                                                             |    0 
 ruoyi-common/ruoyi-common-dubbo/src/main/resources/common-dubbo.yml                                                                         |   30 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/SpringUtils.java                                                 |   60 
 ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelDictFormat.java                                      |   32 
 ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/MailConfig.java                                                 |   37 
 ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/properties/RedissonProperties.java                            |  135 
 ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java                                                      |  262 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/UserConstants.java                                            |  142 
 ruoyi-common/ruoyi-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports      |    2 
 ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/core/BaseController.java                                                 |   32 
 ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/config/CustomLoadBalanceClientConfiguration.java        |   30 
 ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Base64Encryptor.java                              |   48 
 ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptContext.java                                         |   41 
 ruoyi-common/ruoyi-common-prometheus/src/main/java/org/dromara/common/prometheus/config/PrometheusConfiguration.java                        |   22 
 ruoyi-common/ruoyi-common-loadbalancer/src/main/resources/META-INF/spring.factories                                                         |    2 
 ruoyi-common/ruoyi-common-security/pom.xml                                                                                                  |   32 
 ruoyi-common/ruoyi-common-sms/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports           |    1 
 ruoyi-common/ruoyi-common-websocket/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports     |    1 
 ruoyi-common/ruoyi-common-prometheus/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports    |    1 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/HttpStatus.java                                               |   93 
 ruoyi-common/ruoyi-common-log/pom.xml                                                                                                       |   43 
 ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/AesEncryptor.java                                 |   55 
 ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/utils/EncryptUtils.java                                          |  311 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/FileUtils.java                                              |   43 
 ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/handler/BigNumberSerializer.java                                       |   42 
 ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/config/properties/WebSocketProperties.java                   |   26 
 ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/annotation/ApiEncrypt.java                                       |   20 
 ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialLoginConfigProperties.java                 |   68 
 ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelEnumConvert.java                                        |   87 
 ruoyi-common/ruoyi-common-ratelimiter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports   |    1 
 ruoyi-common/ruoyi-common-mybatis/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter                                             |    1 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileException.java                                      |   21 
 ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/holder/WebSocketSessionHolder.java                           |   42 
 ruoyi-common/ruoyi-common-satoken/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports       |    1 
 ruoyi-common/ruoyi-common-sentinel/src/main/java/com/alibaba/cloud/sentinel/custom/SentinelAutoConfiguration.java                           |  265 
 ruoyi-common/ruoyi-common-seata/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports         |    1 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/RegionUtils.java                                              |   67 
 ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/properties/OssProperties.java                                            |   63 
 ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptorManager.java                                       |  100 
 ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/filter/DubboRequestFilter.java                                       |   58 
 ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/listener/WebSocketTopicListener.java                         |   43 
 ruoyi-common/ruoyi-common-dict/src/main/java/org/dromara/common/dict/utils/DictUtils.java                                                   |   51 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileNameLengthLimitExceededException.java               |   18 
 ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/config/SmsAutoConfiguration.java                                         |   14 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/AddressUtils.java                                             |   33 
 ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelEnumFormat.java                                      |   30 
 ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/core/CustomDubboLoadBalancer.java                       |   30 
 ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/manager/TenantSpringCacheManager.java                              |   32 
 ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java                                     |  148 
 ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/service/SaPermissionImpl.java                               |   47 
 ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java                                           |  371 +
 ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/aspectj/RepeatSubmitAspect.java                            |  146 
 ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveService.java                                   |   18 
 ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/PlusTenantLineHandler.java                                  |   56 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/TenantStatus.java                                                |   30 
 ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/handler/KeyPrefixHandler.java                                        |   50 
 ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/dto/WebSocketMessageDto.java                                 |   29 
 ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DropDownOptions.java                                            |  149 
 ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/InternalMailUtil.java                                            |  108 
 ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/ApiDecryptAutoConfiguration.java                          |   32 
 ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/core/TenantEntity.java                                             |   21 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/MapstructUtils.java                                              |   92 
 ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/BusinessStatus.java                                                |   18 
 ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataColumn.java                                       |   28 
 ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/interceptor/PlusWebSocketInterceptor.java                    |   51 
 ruoyi-common/ruoyi-common-sentinel/pom.xml                                                                                                  |   51 
 ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/properties/DubboCustomProperties.java                                |   22 
 ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/DecryptRequestBodyWrapper.java                            |   94 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/base/BaseException.java                                      |   73 
 ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailUtils.java                                                   |  467 +
 ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java                                           |  176 
 ruoyi-common/ruoyi-common-satoken/src/main/resources/common-satoken.yml                                                                     |   13 
 ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/UploadResult.java                                                 |   24 
 ruoyi-common/ruoyi-common-elasticsearch/src/main/java/org/dromara/common/elasticsearch/config/EasyEsConfiguration.java                      |   17 
 ruoyi-common/pom.xml                                                                                                                        |   54 
 ruoyi-common/ruoyi-common-job/pom.xml                                                                                                       |   58 
 ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/annotation/Sensitive.java                                    |   28 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/TreeBuildUtils.java                                              |   35 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/UserStatus.java                                                  |   30 
 ruoyi-common/ruoyi-common-idempotent/pom.xml                                                                                                |   41 
 ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/PlusPaths.java                                                    |   15 
 ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeySource.java                                       |   52 
 ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/MybatisExceptionHandler.java                             |   46 
 ruoyi-common/ruoyi-common-core/pom.xml                                                                                                      |  104 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/CaptchaExpireException.java                             |   18 
 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ApplicationConfig.java                                          |   16 
 ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enumd/AccessPolicyType.java                                              |   55 
 296 files changed, 19,367 insertions(+), 0 deletions(-)

diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml
new file mode 100644
index 0000000..ff3ea10
--- /dev/null
+++ b/ruoyi-common/pom.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-cloud-plus</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <modules>
+        <module>ruoyi-common-bom</module>
+        <module>ruoyi-common-alibaba-bom</module>
+        <module>ruoyi-common-log</module>
+        <module>ruoyi-common-dict</module>
+        <module>ruoyi-common-excel</module>
+        <module>ruoyi-common-core</module>
+        <module>ruoyi-common-redis</module>
+        <module>ruoyi-common-doc</module>
+        <module>ruoyi-common-security</module>
+        <module>ruoyi-common-satoken</module>
+        <module>ruoyi-common-web</module>
+        <module>ruoyi-common-mybatis</module>
+        <module>ruoyi-common-job</module>
+        <module>ruoyi-common-dubbo</module>
+        <module>ruoyi-common-seata</module>
+        <module>ruoyi-common-loadbalancer</module>
+        <module>ruoyi-common-oss</module>
+        <module>ruoyi-common-ratelimiter</module>
+        <module>ruoyi-common-idempotent</module>
+        <module>ruoyi-common-mail</module>
+        <module>ruoyi-common-sms</module>
+        <module>ruoyi-common-logstash</module>
+        <module>ruoyi-common-elasticsearch</module>
+        <module>ruoyi-common-sentinel</module>
+        <module>ruoyi-common-skylog</module>
+        <module>ruoyi-common-prometheus</module>
+        <module>ruoyi-common-translation</module>
+        <module>ruoyi-common-sensitive</module>
+        <module>ruoyi-common-json</module>
+        <module>ruoyi-common-encrypt</module>
+        <module>ruoyi-common-tenant</module>
+        <module>ruoyi-common-websocket</module>
+        <module>ruoyi-common-social</module>
+    </modules>
+
+    <artifactId>ruoyi-common</artifactId>
+    <packaging>pom</packaging>
+
+    <description>
+        ruoyi-common閫氱敤妯″潡
+    </description>
+
+</project>
diff --git a/ruoyi-common/ruoyi-common-alibaba-bom/pom.xml b/ruoyi-common/ruoyi-common-alibaba-bom/pom.xml
new file mode 100644
index 0000000..4a7cb38
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-alibaba-bom/pom.xml
@@ -0,0 +1,176 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.dromara</groupId>
+    <artifactId>ruoyi-common-alibaba-bom</artifactId>
+    <version>${revision}</version>
+    <packaging>pom</packaging>
+
+    <description>
+        ruoyi-common-alibaba-bom alibaba渚濊禆椤�
+    </description>
+
+    <properties>
+        <revision>2.1.2</revision>
+        <spring-cloud-alibaba.version>2022.0.0.0</spring-cloud-alibaba.version>
+        <sentinel.version>1.8.6</sentinel.version>
+        <seata.version>1.7.1</seata.version>
+        <nacos.client.version>2.2.1</nacos.client.version>
+        <dubbo.version>3.2.7</dubbo.version>
+        <spring.context.support.version>1.0.11</spring.context.support.version>
+    </properties>
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>com.alibaba.cloud</groupId>
+                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
+                <version>${spring-cloud-alibaba.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.nacos</groupId>
+                <artifactId>nacos-client</artifactId>
+                <version>${nacos.client.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-core</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-parameter-flow-control</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-datasource-extension</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-datasource-apollo</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-datasource-zookeeper</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-datasource-nacos</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-datasource-redis</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-datasource-consul</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-web-servlet</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-transport-simple-http</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-annotation-aspectj</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-reactor-adapter</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-cluster-server-default</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-cluster-client-default</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-spring-webflux-adapter</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-api-gateway-adapter-common</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-spring-webmvc-adapter</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-dubbo-adapter</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-apache-dubbo-adapter</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-apache-dubbo3-adapter</artifactId>
+                <version>${sentinel.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>io.seata</groupId>
+                <artifactId>seata-spring-boot-starter</artifactId>
+                <version>${seata.version}</version>
+            </dependency>
+
+            <!-- Apache Dubbo 閰嶇疆 -->
+            <dependency>
+                <groupId>org.apache.dubbo</groupId>
+                <artifactId>dubbo-spring-boot-starter</artifactId>
+                <version>${dubbo.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.apache.dubbo</groupId>
+                <artifactId>dubbo-spring-boot-actuator</artifactId>
+                <version>${dubbo.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.apache.dubbo</groupId>
+                <artifactId>dubbo</artifactId>
+                <version>${dubbo.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.alibaba.spring</groupId>
+                <artifactId>spring-context-support</artifactId>
+                <version>${spring.context.support.version}</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
diff --git a/ruoyi-common/ruoyi-common-bom/pom.xml b/ruoyi-common/ruoyi-common-bom/pom.xml
new file mode 100644
index 0000000..043824e
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-bom/pom.xml
@@ -0,0 +1,223 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.dromara</groupId>
+    <artifactId>ruoyi-common-bom</artifactId>
+    <version>${revision}</version>
+    <packaging>pom</packaging>
+
+    <description>
+        ruoyi-common-bom common渚濊禆椤�
+    </description>
+
+    <properties>
+        <revision>2.1.2</revision>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <!-- 鏍稿績妯″潡 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-core</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 鎺ュ彛妯″潡 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-doc</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 瀹夊叏妯″潡 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-security</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-satoken</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 鏃ュ織璁板綍 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-log</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 瀛楀吀 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-dict</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- excel -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-excel</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 缂撳瓨鏈嶅姟 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-redis</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- web鏈嶅姟 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-web</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 鏁版嵁搴撴湇鍔� -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-mybatis</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-job</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-dubbo</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-seata</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-loadbalancer</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-oss</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 闄愭祦 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-ratelimiter</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-idempotent</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-mail</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-sms</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-logstash</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-elasticsearch</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-sentinel</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-skylog</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-prometheus</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-translation</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 鑴辨晱妯″潡 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-sensitive</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 搴忓垪鍖栨ā鍧� -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-json</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-encrypt</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <!-- 绉熸埛妯″潡 -->
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-tenant</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-websocket</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.dromara</groupId>
+                <artifactId>ruoyi-common-social</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+        </dependencies>
+    </dependencyManagement>
+</project>
diff --git a/ruoyi-common/ruoyi-common-core/pom.xml b/ruoyi-common/ruoyi-common-core/pom.xml
new file mode 100644
index 0000000..01d876e
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/pom.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-core</artifactId>
+
+    <description>
+        ruoyi-common-core 鏍稿績妯″潡
+    </description>
+
+    <dependencies>
+        <!-- Spring妗嗘灦鍩烘湰鐨勬牳蹇冨伐鍏� -->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context-support</artifactId>
+        </dependency>
+
+        <!-- SpringWeb妯″潡 -->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+        </dependency>
+
+        <!-- Hibernate Validator -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+
+        <!--甯哥敤宸ュ叿绫� -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+
+        <!-- servlet鍖� -->
+        <dependency>
+            <groupId>jakarta.servlet</groupId>
+            <artifactId>jakarta.servlet-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.swagger.core.v3</groupId>
+            <artifactId>swagger-annotations</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-http</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-extra</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <!--  鑷姩鐢熸垚YML閰嶇疆鍏宠仈JSON鏂囦欢  -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-properties-migrator</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>io.github.linpeilie</groupId>
+            <artifactId>mapstruct-plus-spring-boot-starter</artifactId>
+        </dependency>
+
+        <!-- 绂荤嚎IP鍦板潃瀹氫綅搴� -->
+        <dependency>
+            <groupId>org.lionsoul</groupId>
+            <artifactId>ip2region</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ApplicationConfig.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ApplicationConfig.java
new file mode 100644
index 0000000..07500ba
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ApplicationConfig.java
@@ -0,0 +1,16 @@
+package org.dromara.common.core.config;
+
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+
+/**
+ * 绋嬪簭娉ㄨВ閰嶇疆
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration
+// 琛ㄧず閫氳繃aop妗嗘灦鏆撮湶璇ヤ唬鐞嗗璞�,AopContext鑳藉璁块棶
+@EnableAspectJAutoProxy(exposeProxy = true)
+public class ApplicationConfig {
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/AsyncConfig.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/AsyncConfig.java
new file mode 100644
index 0000000..5a8714e
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/AsyncConfig.java
@@ -0,0 +1,111 @@
+package org.dromara.common.core.config;
+
+import cn.hutool.core.util.ArrayUtil;
+import jakarta.annotation.PreDestroy;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.Threads;
+import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.scheduling.annotation.AsyncConfigurer;
+import org.springframework.scheduling.annotation.EnableAsync;
+
+import java.util.Arrays;
+import java.util.concurrent.*;
+
+/**
+ * 寮傛閰嶇疆
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@EnableAsync(proxyTargetClass = true)
+@AutoConfiguration
+public class AsyncConfig implements AsyncConfigurer {
+
+    private final int corePoolSize = Runtime.getRuntime().availableProcessors() + 1;
+    private ScheduledExecutorService scheduledExecutorService;
+
+    /**
+     * 鎵ц鍛ㄦ湡鎬ф垨瀹氭椂浠诲姟
+     */
+    @Bean(name = "scheduledExecutorService")
+    public ScheduledExecutorService scheduledExecutorService() {
+        ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(corePoolSize,
+            new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(),
+            new ThreadPoolExecutor.CallerRunsPolicy()) {
+            @Override
+            protected void afterExecute(Runnable r, Throwable t) {
+                super.afterExecute(r, t);
+                printException(r, t);
+            }
+        };
+        this.scheduledExecutorService = scheduledThreadPoolExecutor;
+        return scheduledThreadPoolExecutor;
+    }
+
+    /**
+     * 閿�姣佷簨浠�
+     */
+    @PreDestroy
+    public void destroy() {
+        try {
+            log.info("====鍏抽棴鍚庡彴浠诲姟浠诲姟绾跨▼姹�====");
+            Threads.shutdownAndAwaitTermination(scheduledExecutorService);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 鑷畾涔� @Async 娉ㄨВ浣跨敤绯荤粺绾跨▼姹�
+     */
+    @Override
+    public Executor getAsyncExecutor() {
+        return SpringUtils.getBean("scheduledExecutorService");
+    }
+
+    /**
+     * 寮傛鎵ц寮傚父澶勭悊
+     */
+    @Override
+    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
+        return (throwable, method, objects) -> {
+            throwable.printStackTrace();
+            StringBuilder sb = new StringBuilder();
+            sb.append("Exception message - ").append(throwable.getMessage())
+                .append(", Method name - ").append(method.getName());
+            if (ArrayUtil.isNotEmpty(objects)) {
+                sb.append(", Parameter value - ").append(Arrays.toString(objects));
+            }
+            throw new ServiceException(sb.toString());
+        };
+    }
+
+    /**
+     * 鎵撳嵃绾跨▼寮傚父淇℃伅
+     */
+    public void printException(Runnable r, Throwable t) {
+        if (t == null && r instanceof Future<?>) {
+            try {
+                Future<?> future = (Future<?>) r;
+                if (future.isDone()) {
+                    future.get();
+                }
+            } catch (CancellationException ce) {
+                t = ce;
+            } catch (ExecutionException ee) {
+                t = ee.getCause();
+            } catch (InterruptedException ie) {
+                Thread.currentThread().interrupt();
+            }
+        }
+        if (t != null) {
+            log.error(t.getMessage(), t);
+        }
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ValidatorConfig.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ValidatorConfig.java
new file mode 100644
index 0000000..45c5bd1
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/config/ValidatorConfig.java
@@ -0,0 +1,40 @@
+package org.dromara.common.core.config;
+
+import jakarta.validation.Validator;
+import org.hibernate.validator.HibernateValidator;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.MessageSource;
+import org.springframework.context.annotation.Bean;
+import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
+
+import java.util.Properties;
+
+/**
+ * 鏍¢獙妗嗘灦閰嶇疆绫�
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration
+public class ValidatorConfig {
+
+    /**
+     * 閰嶇疆鏍¢獙妗嗘灦 蹇�熻繑鍥炴ā寮�
+     */
+    @Bean
+    public Validator validator(MessageSource messageSource) {
+        try (LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean()) {
+            // 鍥介檯鍖�
+            factoryBean.setValidationMessageSource(messageSource);
+            // 璁剧疆浣跨敤 HibernateValidator 鏍¢獙鍣�
+            factoryBean.setProviderClass(HibernateValidator.class);
+            Properties properties = new Properties();
+            // 璁剧疆 蹇�熷紓甯歌繑鍥�
+            properties.setProperty("hibernate.validator.fail_fast", "true");
+            factoryBean.setValidationProperties(properties);
+            // 鍔犺浇閰嶇疆
+            factoryBean.afterPropertiesSet();
+            return factoryBean.getValidator();
+        }
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheConstants.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheConstants.java
new file mode 100644
index 0000000..67bc8e4
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheConstants.java
@@ -0,0 +1,25 @@
+package org.dromara.common.core.constant;
+
+/**
+ * 缂撳瓨鐨刱ey 甯搁噺
+ *
+ * @author Lion Li
+ */
+public interface CacheConstants {
+
+    /**
+     * 鍦ㄧ嚎鐢ㄦ埛 redis key
+     */
+    String ONLINE_TOKEN_KEY = "online_tokens:";
+
+    /**
+     * 鍙傛暟绠$悊 cache key
+     */
+    String SYS_CONFIG_KEY = "sys_config:";
+
+    /**
+     * 瀛楀吀绠$悊 cache key
+     */
+    String SYS_DICT_KEY = "sys_dict:";
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java
new file mode 100644
index 0000000..e59277a
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java
@@ -0,0 +1,68 @@
+package org.dromara.common.core.constant;
+
+/**
+ * 缂撳瓨缁勫悕绉板父閲�
+ * <p>
+ * key 鏍煎紡涓� cacheNames#ttl#maxIdleTime#maxSize
+ * <p>
+ * ttl 杩囨湡鏃堕棿 濡傛灉璁剧疆涓�0鍒欎笉杩囨湡 榛樿涓�0
+ * maxIdleTime 鏈�澶х┖闂叉椂闂� 鏍规嵁LRU绠楁硶娓呯悊绌洪棽鏁版嵁 濡傛灉璁剧疆涓�0鍒欎笉妫�娴� 榛樿涓�0
+ * maxSize 缁勬渶澶ч暱搴� 鏍规嵁LRU绠楁硶娓呯悊婧㈠嚭鏁版嵁 濡傛灉璁剧疆涓�0鍒欐棤闄愰暱 榛樿涓�0
+ * <p>
+ * 渚嬪瓙: test#60s銆乼est#0#60s銆乼est#0#1m#1000銆乼est#1h#0#500
+ *
+ * @author Lion Li
+ */
+public interface CacheNames {
+
+    /**
+     * 婕旂ず妗堜緥
+     */
+    String DEMO_CACHE = "demo:cache#60s#10m#20";
+
+    /**
+     * 绯荤粺閰嶇疆
+     */
+    String SYS_CONFIG = "sys_config";
+
+    /**
+     * 鏁版嵁瀛楀吀
+     */
+    String SYS_DICT = "sys_dict";
+
+    /**
+     * 绉熸埛
+     */
+    String SYS_TENANT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_tenant#30d";
+
+    /**
+     * 鐢ㄦ埛璐︽埛
+     */
+    String SYS_USER_NAME = "sys_user_name#30d";
+
+    /**
+     * 鐢ㄦ埛鍚嶇О
+     */
+    String SYS_NICKNAME = "sys_nickname#30d";
+
+    /**
+     * 閮ㄩ棬
+     */
+    String SYS_DEPT = "sys_dept#30d";
+
+    /**
+     * OSS鍐呭
+     */
+    String SYS_OSS = "sys_oss#30d";
+
+    /**
+     * OSS閰嶇疆
+     */
+    String SYS_OSS_CONFIG = GlobalConstants.GLOBAL_REDIS_KEY + "sys_oss_config";
+
+    /**
+     * 鍦ㄧ嚎鐢ㄦ埛
+     */
+    String ONLINE_TOKEN = "online_tokens";
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/Constants.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/Constants.java
new file mode 100644
index 0000000..cdbda89
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/Constants.java
@@ -0,0 +1,81 @@
+package org.dromara.common.core.constant;
+
+/**
+ * 閫氱敤甯搁噺淇℃伅
+ *
+ * @author ruoyi
+ */
+public interface Constants {
+
+    /**
+     * UTF-8 瀛楃闆�
+     */
+    String UTF8 = "UTF-8";
+
+    /**
+     * GBK 瀛楃闆�
+     */
+    String GBK = "GBK";
+
+    /**
+     * www涓诲煙
+     */
+    String WWW = "www.";
+
+    /**
+     * http璇锋眰
+     */
+    String HTTP = "http://";
+
+    /**
+     * https璇锋眰
+     */
+    String HTTPS = "https://";
+
+    /**
+     * 閫氱敤鎴愬姛鏍囪瘑
+     */
+    String SUCCESS = "0";
+
+    /**
+     * 閫氱敤澶辫触鏍囪瘑
+     */
+    String FAIL = "1";
+
+    /**
+     * 鐧诲綍鎴愬姛
+     */
+    String LOGIN_SUCCESS = "Success";
+
+    /**
+     * 娉ㄩ攢
+     */
+    String LOGOUT = "Logout";
+
+    /**
+     * 娉ㄥ唽
+     */
+    String REGISTER = "Register";
+
+    /**
+     * 鐧诲綍澶辫触
+     */
+    String LOGIN_FAIL = "Error";
+
+    /**
+     * 楠岃瘉鐮佹湁鏁堟湡锛堝垎閽燂級
+     */
+    Integer CAPTCHA_EXPIRATION = 2;
+
+    /**
+     * 浠ょ墝
+     */
+    String TOKEN = "token";
+
+    /**
+     * 椤剁骇閮ㄩ棬id
+     */
+    Long TOP_PARENT_ID = 0L;
+
+}
+
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/GlobalConstants.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/GlobalConstants.java
new file mode 100644
index 0000000..8e5a6eb
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/GlobalConstants.java
@@ -0,0 +1,40 @@
+package org.dromara.common.core.constant;
+
+/**
+ * 鍏ㄥ眬鐨刱ey甯搁噺 (涓氬姟鏃犲叧鐨刱ey)
+ *
+ * @author Lion Li
+ */
+public interface GlobalConstants {
+
+    /**
+     * 鍏ㄥ眬 redis key (涓氬姟鏃犲叧鐨刱ey)
+     */
+    String GLOBAL_REDIS_KEY = "global:";
+
+    /**
+     * 楠岃瘉鐮� redis key
+     */
+    String CAPTCHA_CODE_KEY = GLOBAL_REDIS_KEY + "captcha_codes:";
+
+    /**
+     * 闃查噸鎻愪氦 redis key
+     */
+    String REPEAT_SUBMIT_KEY = GLOBAL_REDIS_KEY + "repeat_submit:";
+
+    /**
+     * 闄愭祦 redis key
+     */
+    String RATE_LIMIT_KEY = GLOBAL_REDIS_KEY + "rate_limit:";
+
+    /**
+     * 鐧诲綍璐︽埛瀵嗙爜閿欒娆℃暟 redis key
+     */
+    String PWD_ERR_CNT_KEY = GLOBAL_REDIS_KEY + "pwd_err_cnt:";
+
+    /**
+     * 涓夋柟璁よ瘉 redis key
+     */
+    String SOCIAL_AUTH_CODE_KEY = GLOBAL_REDIS_KEY + "social_auth_codes:";
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/HttpStatus.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/HttpStatus.java
new file mode 100644
index 0000000..85566e8
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/HttpStatus.java
@@ -0,0 +1,93 @@
+package org.dromara.common.core.constant;
+
+/**
+ * 杩斿洖鐘舵�佺爜
+ *
+ * @author Lion Li
+ */
+public interface HttpStatus {
+    /**
+     * 鎿嶄綔鎴愬姛
+     */
+    int SUCCESS = 200;
+
+    /**
+     * 瀵硅薄鍒涘缓鎴愬姛
+     */
+    int CREATED = 201;
+
+    /**
+     * 璇锋眰宸茬粡琚帴鍙�
+     */
+    int ACCEPTED = 202;
+
+    /**
+     * 鎿嶄綔宸茬粡鎵ц鎴愬姛锛屼絾鏄病鏈夎繑鍥炴暟鎹�
+     */
+    int NO_CONTENT = 204;
+
+    /**
+     * 璧勬簮宸茶绉婚櫎
+     */
+    int MOVED_PERM = 301;
+
+    /**
+     * 閲嶅畾鍚�
+     */
+    int SEE_OTHER = 303;
+
+    /**
+     * 璧勬簮娌℃湁琚慨鏀�
+     */
+    int NOT_MODIFIED = 304;
+
+    /**
+     * 鍙傛暟鍒楄〃閿欒锛堢己灏戯紝鏍煎紡涓嶅尮閰嶏級
+     */
+    int BAD_REQUEST = 400;
+
+    /**
+     * 鏈巿鏉�
+     */
+    int UNAUTHORIZED = 401;
+
+    /**
+     * 璁块棶鍙楅檺锛屾巿鏉冭繃鏈�
+     */
+    int FORBIDDEN = 403;
+
+    /**
+     * 璧勬簮锛屾湇鍔℃湭鎵惧埌
+     */
+    int NOT_FOUND = 404;
+
+    /**
+     * 涓嶅厑璁哥殑http鏂规硶
+     */
+    int BAD_METHOD = 405;
+
+    /**
+     * 璧勬簮鍐茬獊锛屾垨鑰呰祫婧愯閿�
+     */
+    int CONFLICT = 409;
+
+    /**
+     * 涓嶆敮鎸佺殑鏁版嵁锛屽獟浣撶被鍨�
+     */
+    int UNSUPPORTED_TYPE = 415;
+
+    /**
+     * 绯荤粺鍐呴儴閿欒
+     */
+    int ERROR = 500;
+
+    /**
+     * 鎺ュ彛鏈疄鐜�
+     */
+    int NOT_IMPLEMENTED = 501;
+
+    /**
+     * 绯荤粺璀﹀憡娑堟伅
+     */
+    int WARN = 601;
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/TenantConstants.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/TenantConstants.java
new file mode 100644
index 0000000..86b63c9
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/TenantConstants.java
@@ -0,0 +1,45 @@
+package org.dromara.common.core.constant;
+
+/**
+ * 绉熸埛甯搁噺淇℃伅
+ *
+ * @author Lion Li
+ */
+public interface TenantConstants {
+
+    /**
+     * 绉熸埛姝e父鐘舵��
+     */
+    String NORMAL = "0";
+
+    /**
+     * 绉熸埛灏佺鐘舵��
+     */
+    String DISABLE = "1";
+
+    /**
+     * 瓒呯骇绠$悊鍛業D
+     */
+    Long SUPER_ADMIN_ID = 1L;
+
+    /**
+     * 瓒呯骇绠$悊鍛樿鑹� roleKey
+     */
+    String SUPER_ADMIN_ROLE_KEY = "superadmin";
+
+    /**
+     * 绉熸埛绠$悊鍛樿鑹� roleKey
+     */
+    String TENANT_ADMIN_ROLE_KEY = "admin";
+
+    /**
+     * 绉熸埛绠$悊鍛樿鑹插悕绉�
+     */
+    String TENANT_ADMIN_ROLE_NAME = "绠$悊鍛�";
+
+    /**
+     * 榛樿绉熸埛ID
+     */
+    String DEFAULT_TENANT_ID = "000000";
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/UserConstants.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/UserConstants.java
new file mode 100644
index 0000000..6f3b0b9
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/UserConstants.java
@@ -0,0 +1,142 @@
+package org.dromara.common.core.constant;
+
+/**
+ * 鐢ㄦ埛甯搁噺淇℃伅
+ *
+ * @author ruoyi
+ */
+public interface UserConstants {
+
+    /**
+     * 骞冲彴鍐呯郴缁熺敤鎴风殑鍞竴鏍囧織
+     */
+    String SYS_USER = "SYS_USER";
+
+    /**
+     * 姝e父鐘舵��
+     */
+    String NORMAL = "0";
+
+    /**
+     * 寮傚父鐘舵��
+     */
+    String EXCEPTION = "1";
+
+    /**
+     * 鐢ㄦ埛姝e父鐘舵��
+     */
+    String USER_NORMAL = "0";
+
+    /**
+     * 鐢ㄦ埛灏佺鐘舵��
+     */
+    String USER_DISABLE = "1";
+
+    /**
+     * 瑙掕壊姝e父鐘舵��
+     */
+    String ROLE_NORMAL = "0";
+
+    /**
+     * 瑙掕壊灏佺鐘舵��
+     */
+    String ROLE_DISABLE = "1";
+
+    /**
+     * 閮ㄩ棬姝e父鐘舵��
+     */
+    String DEPT_NORMAL = "0";
+
+    /**
+     * 閮ㄩ棬鍋滅敤鐘舵��
+     */
+    String DEPT_DISABLE = "1";
+
+    /**
+     * 宀椾綅姝e父鐘舵��
+     */
+    String POST_NORMAL = "0";
+
+    /**
+     * 宀椾綅鍋滅敤鐘舵��
+     */
+    String POST_DISABLE = "1";
+
+    /**
+     * 瀛楀吀姝e父鐘舵��
+     */
+    String DICT_NORMAL = "0";
+
+    /**
+     * 鏄惁涓虹郴缁熼粯璁わ紙鏄級
+     */
+    String YES = "Y";
+
+    /**
+     * 鏄惁鑿滃崟澶栭摼锛堟槸锛�
+     */
+    String YES_FRAME = "0";
+
+    /**
+     * 鏄惁鑿滃崟澶栭摼锛堝惁锛�
+     */
+    String NO_FRAME = "1";
+
+    /**
+     * 鑿滃崟姝e父鐘舵��
+     */
+    String MENU_NORMAL = "0";
+
+    /**
+     * 鑿滃崟鍋滅敤鐘舵��
+     */
+    String MENU_DISABLE = "1";
+
+    /**
+     * 鑿滃崟绫诲瀷锛堢洰褰曪級
+     */
+    String TYPE_DIR = "M";
+
+    /**
+     * 鑿滃崟绫诲瀷锛堣彍鍗曪級
+     */
+    String TYPE_MENU = "C";
+
+    /**
+     * 鑿滃崟绫诲瀷锛堟寜閽級
+     */
+    String TYPE_BUTTON = "F";
+
+    /**
+     * Layout缁勪欢鏍囪瘑
+     */
+    String LAYOUT = "Layout";
+
+    /**
+     * ParentView缁勪欢鏍囪瘑
+     */
+    String PARENT_VIEW = "ParentView";
+
+    /**
+     * InnerLink缁勪欢鏍囪瘑
+     */
+    String INNER_LINK = "InnerLink";
+
+    /**
+     * 鐢ㄦ埛鍚嶉暱搴﹂檺鍒�
+     */
+    int USERNAME_MIN_LENGTH = 2;
+    int USERNAME_MAX_LENGTH = 20;
+
+    /**
+     * 瀵嗙爜闀垮害闄愬埗
+     */
+    int PASSWORD_MIN_LENGTH = 5;
+    int PASSWORD_MAX_LENGTH = 20;
+
+    /**
+     * 瓒呯骇绠$悊鍛業D
+     */
+    Long SUPER_ADMIN_ID = 1L;
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/R.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/R.java
new file mode 100644
index 0000000..625f58b
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/R.java
@@ -0,0 +1,120 @@
+package org.dromara.common.core.domain;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.constant.HttpStatus;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 鍝嶅簲淇℃伅涓讳綋
+ *
+ * @author Lion Li
+ */
+@Data
+@NoArgsConstructor
+public class R<T> implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 鎴愬姛
+     */
+    public static final int SUCCESS = 200;
+
+    /**
+     * 澶辫触
+     */
+    public static final int FAIL = 500;
+
+    /**
+     * 娑堟伅鐘舵�佺爜
+     */
+    private int code;
+
+    /**
+     * 娑堟伅鍐呭
+     */
+    private String msg;
+
+    /**
+     * 鏁版嵁瀵硅薄
+     */
+    private T data;
+
+    public static <T> R<T> ok() {
+        return restResult(null, SUCCESS, "鎿嶄綔鎴愬姛");
+    }
+
+    public static <T> R<T> ok(T data) {
+        return restResult(data, SUCCESS, "鎿嶄綔鎴愬姛");
+    }
+
+    public static <T> R<T> ok(String msg) {
+        return restResult(null, SUCCESS, msg);
+    }
+
+    public static <T> R<T> ok(String msg, T data) {
+        return restResult(data, SUCCESS, msg);
+    }
+
+    public static <T> R<T> fail() {
+        return restResult(null, FAIL, "鎿嶄綔澶辫触");
+    }
+
+    public static <T> R<T> fail(String msg) {
+        return restResult(null, FAIL, msg);
+    }
+
+    public static <T> R<T> fail(T data) {
+        return restResult(data, FAIL, "鎿嶄綔澶辫触");
+    }
+
+    public static <T> R<T> fail(String msg, T data) {
+        return restResult(data, FAIL, msg);
+    }
+
+    public static <T> R<T> fail(int code, String msg) {
+        return restResult(null, code, msg);
+    }
+
+    /**
+     * 杩斿洖璀﹀憡娑堟伅
+     *
+     * @param msg 杩斿洖鍐呭
+     * @return 璀﹀憡娑堟伅
+     */
+    public static <T> R<T> warn(String msg) {
+        return restResult(null, HttpStatus.WARN, msg);
+    }
+
+    /**
+     * 杩斿洖璀﹀憡娑堟伅
+     *
+     * @param msg 杩斿洖鍐呭
+     * @param data 鏁版嵁瀵硅薄
+     * @return 璀﹀憡娑堟伅
+     */
+    public static <T> R<T> warn(String msg, T data) {
+        return restResult(data, HttpStatus.WARN, msg);
+    }
+
+    private static <T> R<T> restResult(T data, int code, String msg) {
+        R<T> r = new R<>();
+        r.setCode(code);
+        r.setData(data);
+        r.setMsg(msg);
+        return r;
+    }
+
+    public static <T> Boolean isError(R<T> ret) {
+        return !isSuccess(ret);
+    }
+
+    public static <T> Boolean isSuccess(R<T> ret) {
+        return R.SUCCESS == ret.getCode();
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginBody.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginBody.java
new file mode 100644
index 0000000..ee612fd
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginBody.java
@@ -0,0 +1,43 @@
+package org.dromara.common.core.domain.model;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 鐢ㄦ埛鐧诲綍瀵硅薄
+ *
+ * @author Lion Li
+ */
+@Data
+@NoArgsConstructor
+public class LoginBody {
+
+    /**
+     * 瀹㈡埛绔痠d
+     */
+    @NotBlank(message = "{auth.clientid.not.blank}")
+    private String clientId;
+
+    /**
+     * 鎺堟潈绫诲瀷
+     */
+    @NotBlank(message = "{auth.grant.type.not.blank}")
+    private String grantType;
+
+    /**
+     * 绉熸埛ID
+     */
+    private String tenantId;
+
+    /**
+     * 楠岃瘉鐮�
+     */
+    private String code;
+
+    /**
+     * 鍞竴鏍囪瘑
+     */
+    private String uuid;
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/DeviceType.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/DeviceType.java
new file mode 100644
index 0000000..09bf44b
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/DeviceType.java
@@ -0,0 +1,32 @@
+package org.dromara.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 璁惧绫诲瀷
+ * 閽堝涓�濂� 鐢ㄦ埛浣撶郴
+ *
+ * @author Lion Li
+ */
+@Getter
+@AllArgsConstructor
+public enum DeviceType {
+
+    /**
+     * pc绔�
+     */
+    PC("pc"),
+
+    /**
+     * app绔�
+     */
+    APP("app"),
+
+    /**
+     * 灏忕▼搴忕
+     */
+    XCX("xcx");
+
+    private final String device;
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/LoginType.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/LoginType.java
new file mode 100644
index 0000000..f9cac66
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/LoginType.java
@@ -0,0 +1,44 @@
+package org.dromara.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 鐧诲綍绫诲瀷
+ *
+ * @author Lion Li
+ */
+@Getter
+@AllArgsConstructor
+public enum LoginType {
+
+    /**
+     * 瀵嗙爜鐧诲綍
+     */
+    PASSWORD("user.password.retry.limit.exceed", "user.password.retry.limit.count"),
+
+    /**
+     * 鐭俊鐧诲綍
+     */
+    SMS("sms.code.retry.limit.exceed", "sms.code.retry.limit.count"),
+
+    /**
+     * 閭鐧诲綍
+     */
+    EMAIL("email.code.retry.limit.exceed", "email.code.retry.limit.count"),
+
+    /**
+     * 灏忕▼搴忕櫥褰�
+     */
+    XCX("", "");
+
+    /**
+     * 鐧诲綍閲嶈瘯瓒呭嚭闄愬埗鎻愮ず
+     */
+    final String retryLimitExceed;
+
+    /**
+     * 鐧诲綍閲嶈瘯闄愬埗璁℃暟鎻愮ず
+     */
+    final String retryLimitCount;
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/TenantStatus.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/TenantStatus.java
new file mode 100644
index 0000000..400a399
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/TenantStatus.java
@@ -0,0 +1,30 @@
+package org.dromara.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 鐢ㄦ埛鐘舵��
+ *
+ * @author LionLi
+ */
+@Getter
+@AllArgsConstructor
+public enum TenantStatus {
+    /**
+     * 姝e父
+     */
+    OK("0", "姝e父"),
+    /**
+     * 鍋滅敤
+     */
+    DISABLE("1", "鍋滅敤"),
+    /**
+     * 鍒犻櫎
+     */
+    DELETED("2", "鍒犻櫎");
+
+    private final String code;
+    private final String info;
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/UserStatus.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/UserStatus.java
new file mode 100644
index 0000000..be7e44d
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/UserStatus.java
@@ -0,0 +1,30 @@
+package org.dromara.common.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 鐢ㄦ埛鐘舵��
+ *
+ * @author ruoyi
+ */
+@Getter
+@AllArgsConstructor
+public enum UserStatus {
+    /**
+     * 姝e父
+     */
+    OK("0", "姝e父"),
+    /**
+     * 鍋滅敤
+     */
+    DISABLE("1", "鍋滅敤"),
+    /**
+     * 鍒犻櫎
+     */
+    DELETED("2", "鍒犻櫎");
+
+    private final String code;
+    private final String info;
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/UserType.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/UserType.java
new file mode 100644
index 0000000..69e4753
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/enums/UserType.java
@@ -0,0 +1,37 @@
+package org.dromara.common.core.enums;
+
+import org.dromara.common.core.utils.StringUtils;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 璁惧绫诲瀷
+ * 閽堝澶氬 鐢ㄦ埛浣撶郴
+ *
+ * @author Lion Li
+ */
+@Getter
+@AllArgsConstructor
+public enum UserType {
+
+    /**
+     * pc绔�
+     */
+    SYS_USER("sys_user"),
+
+    /**
+     * app绔�
+     */
+    APP_USER("app_user");
+
+    private final String userType;
+
+    public static UserType getUserType(String str) {
+        for (UserType value : values()) {
+            if (StringUtils.contains(str, value.getUserType())) {
+                return value;
+            }
+        }
+        throw new RuntimeException("'UserType' not found By " + str);
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/ServiceException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/ServiceException.java
new file mode 100644
index 0000000..4fb097a
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/ServiceException.java
@@ -0,0 +1,70 @@
+package org.dromara.common.core.exception;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+
+/**
+ * 涓氬姟寮傚父
+ *
+ * @author ruoyi
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@AllArgsConstructor
+public final class ServiceException extends RuntimeException {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 閿欒鐮�
+     */
+    private Integer code;
+
+    /**
+     * 閿欒鎻愮ず
+     */
+    private String message;
+
+    /**
+     * 閿欒鏄庣粏锛屽唴閮ㄨ皟璇曢敊璇�
+     */
+    private String detailMessage;
+
+    public ServiceException(String message) {
+        this.message = message;
+    }
+
+    public ServiceException(String message, Integer code) {
+        this.message = message;
+        this.code = code;
+    }
+
+    public String getDetailMessage() {
+        return detailMessage;
+    }
+
+    @Override
+    public String getMessage() {
+        return message;
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+
+    public ServiceException setMessage(String message) {
+        this.message = message;
+        return this;
+    }
+
+    public ServiceException setDetailMessage(String detailMessage) {
+        this.detailMessage = detailMessage;
+        return this;
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/base/BaseException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/base/BaseException.java
new file mode 100644
index 0000000..8df8acf
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/base/BaseException.java
@@ -0,0 +1,73 @@
+package org.dromara.common.core.exception.base;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.utils.MessageUtils;
+import org.dromara.common.core.utils.StringUtils;
+
+import java.io.Serial;
+
+/**
+ * 鍩虹寮傚父
+ *
+ * @author ruoyi
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@NoArgsConstructor
+@AllArgsConstructor
+public class BaseException extends RuntimeException {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 鎵�灞炴ā鍧�
+     */
+    private String module;
+
+    /**
+     * 閿欒鐮�
+     */
+    private String code;
+
+    /**
+     * 閿欒鐮佸搴旂殑鍙傛暟
+     */
+    private Object[] args;
+
+    /**
+     * 閿欒娑堟伅
+     */
+    private String defaultMessage;
+
+    public BaseException(String module, String code, Object[] args) {
+        this(module, code, args, null);
+    }
+
+    public BaseException(String module, String defaultMessage) {
+        this(module, null, null, defaultMessage);
+    }
+
+    public BaseException(String code, Object[] args) {
+        this(null, code, args, null);
+    }
+
+    public BaseException(String defaultMessage) {
+        this(null, null, null, defaultMessage);
+    }
+
+    @Override
+    public String getMessage() {
+        String message = null;
+        if (!StringUtils.isEmpty(code)) {
+            message = MessageUtils.message(code, args);
+        }
+        if (message == null) {
+            message = defaultMessage;
+        }
+        return message;
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileException.java
new file mode 100644
index 0000000..d374fc0
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileException.java
@@ -0,0 +1,21 @@
+package org.dromara.common.core.exception.file;
+
+import org.dromara.common.core.exception.base.BaseException;
+
+import java.io.Serial;
+
+/**
+ * 鏂囦欢淇℃伅寮傚父绫�
+ *
+ * @author ruoyi
+ */
+public class FileException extends BaseException {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    public FileException(String code, Object[] args) {
+        super("file", code, args, null);
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileNameLengthLimitExceededException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileNameLengthLimitExceededException.java
new file mode 100644
index 0000000..af98124
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileNameLengthLimitExceededException.java
@@ -0,0 +1,18 @@
+package org.dromara.common.core.exception.file;
+
+import java.io.Serial;
+
+/**
+ * 鏂囦欢鍚嶇О瓒呴暱闄愬埗寮傚父绫�
+ *
+ * @author ruoyi
+ */
+public class FileNameLengthLimitExceededException extends FileException {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    public FileNameLengthLimitExceededException(int defaultFileNameLength) {
+        super("upload.filename.exceed.length", new Object[]{defaultFileNameLength});
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileSizeLimitExceededException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileSizeLimitExceededException.java
new file mode 100644
index 0000000..1eb8d40
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/file/FileSizeLimitExceededException.java
@@ -0,0 +1,18 @@
+package org.dromara.common.core.exception.file;
+
+import java.io.Serial;
+
+/**
+ * 鏂囦欢鍚嶅ぇ灏忛檺鍒跺紓甯哥被
+ *
+ * @author ruoyi
+ */
+public class FileSizeLimitExceededException extends FileException {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    public FileSizeLimitExceededException(long defaultMaxSize) {
+        super("upload.exceed.maxSize", new Object[]{defaultMaxSize});
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/CaptchaException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/CaptchaException.java
new file mode 100644
index 0000000..a18e581
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/CaptchaException.java
@@ -0,0 +1,21 @@
+package org.dromara.common.core.exception.user;
+
+import java.io.Serial;
+
+/**
+ * 楠岃瘉鐮侀敊璇紓甯哥被
+ *
+ * @author Lion Li
+ */
+public class CaptchaException extends UserException {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    public CaptchaException() {
+        super("user.jcaptcha.error");
+    }
+
+    public CaptchaException(String msg) {
+        super(msg);
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/CaptchaExpireException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/CaptchaExpireException.java
new file mode 100644
index 0000000..f4b8cac
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/CaptchaExpireException.java
@@ -0,0 +1,18 @@
+package org.dromara.common.core.exception.user;
+
+import java.io.Serial;
+
+/**
+ * 楠岃瘉鐮佸け鏁堝紓甯哥被
+ *
+ * @author ruoyi
+ */
+public class CaptchaExpireException extends UserException {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    public CaptchaExpireException() {
+        super("user.jcaptcha.expire");
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/UserException.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/UserException.java
new file mode 100644
index 0000000..024fed6
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/exception/user/UserException.java
@@ -0,0 +1,20 @@
+package org.dromara.common.core.exception.user;
+
+import org.dromara.common.core.exception.base.BaseException;
+
+import java.io.Serial;
+
+/**
+ * 鐢ㄦ埛淇℃伅寮傚父绫�
+ *
+ * @author ruoyi
+ */
+public class UserException extends BaseException {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    public UserException(String code, Object... args) {
+        super("user", code, args, null);
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/factory/YmlPropertySourceFactory.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/factory/YmlPropertySourceFactory.java
new file mode 100644
index 0000000..af61b90
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/factory/YmlPropertySourceFactory.java
@@ -0,0 +1,31 @@
+package org.dromara.common.core.factory;
+
+import org.dromara.common.core.utils.StringUtils;
+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.DefaultPropertySourceFactory;
+import org.springframework.core.io.support.EncodedResource;
+
+import java.io.IOException;
+
+/**
+ * yml 閰嶇疆婧愬伐鍘�
+ *
+ * @author Lion Li
+ */
+public class YmlPropertySourceFactory extends DefaultPropertySourceFactory {
+
+    @Override
+    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
+        String sourceName = resource.getResource().getFilename();
+        if (StringUtils.isNotBlank(sourceName) && StringUtils.endsWithAny(sourceName, ".yml", ".yaml")) {
+            YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
+            factory.setResources(resource.getResource());
+            factory.afterPropertiesSet();
+            return new PropertiesPropertySource(sourceName, factory.getObject());
+        }
+        return super.createPropertySource(name, resource);
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/DictService.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/DictService.java
new file mode 100644
index 0000000..9f2632f
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/service/DictService.java
@@ -0,0 +1,63 @@
+package org.dromara.common.core.service;
+
+import org.dromara.common.core.utils.StringUtils;
+
+import java.util.Map;
+
+/**
+ * 瀛楀吀鏈嶅姟鏈嶅姟
+ *
+ * @author Lion Li
+ */
+public interface DictService {
+
+    /**
+     * 鏍规嵁瀛楀吀绫诲瀷鍜屽瓧鍏稿�艰幏鍙栧瓧鍏告爣绛�
+     *
+     * @param dictType  瀛楀吀绫诲瀷
+     * @param dictValue 瀛楀吀鍊�
+     * @return 瀛楀吀鏍囩
+     */
+    default String getDictLabel(String dictType, String dictValue) {
+        return getDictLabel(dictType, dictValue, StringUtils.SEPARATOR);
+    }
+
+    /**
+     * 鏍规嵁瀛楀吀绫诲瀷鍜屽瓧鍏告爣绛捐幏鍙栧瓧鍏稿��
+     *
+     * @param dictType  瀛楀吀绫诲瀷
+     * @param dictLabel 瀛楀吀鏍囩
+     * @return 瀛楀吀鍊�
+     */
+    default String getDictValue(String dictType, String dictLabel) {
+        return getDictValue(dictType, dictLabel, StringUtils.SEPARATOR);
+    }
+
+    /**
+     * 鏍规嵁瀛楀吀绫诲瀷鍜屽瓧鍏稿�艰幏鍙栧瓧鍏告爣绛�
+     *
+     * @param dictType  瀛楀吀绫诲瀷
+     * @param dictValue 瀛楀吀鍊�
+     * @param separator 鍒嗛殧绗�
+     * @return 瀛楀吀鏍囩
+     */
+    String getDictLabel(String dictType, String dictValue, String separator);
+
+    /**
+     * 鏍规嵁瀛楀吀绫诲瀷鍜屽瓧鍏告爣绛捐幏鍙栧瓧鍏稿��
+     *
+     * @param dictType  瀛楀吀绫诲瀷
+     * @param dictLabel 瀛楀吀鏍囩
+     * @param separator 鍒嗛殧绗�
+     * @return 瀛楀吀鍊�
+     */
+    String getDictValue(String dictType, String dictLabel, String separator);
+
+    /**
+     * 鑾峰彇瀛楀吀涓嬫墍鏈夌殑瀛楀吀鍊间笌鏍囩
+     *
+     * @param dictType 瀛楀吀绫诲瀷
+     * @return dictValue涓簁ey锛宒ictLabel涓哄�肩粍鎴愮殑Map
+     */
+    Map<String, String> getAllDictByDictType(String dictType);
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DateUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DateUtils.java
new file mode 100644
index 0000000..72178a7
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/DateUtils.java
@@ -0,0 +1,168 @@
+package org.dromara.common.core.utils;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.apache.commons.lang3.time.DateFormatUtils;
+
+import java.lang.management.ManagementFactory;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.Date;
+
+/**
+ * 鏃堕棿宸ュ叿绫�
+ *
+ * @author ruoyi
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
+
+    public static final String YYYY = "yyyy";
+
+    public static final String YYYY_MM = "yyyy-MM";
+
+    public static final String YYYY_MM_DD = "yyyy-MM-dd";
+
+    public static final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
+
+    public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
+
+    private static final String[] PARSE_PATTERNS = {
+        "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
+        "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
+        "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
+
+    /**
+     * 鑾峰彇褰撳墠Date鍨嬫棩鏈�
+     *
+     * @return Date() 褰撳墠鏃ユ湡
+     */
+    public static Date getNowDate() {
+        return new Date();
+    }
+
+    /**
+     * 鑾峰彇褰撳墠鏃ユ湡, 榛樿鏍煎紡涓簓yyy-MM-dd
+     *
+     * @return String
+     */
+    public static String getDate() {
+        return dateTimeNow(YYYY_MM_DD);
+    }
+
+    public static String getTime() {
+        return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
+    }
+
+    public static String dateTimeNow() {
+        return dateTimeNow(YYYYMMDDHHMMSS);
+    }
+
+    public static String dateTimeNow(final String format) {
+        return parseDateToStr(format, new Date());
+    }
+
+    public static String dateTime(final Date date) {
+        return parseDateToStr(YYYY_MM_DD, date);
+    }
+
+    public static String parseDateToStr(final String format, final Date date) {
+        return new SimpleDateFormat(format).format(date);
+    }
+
+    public static Date dateTime(final String format, final String ts) {
+        try {
+            return new SimpleDateFormat(format).parse(ts);
+        } catch (ParseException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * 鏃ユ湡璺緞 鍗冲勾/鏈�/鏃� 濡�2018/08/08
+     */
+    public static String datePath() {
+        Date now = new Date();
+        return DateFormatUtils.format(now, "yyyy/MM/dd");
+    }
+
+    /**
+     * 鏃ユ湡璺緞 鍗冲勾/鏈�/鏃� 濡�20180808
+     */
+    public static String dateTime() {
+        Date now = new Date();
+        return DateFormatUtils.format(now, "yyyyMMdd");
+    }
+
+    /**
+     * 鏃ユ湡鍨嬪瓧绗︿覆杞寲涓烘棩鏈� 鏍煎紡
+     */
+    public static Date parseDate(Object str) {
+        if (str == null) {
+            return null;
+        }
+        try {
+            return parseDate(str.toString(), PARSE_PATTERNS);
+        } catch (ParseException e) {
+            return null;
+        }
+    }
+
+    /**
+     * 鑾峰彇鏈嶅姟鍣ㄥ惎鍔ㄦ椂闂�
+     */
+    public static Date getServerStartDate() {
+        long time = ManagementFactory.getRuntimeMXBean().getStartTime();
+        return new Date(time);
+    }
+
+    /**
+     * 璁$畻鐩稿樊澶╂暟
+     */
+    public static int differentDaysByMillisecond(Date date1, Date date2) {
+        return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24)));
+    }
+
+    /**
+     * 璁$畻涓や釜鏃堕棿宸�
+     */
+    public static String getDatePoor(Date endDate, Date nowDate) {
+        long nd = 1000 * 24 * 60 * 60;
+        long nh = 1000 * 60 * 60;
+        long nm = 1000 * 60;
+        // long ns = 1000;
+        // 鑾峰緱涓や釜鏃堕棿鐨勬绉掓椂闂村樊寮�
+        long diff = endDate.getTime() - nowDate.getTime();
+        // 璁$畻宸灏戝ぉ
+        long day = diff / nd;
+        // 璁$畻宸灏戝皬鏃�
+        long hour = diff % nd / nh;
+        // 璁$畻宸灏戝垎閽�
+        long min = diff % nd % nh / nm;
+        // 璁$畻宸灏戠//杈撳嚭缁撴灉
+        // long sec = diff % nd % nh % nm / ns;
+        return day + "澶�" + hour + "灏忔椂" + min + "鍒嗛挓";
+    }
+
+    /**
+     * 澧炲姞 LocalDateTime ==> Date
+     */
+    public static Date toDate(LocalDateTime temporalAccessor) {
+        ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault());
+        return Date.from(zdt.toInstant());
+    }
+
+    /**
+     * 澧炲姞 LocalDate ==> Date
+     */
+    public static Date toDate(LocalDate temporalAccessor) {
+        LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0));
+        ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
+        return Date.from(zdt.toInstant());
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/MapstructUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/MapstructUtils.java
new file mode 100644
index 0000000..a3fcb8e
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/MapstructUtils.java
@@ -0,0 +1,92 @@
+package org.dromara.common.core.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ObjectUtil;
+import io.github.linpeilie.Converter;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Mapstruct 宸ュ叿绫�
+ * <p>鍙傝�冩枃妗o細<a href="https://mapstruct.plus/introduction/quick-start.html">mapstruct-plus</a></p>
+ *
+ * @author Michelle.Chung
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class MapstructUtils {
+
+    private final static Converter CONVERTER = SpringUtils.getBean(Converter.class);
+
+    /**
+     * 灏� T 绫诲瀷瀵硅薄锛岃浆鎹负 desc 绫诲瀷鐨勫璞″苟杩斿洖
+     *
+     * @param source 鏁版嵁鏉ユ簮瀹炰綋
+     * @param desc   鎻忚堪瀵硅薄 杞崲鍚庣殑瀵硅薄
+     * @return desc
+     */
+    public static <T, V> V convert(T source, Class<V> desc) {
+        if (ObjectUtil.isNull(source)) {
+            return null;
+        }
+        if (ObjectUtil.isNull(desc)) {
+            return null;
+        }
+        return CONVERTER.convert(source, desc);
+    }
+
+    /**
+     * 灏� T 绫诲瀷瀵硅薄锛屾寜鐓ч厤缃殑鏄犲皠瀛楁瑙勫垯锛岀粰 desc 绫诲瀷鐨勫璞¤祴鍊煎苟杩斿洖 desc 瀵硅薄
+     *
+     * @param source 鏁版嵁鏉ユ簮瀹炰綋
+     * @param desc   杞崲鍚庣殑瀵硅薄
+     * @return desc
+     */
+    public static <T, V> V convert(T source, V desc) {
+        if (ObjectUtil.isNull(source)) {
+            return null;
+        }
+        if (ObjectUtil.isNull(desc)) {
+            return null;
+        }
+        return CONVERTER.convert(source, desc);
+    }
+
+    /**
+     * 灏� T 绫诲瀷鐨勯泦鍚堬紝杞崲涓� desc 绫诲瀷鐨勯泦鍚堝苟杩斿洖
+     *
+     * @param sourceList 鏁版嵁鏉ユ簮瀹炰綋鍒楄〃
+     * @param desc       鎻忚堪瀵硅薄 杞崲鍚庣殑瀵硅薄
+     * @return desc
+     */
+    public static <T, V> List<V> convert(List<T> sourceList, Class<V> desc) {
+        if (ObjectUtil.isNull(sourceList)) {
+            return null;
+        }
+        if (CollUtil.isEmpty(sourceList)) {
+            return CollUtil.newArrayList();
+        }
+        return CONVERTER.convert(sourceList, desc);
+    }
+
+    /**
+     * 灏� Map 杞崲涓� beanClass 绫诲瀷鐨勯泦鍚堝苟杩斿洖
+     *
+     * @param map       鏁版嵁鏉ユ簮
+     * @param beanClass bean绫�
+     * @return bean瀵硅薄
+     */
+    public static <T> T convert(Map<String, Object> map, Class<T> beanClass) {
+        if (MapUtil.isEmpty(map)) {
+            return null;
+        }
+        if (ObjectUtil.isNull(beanClass)) {
+            return null;
+        }
+        return CONVERTER.convert(map, beanClass);
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/MessageUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/MessageUtils.java
new file mode 100644
index 0000000..48dfc08
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/MessageUtils.java
@@ -0,0 +1,33 @@
+package org.dromara.common.core.utils;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.springframework.context.MessageSource;
+import org.springframework.context.NoSuchMessageException;
+import org.springframework.context.i18n.LocaleContextHolder;
+
+/**
+ * 鑾峰彇i18n璧勬簮鏂囦欢
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class MessageUtils {
+
+    private static final MessageSource MESSAGE_SOURCE = SpringUtils.getBean(MessageSource.class);
+
+    /**
+     * 鏍规嵁娑堟伅閿拰鍙傛暟 鑾峰彇娑堟伅 濮旀墭缁檚pring messageSource
+     *
+     * @param code 娑堟伅閿�
+     * @param args 鍙傛暟
+     * @return 鑾峰彇鍥介檯鍖栫炕璇戝��
+     */
+    public static String message(String code, Object... args) {
+        try {
+            return MESSAGE_SOURCE.getMessage(code, args, LocaleContextHolder.getLocale());
+        } catch (NoSuchMessageException e) {
+            return code;
+        }
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ReUtil.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ReUtil.java
new file mode 100644
index 0000000..2de7f4f
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ReUtil.java
@@ -0,0 +1,148 @@
+package org.dromara.common.core.utils;
+
+
+import cn.hutool.core.convert.Convert;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class ReUtil {
+    public final static Pattern GROUP_VAR = Pattern.compile("\\$(\\d+)");
+
+    /**
+     * 姝e垯涓渶瑕佽杞箟鐨勫叧閿瓧
+     */
+    public final static Set<Character> RE_KEYS = new HashSet<>(
+            Arrays.asList('$', '(', ')', '*', '+', '.', '[', ']', '?', '\\', '^', '{', '}', '|'));
+    ;
+
+    /**
+     * 姝e垯鏇挎崲鎸囧畾鍊�<br>
+     * 閫氳繃姝e垯鏌ユ壘鍒板瓧绗︿覆锛岀劧鍚庢妸鍖归厤鍒扮殑瀛楃涓插姞鍏ュ埌replacementTemplate涓紝$1琛ㄧず鍒嗙粍1鐨勫瓧绗︿覆
+     *
+     * <p>
+     * 渚嬪锛氬師瀛楃涓叉槸锛氫腑鏂�1234锛屾垜鎯虫妸1234鎹㈡垚(1234)锛屽垯鍙互锛�
+     *
+     * <pre>
+     * ReUtil.replaceAll("涓枃1234", "(\\d+)", "($1)"))
+     *
+     * 缁撴灉锛氫腑鏂�(1234)
+     * </pre>
+     *
+     * @param content             鏂囨湰
+     * @param regex               姝e垯
+     * @param replacementTemplate 鏇挎崲鐨勬枃鏈ā鏉匡紝鍙互浣跨敤$1绫讳技鐨勫彉閲忔彁鍙栨鍒欏尮閰嶅嚭鐨勫唴瀹�
+     * @return 澶勭悊鍚庣殑鏂囨湰
+     */
+    public static String replaceAll(CharSequence content, String regex, String replacementTemplate) {
+        final Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
+        return replaceAll(content, pattern, replacementTemplate);
+    }
+
+    /**
+     * 姝e垯鏇挎崲鎸囧畾鍊�<br>
+     * 閫氳繃姝e垯鏌ユ壘鍒板瓧绗︿覆锛岀劧鍚庢妸鍖归厤鍒扮殑瀛楃涓插姞鍏ュ埌replacementTemplate涓紝$1琛ㄧず鍒嗙粍1鐨勫瓧绗︿覆
+     *
+     * @param content             鏂囨湰
+     * @param pattern             {@link Pattern}
+     * @param replacementTemplate 鏇挎崲鐨勬枃鏈ā鏉匡紝鍙互浣跨敤$1绫讳技鐨勫彉閲忔彁鍙栨鍒欏尮閰嶅嚭鐨勫唴瀹�
+     * @return 澶勭悊鍚庣殑鏂囨湰
+     * @since 3.0.4
+     */
+    public static String replaceAll(CharSequence content, Pattern pattern, String replacementTemplate) {
+        if (StringUtils.isEmpty(content)) {
+            return StringUtils.EMPTY;
+        }
+
+        final Matcher matcher = pattern.matcher(content);
+        boolean result = matcher.find();
+        if (result) {
+            final Set<String> varNums = findAll(GROUP_VAR, replacementTemplate, 1, new HashSet<>());
+            final StringBuffer sb = new StringBuffer();
+            do {
+                String replacement = replacementTemplate;
+                for (String var : varNums) {
+                    int group = Integer.parseInt(var);
+                    replacement = replacement.replace("$" + var, matcher.group(group));
+                }
+                matcher.appendReplacement(sb, escape(replacement));
+                result = matcher.find();
+            }
+            while (result);
+            matcher.appendTail(sb);
+            return sb.toString();
+        }
+        return Convert.toStr(content);
+    }
+
+    /**
+     * 鍙栧緱鍐呭涓尮閰嶇殑鎵�鏈夌粨鏋�
+     *
+     * @param <T>        闆嗗悎绫诲瀷
+     * @param pattern    缂栬瘧鍚庣殑姝e垯妯″紡
+     * @param content    琚煡鎵剧殑鍐呭
+     * @param group      姝e垯鐨勫垎缁�
+     * @param collection 杩斿洖鐨勯泦鍚堢被鍨�
+     * @return 缁撴灉闆�
+     */
+    public static <T extends Collection<String>> T findAll(Pattern pattern, CharSequence content, int group,
+                                                           T collection) {
+        if (null == pattern || null == content) {
+            return null;
+        }
+
+        if (null == collection) {
+            throw new NullPointerException("Null collection param provided!");
+        }
+
+        final Matcher matcher = pattern.matcher(content);
+        while (matcher.find()) {
+            collection.add(matcher.group(group));
+        }
+        return collection;
+    }
+
+    /**
+     * 杞箟瀛楃锛屽皢姝e垯鐨勫叧閿瓧杞箟
+     *
+     * @param c 瀛楃
+     * @return 杞箟鍚庣殑鏂囨湰
+     */
+    public static String escape(char c) {
+        final StringBuilder builder = new StringBuilder();
+        if (RE_KEYS.contains(c)) {
+            builder.append('\\');
+        }
+        builder.append(c);
+        return builder.toString();
+    }
+
+    /**
+     * 杞箟瀛楃涓诧紝灏嗘鍒欑殑鍏抽敭瀛楄浆涔�
+     *
+     * @param content 鏂囨湰
+     * @return 杞箟鍚庣殑鏂囨湰
+     */
+    public static String escape(CharSequence content) {
+        if (StringUtils.isBlank(content)) {
+            return StringUtils.EMPTY;
+        }
+
+        final StringBuilder builder = new StringBuilder();
+        int len = content.length();
+        char current;
+        for (int i = 0; i < len; i++) {
+            current = content.charAt(i);
+            if (RE_KEYS.contains(current)) {
+                builder.append('\\');
+            }
+            builder.append(current);
+        }
+        return builder.toString();
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ServletUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ServletUtils.java
new file mode 100644
index 0000000..a1316eb
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ServletUtils.java
@@ -0,0 +1,228 @@
+package org.dromara.common.core.utils;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.extra.servlet.JakartaServletUtil;
+import cn.hutool.http.HttpStatus;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpSession;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.springframework.http.MediaType;
+import org.springframework.util.LinkedCaseInsensitiveMap;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import java.io.IOException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 瀹㈡埛绔伐鍏风被
+ *
+ * @author ruoyi
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ServletUtils extends JakartaServletUtil {
+
+    /**
+     * 鑾峰彇String鍙傛暟
+     */
+    public static String getParameter(String name) {
+        return getRequest().getParameter(name);
+    }
+
+    /**
+     * 鑾峰彇String鍙傛暟
+     */
+    public static String getParameter(String name, String defaultValue) {
+        return Convert.toStr(getRequest().getParameter(name), defaultValue);
+    }
+
+    /**
+     * 鑾峰彇Integer鍙傛暟
+     */
+    public static Integer getParameterToInt(String name) {
+        return Convert.toInt(getRequest().getParameter(name));
+    }
+
+    /**
+     * 鑾峰彇Integer鍙傛暟
+     */
+    public static Integer getParameterToInt(String name, Integer defaultValue) {
+        return Convert.toInt(getRequest().getParameter(name), defaultValue);
+    }
+
+    /**
+     * 鑾峰彇Boolean鍙傛暟
+     */
+    public static Boolean getParameterToBool(String name) {
+        return Convert.toBool(getRequest().getParameter(name));
+    }
+
+    /**
+     * 鑾峰彇Boolean鍙傛暟
+     */
+    public static Boolean getParameterToBool(String name, Boolean defaultValue) {
+        return Convert.toBool(getRequest().getParameter(name), defaultValue);
+    }
+
+    /**
+     * 鑾峰緱鎵�鏈夎姹傚弬鏁�
+     *
+     * @param request 璇锋眰瀵硅薄{@link ServletRequest}
+     * @return Map
+     */
+    public static Map<String, String[]> getParams(ServletRequest request) {
+        final Map<String, String[]> map = request.getParameterMap();
+        return Collections.unmodifiableMap(map);
+    }
+
+    /**
+     * 鑾峰緱鎵�鏈夎姹傚弬鏁�
+     *
+     * @param request 璇锋眰瀵硅薄{@link ServletRequest}
+     * @return Map
+     */
+    public static Map<String, String> getParamMap(ServletRequest request) {
+        Map<String, String> params = new HashMap<>();
+        for (Map.Entry<String, String[]> entry : getParams(request).entrySet()) {
+            params.put(entry.getKey(), StringUtils.join(entry.getValue(), StringUtils.SEPARATOR));
+        }
+        return params;
+    }
+
+    /**
+     * 鑾峰彇request
+     */
+    public static HttpServletRequest getRequest() {
+        try {
+            return getRequestAttributes().getRequest();
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+     * 鑾峰彇response
+     */
+    public static HttpServletResponse getResponse() {
+        try {
+            return getRequestAttributes().getResponse();
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+     * 鑾峰彇session
+     */
+    public static HttpSession getSession() {
+        return getRequest().getSession();
+    }
+
+    public static ServletRequestAttributes getRequestAttributes() {
+        try {
+            RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
+            return (ServletRequestAttributes) attributes;
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    public static String getHeader(HttpServletRequest request, String name) {
+        String value = request.getHeader(name);
+        if (StringUtils.isEmpty(value)) {
+            return StringUtils.EMPTY;
+        }
+        return urlDecode(value);
+    }
+
+    public static Map<String, String> getHeaders(HttpServletRequest request) {
+        Map<String, String> map = new LinkedCaseInsensitiveMap<>();
+        Enumeration<String> enumeration = request.getHeaderNames();
+        if (enumeration != null) {
+            while (enumeration.hasMoreElements()) {
+                String key = enumeration.nextElement();
+                String value = request.getHeader(key);
+                map.put(key, value);
+            }
+        }
+        return map;
+    }
+
+    /**
+     * 灏嗗瓧绗︿覆娓叉煋鍒板鎴风
+     *
+     * @param response 娓叉煋瀵硅薄
+     * @param string   寰呮覆鏌撶殑瀛楃涓�
+     */
+    public static void renderString(HttpServletResponse response, String string) {
+        try {
+            response.setStatus(HttpStatus.HTTP_OK);
+            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+            response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
+            response.getWriter().print(string);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 鏄惁鏄疉jax寮傛璇锋眰
+     *
+     * @param request
+     */
+    public static boolean isAjaxRequest(HttpServletRequest request) {
+
+        String accept = request.getHeader("accept");
+        if (accept != null && accept.contains(MediaType.APPLICATION_JSON_VALUE)) {
+            return true;
+        }
+
+        String xRequestedWith = request.getHeader("X-Requested-With");
+        if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) {
+            return true;
+        }
+
+        String uri = request.getRequestURI();
+        if (StringUtils.equalsAnyIgnoreCase(uri, ".json", ".xml")) {
+            return true;
+        }
+
+        String ajax = request.getParameter("__ajax");
+        return StringUtils.equalsAnyIgnoreCase(ajax, "json", "xml");
+    }
+
+    public static String getClientIP() {
+        return getClientIP(getRequest());
+    }
+
+    /**
+     * 鍐呭缂栫爜
+     *
+     * @param str 鍐呭
+     * @return 缂栫爜鍚庣殑鍐呭
+     */
+    public static String urlEncode(String str) {
+        return URLEncoder.encode(str, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * 鍐呭瑙g爜
+     *
+     * @param str 鍐呭
+     * @return 瑙g爜鍚庣殑鍐呭
+     */
+    public static String urlDecode(String str) {
+        return URLDecoder.decode(str, StandardCharsets.UTF_8);
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/SpringUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/SpringUtils.java
new file mode 100644
index 0000000..0af4d51
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/SpringUtils.java
@@ -0,0 +1,60 @@
+package org.dromara.common.core.utils;
+
+import cn.hutool.extra.spring.SpringUtil;
+import org.springframework.aop.framework.AopContext;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.context.ApplicationContext;
+
+/**
+ * spring宸ュ叿绫�
+ *
+ * @author Lion Li
+ */
+public final class SpringUtils extends SpringUtil {
+
+    /**
+     * 濡傛灉BeanFactory鍖呭惈涓�涓笌鎵�缁欏悕绉板尮閰嶇殑bean瀹氫箟锛屽垯杩斿洖true
+     */
+    public static boolean containsBean(String name) {
+        return getBeanFactory().containsBean(name);
+    }
+
+    /**
+     * 鍒ゆ柇浠ョ粰瀹氬悕瀛楁敞鍐岀殑bean瀹氫箟鏄竴涓猻ingleton杩樻槸涓�涓猵rototype銆�
+     * 濡傛灉涓庣粰瀹氬悕瀛楃浉搴旂殑bean瀹氫箟娌℃湁琚壘鍒帮紝灏嗕細鎶涘嚭涓�涓紓甯革紙NoSuchBeanDefinitionException锛�
+     */
+    public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
+        return getBeanFactory().isSingleton(name);
+    }
+
+    /**
+     * @return Class 娉ㄥ唽瀵硅薄鐨勭被鍨�
+     */
+    public static Class<?> getType(String name) throws NoSuchBeanDefinitionException {
+        return getBeanFactory().getType(name);
+    }
+
+    /**
+     * 濡傛灉缁欏畾鐨刡ean鍚嶅瓧鍦╞ean瀹氫箟涓湁鍒悕锛屽垯杩斿洖杩欎簺鍒悕
+     */
+    public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
+        return getBeanFactory().getAliases(name);
+    }
+
+    /**
+     * 鑾峰彇aop浠g悊瀵硅薄
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T getAopProxy(T invoker) {
+        return (T) AopContext.currentProxy();
+    }
+
+
+    /**
+     * 鑾峰彇spring涓婁笅鏂�
+     */
+    public static ApplicationContext context() {
+        return getApplicationContext();
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StreamUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StreamUtils.java
new file mode 100644
index 0000000..967612e
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StreamUtils.java
@@ -0,0 +1,254 @@
+package org.dromara.common.core.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.util.*;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+/**
+ * stream 娴佸伐鍏风被
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class StreamUtils {
+
+    /**
+     * 灏哻ollection杩囨护
+     *
+     * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+     * @param function   杩囨护鏂规硶
+     * @return 杩囨护鍚庣殑list
+     */
+    public static <E> List<E> filter(Collection<E> collection, Predicate<E> function) {
+        if (CollUtil.isEmpty(collection)) {
+            return CollUtil.newArrayList();
+        }
+        // 娉ㄦ剰姝ゅ涓嶈浣跨敤 .toList() 鏂拌娉� 鍥犱负杩斿洖鐨勬槸涓嶅彲鍙楲ist 浼氬鑷村簭鍒楀寲闂
+        return collection.stream().filter(function).collect(Collectors.toList());
+    }
+
+    /**
+     * 灏哻ollection鎷兼帴
+     *
+     * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+     * @param function   鎷兼帴鏂规硶
+     * @return 鎷兼帴鍚庣殑list
+     */
+    public static <E> String join(Collection<E> collection, Function<E, String> function) {
+        return join(collection, function, StringUtils.SEPARATOR);
+    }
+
+    /**
+     * 灏哻ollection鎷兼帴
+     *
+     * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+     * @param function   鎷兼帴鏂规硶
+     * @param delimiter  鎷兼帴绗�
+     * @return 鎷兼帴鍚庣殑list
+     */
+    public static <E> String join(Collection<E> collection, Function<E, String> function, CharSequence delimiter) {
+        if (CollUtil.isEmpty(collection)) {
+            return StringUtils.EMPTY;
+        }
+        return collection.stream().map(function).filter(Objects::nonNull).collect(Collectors.joining(delimiter));
+    }
+
+    /**
+     * 灏哻ollection鎺掑簭
+     *
+     * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+     * @param comparing  鎺掑簭鏂规硶
+     * @return 鎺掑簭鍚庣殑list
+     */
+    public static <E> List<E> sorted(Collection<E> collection, Comparator<E> comparing) {
+        if (CollUtil.isEmpty(collection)) {
+            return CollUtil.newArrayList();
+        }
+        // 娉ㄦ剰姝ゅ涓嶈浣跨敤 .toList() 鏂拌娉� 鍥犱负杩斿洖鐨勬槸涓嶅彲鍙楲ist 浼氬鑷村簭鍒楀寲闂
+        return collection.stream().filter(Objects::nonNull).sorted(comparing).collect(Collectors.toList());
+    }
+
+    /**
+     * 灏哻ollection杞寲涓虹被鍨嬩笉鍙樼殑map<br>
+     * <B>{@code Collection<V>  ---->  Map<K,V>}</B>
+     *
+     * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+     * @param key        V绫诲瀷杞寲涓篕绫诲瀷鐨刲ambda鏂规硶
+     * @param <V>        collection涓殑娉涘瀷
+     * @param <K>        map涓殑key绫诲瀷
+     * @return 杞寲鍚庣殑map
+     */
+    public static <V, K> Map<K, V> toIdentityMap(Collection<V> collection, Function<V, K> key) {
+        if (CollUtil.isEmpty(collection)) {
+            return MapUtil.newHashMap();
+        }
+        return collection.stream().filter(Objects::nonNull).collect(Collectors.toMap(key, Function.identity(), (l, r) -> l));
+    }
+
+    /**
+     * 灏咰ollection杞寲涓簃ap(value绫诲瀷涓巆ollection鐨勬硾鍨嬩笉鍚�)<br>
+     * <B>{@code Collection<E> -----> Map<K,V>  }</B>
+     *
+     * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+     * @param key        E绫诲瀷杞寲涓篕绫诲瀷鐨刲ambda鏂规硶
+     * @param value      E绫诲瀷杞寲涓篤绫诲瀷鐨刲ambda鏂规硶
+     * @param <E>        collection涓殑娉涘瀷
+     * @param <K>        map涓殑key绫诲瀷
+     * @param <V>        map涓殑value绫诲瀷
+     * @return 杞寲鍚庣殑map
+     */
+    public static <E, K, V> Map<K, V> toMap(Collection<E> collection, Function<E, K> key, Function<E, V> value) {
+        if (CollUtil.isEmpty(collection)) {
+            return MapUtil.newHashMap();
+        }
+        return collection.stream().filter(Objects::nonNull).collect(Collectors.toMap(key, value, (l, r) -> l));
+    }
+
+    /**
+     * 灏哻ollection鎸夌収瑙勫垯(姣斿鏈夌浉鍚岀殑鐝骇id)鍒嗙被鎴恗ap<br>
+     * <B>{@code Collection<E> -------> Map<K,List<E>> } </B>
+     *
+     * @param collection 闇�瑕佸垎绫荤殑闆嗗悎
+     * @param key        鍒嗙被鐨勮鍒�
+     * @param <E>        collection涓殑娉涘瀷
+     * @param <K>        map涓殑key绫诲瀷
+     * @return 鍒嗙被鍚庣殑map
+     */
+    public static <E, K> Map<K, List<E>> groupByKey(Collection<E> collection, Function<E, K> key) {
+        if (CollUtil.isEmpty(collection)) {
+            return MapUtil.newHashMap();
+        }
+        return collection
+            .stream().filter(Objects::nonNull)
+            .collect(Collectors.groupingBy(key, LinkedHashMap::new, Collectors.toList()));
+    }
+
+    /**
+     * 灏哻ollection鎸夌収涓や釜瑙勫垯(姣斿鏈夌浉鍚岀殑骞寸骇id,鐝骇id)鍒嗙被鎴愬弻灞俶ap<br>
+     * <B>{@code Collection<E>  --->  Map<T,Map<U,List<E>>> } </B>
+     *
+     * @param collection 闇�瑕佸垎绫荤殑闆嗗悎
+     * @param key1       绗竴涓垎绫荤殑瑙勫垯
+     * @param key2       绗簩涓垎绫荤殑瑙勫垯
+     * @param <E>        闆嗗悎鍏冪礌绫诲瀷
+     * @param <K>        绗竴涓猰ap涓殑key绫诲瀷
+     * @param <U>        绗簩涓猰ap涓殑key绫诲瀷
+     * @return 鍒嗙被鍚庣殑map
+     */
+    public static <E, K, U> Map<K, Map<U, List<E>>> groupBy2Key(Collection<E> collection, Function<E, K> key1, Function<E, U> key2) {
+        if (CollUtil.isEmpty(collection)) {
+            return MapUtil.newHashMap();
+        }
+        return collection
+            .stream().filter(Objects::nonNull)
+            .collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.groupingBy(key2, LinkedHashMap::new, Collectors.toList())));
+    }
+
+    /**
+     * 灏哻ollection鎸夌収涓や釜瑙勫垯(姣斿鏈夌浉鍚岀殑骞寸骇id,鐝骇id)鍒嗙被鎴愬弻灞俶ap<br>
+     * <B>{@code Collection<E>  --->  Map<T,Map<U,E>> } </B>
+     *
+     * @param collection 闇�瑕佸垎绫荤殑闆嗗悎
+     * @param key1       绗竴涓垎绫荤殑瑙勫垯
+     * @param key2       绗簩涓垎绫荤殑瑙勫垯
+     * @param <T>        绗竴涓猰ap涓殑key绫诲瀷
+     * @param <U>        绗簩涓猰ap涓殑key绫诲瀷
+     * @param <E>        collection涓殑娉涘瀷
+     * @return 鍒嗙被鍚庣殑map
+     */
+    public static <E, T, U> Map<T, Map<U, E>> group2Map(Collection<E> collection, Function<E, T> key1, Function<E, U> key2) {
+        if (CollUtil.isEmpty(collection) || key1 == null || key2 == null) {
+            return MapUtil.newHashMap();
+        }
+        return collection
+            .stream().filter(Objects::nonNull)
+            .collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.toMap(key2, Function.identity(), (l, r) -> l)));
+    }
+
+    /**
+     * 灏哻ollection杞寲涓篖ist闆嗗悎锛屼絾鏄袱鑰呯殑娉涘瀷涓嶅悓<br>
+     * <B>{@code Collection<E>  ------>  List<T> } </B>
+     *
+     * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+     * @param function   collection涓殑娉涘瀷杞寲涓簂ist娉涘瀷鐨刲ambda琛ㄨ揪寮�
+     * @param <E>        collection涓殑娉涘瀷
+     * @param <T>        List涓殑娉涘瀷
+     * @return 杞寲鍚庣殑list
+     */
+    public static <E, T> List<T> toList(Collection<E> collection, Function<E, T> function) {
+        if (CollUtil.isEmpty(collection)) {
+            return CollUtil.newArrayList();
+        }
+        return collection
+            .stream()
+            .map(function)
+            .filter(Objects::nonNull)
+            // 娉ㄦ剰姝ゅ涓嶈浣跨敤 .toList() 鏂拌娉� 鍥犱负杩斿洖鐨勬槸涓嶅彲鍙楲ist 浼氬鑷村簭鍒楀寲闂
+            .collect(Collectors.toList());
+    }
+
+    /**
+     * 灏哻ollection杞寲涓篠et闆嗗悎锛屼絾鏄袱鑰呯殑娉涘瀷涓嶅悓<br>
+     * <B>{@code Collection<E>  ------>  Set<T> } </B>
+     *
+     * @param collection 闇�瑕佽浆鍖栫殑闆嗗悎
+     * @param function   collection涓殑娉涘瀷杞寲涓簊et娉涘瀷鐨刲ambda琛ㄨ揪寮�
+     * @param <E>        collection涓殑娉涘瀷
+     * @param <T>        Set涓殑娉涘瀷
+     * @return 杞寲鍚庣殑Set
+     */
+    public static <E, T> Set<T> toSet(Collection<E> collection, Function<E, T> function) {
+        if (CollUtil.isEmpty(collection) || function == null) {
+            return CollUtil.newHashSet();
+        }
+        return collection
+            .stream()
+            .map(function)
+            .filter(Objects::nonNull)
+            .collect(Collectors.toSet());
+    }
+
+
+    /**
+     * 鍚堝苟涓や釜鐩稿悓key绫诲瀷鐨刴ap
+     *
+     * @param map1  绗竴涓渶瑕佸悎骞剁殑 map
+     * @param map2  绗簩涓渶瑕佸悎骞剁殑 map
+     * @param merge 鍚堝苟鐨刲ambda锛屽皢key  value1 value2鍚堝苟鎴愭渶缁堢殑绫诲瀷,娉ㄦ剰value鍙兘涓虹┖鐨勬儏鍐�
+     * @param <K>   map涓殑key绫诲瀷
+     * @param <X>   绗竴涓� map鐨剉alue绫诲瀷
+     * @param <Y>   绗簩涓� map鐨剉alue绫诲瀷
+     * @param <V>   鏈�缁坢ap鐨剉alue绫诲瀷
+     * @return 鍚堝苟鍚庣殑map
+     */
+    public static <K, X, Y, V> Map<K, V> merge(Map<K, X> map1, Map<K, Y> map2, BiFunction<X, Y, V> merge) {
+        if (MapUtil.isEmpty(map1) && MapUtil.isEmpty(map2)) {
+            return MapUtil.newHashMap();
+        } else if (MapUtil.isEmpty(map1)) {
+            map1 = MapUtil.newHashMap();
+        } else if (MapUtil.isEmpty(map2)) {
+            map2 = MapUtil.newHashMap();
+        }
+        Set<K> key = new HashSet<>();
+        key.addAll(map1.keySet());
+        key.addAll(map2.keySet());
+        Map<K, V> map = new HashMap<>();
+        for (K t : key) {
+            X x = map1.get(t);
+            Y y = map2.get(t);
+            V z = merge.apply(x, y);
+            if (z != null) {
+                map.put(t, z);
+            }
+        }
+        return map;
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java
new file mode 100644
index 0000000..5e4db50
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/StringUtils.java
@@ -0,0 +1,321 @@
+package org.dromara.common.core.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.Validator;
+import cn.hutool.core.util.StrUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.springframework.util.AntPathMatcher;
+
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 瀛楃涓插伐鍏风被
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class StringUtils extends org.apache.commons.lang3.StringUtils {
+
+    public static final String SEPARATOR = ",";
+
+    /**
+     * 鑾峰彇鍙傛暟涓嶄负绌哄��
+     *
+     * @param str defaultValue 瑕佸垽鏂殑value
+     * @return value 杩斿洖鍊�
+     */
+    public static String blankToDefault(String str, String defaultValue) {
+        return StrUtil.blankToDefault(str, defaultValue);
+    }
+
+    /**
+     * * 鍒ゆ柇涓�涓瓧绗︿覆鏄惁涓虹┖涓�
+     *
+     * @param str String
+     * @return true锛氫负绌� false锛氶潪绌�
+     */
+    public static boolean isEmpty(String str) {
+        return StrUtil.isEmpty(str);
+    }
+
+    /**
+     * * 鍒ゆ柇涓�涓瓧绗︿覆鏄惁涓洪潪绌轰覆
+     *
+     * @param str String
+     * @return true锛氶潪绌轰覆 false锛氱┖涓�
+     */
+    public static boolean isNotEmpty(String str) {
+        return !isEmpty(str);
+    }
+
+    /**
+     * 鍘荤┖鏍�
+     */
+    public static String trim(String str) {
+        return StrUtil.trim(str);
+    }
+
+    /**
+     * 鎴彇瀛楃涓�
+     *
+     * @param str   瀛楃涓�
+     * @param start 寮�濮�
+     * @return 缁撴灉
+     */
+    public static String substring(final String str, int start) {
+        return substring(str, start, str.length());
+    }
+
+    /**
+     * 鎴彇瀛楃涓�
+     *
+     * @param str   瀛楃涓�
+     * @param start 寮�濮�
+     * @param end   缁撴潫
+     * @return 缁撴灉
+     */
+    public static String substring(final String str, int start, int end) {
+        return StrUtil.sub(str, start, end);
+    }
+
+    /**
+     * 鏍煎紡鍖栨枃鏈�, {} 琛ㄧず鍗犱綅绗�<br>
+     * 姝ゆ柟娉曞彧鏄畝鍗曞皢鍗犱綅绗� {} 鎸夌収椤哄簭鏇挎崲涓哄弬鏁�<br>
+     * 濡傛灉鎯宠緭鍑� {} 浣跨敤 \\杞箟 { 鍗冲彲锛屽鏋滄兂杈撳嚭 {} 涔嬪墠鐨� \ 浣跨敤鍙岃浆涔夌 \\\\ 鍗冲彲<br>
+     * 渚嬶細<br>
+     * 閫氬父浣跨敤锛歠ormat("this is {} for {}", "a", "b") -> this is a for b<br>
+     * 杞箟{}锛� format("this is \\{} for {}", "a", "b") -> this is {} for a<br>
+     * 杞箟\锛� format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br>
+     *
+     * @param template 鏂囨湰妯℃澘锛岃鏇挎崲鐨勯儴鍒嗙敤 {} 琛ㄧず
+     * @param params   鍙傛暟鍊�
+     * @return 鏍煎紡鍖栧悗鐨勬枃鏈�
+     */
+    public static String format(String template, Object... params) {
+        return StrUtil.format(template, params);
+    }
+
+    /**
+     * 鏄惁涓篽ttp(s)://寮�澶�
+     *
+     * @param link 閾炬帴
+     * @return 缁撴灉
+     */
+    public static boolean ishttp(String link) {
+        return Validator.isUrl(link);
+    }
+
+    /**
+     * 瀛楃涓茶浆set
+     *
+     * @param str 瀛楃涓�
+     * @param sep 鍒嗛殧绗�
+     * @return set闆嗗悎
+     */
+    public static Set<String> str2Set(String str, String sep) {
+        return new HashSet<>(str2List(str, sep, true, false));
+    }
+
+    /**
+     * 瀛楃涓茶浆list
+     *
+     * @param str         瀛楃涓�
+     * @param sep         鍒嗛殧绗�
+     * @param filterBlank 杩囨护绾┖鐧�
+     * @param trim        鍘绘帀棣栧熬绌虹櫧
+     * @return list闆嗗悎
+     */
+    public static List<String> str2List(String str, String sep, boolean filterBlank, boolean trim) {
+        List<String> list = new ArrayList<>();
+        if (isEmpty(str)) {
+            return list;
+        }
+
+        // 杩囨护绌虹櫧瀛楃涓�
+        if (filterBlank && isBlank(str)) {
+            return list;
+        }
+        String[] split = str.split(sep);
+        for (String string : split) {
+            if (filterBlank && isBlank(string)) {
+                continue;
+            }
+            if (trim) {
+                string = trim(string);
+            }
+            list.add(string);
+        }
+
+        return list;
+    }
+
+    /**
+     * 鏌ユ壘鎸囧畾瀛楃涓叉槸鍚﹀寘鍚寚瀹氬瓧绗︿覆鍒楄〃涓殑浠绘剰涓�涓瓧绗︿覆鍚屾椂涓插拷鐣ュぇ灏忓啓
+     *
+     * @param cs                  鎸囧畾瀛楃涓�
+     * @param searchCharSequences 闇�瑕佹鏌ョ殑瀛楃涓叉暟缁�
+     * @return 鏄惁鍖呭惈浠绘剰涓�涓瓧绗︿覆
+     */
+    public static boolean containsAnyIgnoreCase(CharSequence cs, CharSequence... searchCharSequences) {
+        return StrUtil.containsAnyIgnoreCase(cs, searchCharSequences);
+    }
+
+    /**
+     * 椹煎嘲杞笅鍒掔嚎鍛藉悕
+     */
+    public static String toUnderScoreCase(String str) {
+        return StrUtil.toUnderlineCase(str);
+    }
+
+    /**
+     * 鏄惁鍖呭惈瀛楃涓�
+     *
+     * @param str  楠岃瘉瀛楃涓�
+     * @param strs 瀛楃涓茬粍
+     * @return 鍖呭惈杩斿洖true
+     */
+    public static boolean inStringIgnoreCase(String str, String... strs) {
+        return StrUtil.equalsAnyIgnoreCase(str, strs);
+    }
+
+    /**
+     * 灏嗕笅鍒掔嚎澶у啓鏂瑰紡鍛藉悕鐨勫瓧绗︿覆杞崲涓洪┘宄板紡銆傚鏋滆浆鎹㈠墠鐨勪笅鍒掔嚎澶у啓鏂瑰紡鍛藉悕鐨勫瓧绗︿覆涓虹┖锛屽垯杩斿洖绌哄瓧绗︿覆銆� 渚嬪锛欻ELLO_WORLD->HelloWorld
+     *
+     * @param name 杞崲鍓嶇殑涓嬪垝绾垮ぇ鍐欐柟寮忓懡鍚嶇殑瀛楃涓�
+     * @return 杞崲鍚庣殑椹煎嘲寮忓懡鍚嶇殑瀛楃涓�
+     */
+    public static String convertToCamelCase(String name) {
+        return StrUtil.upperFirst(StrUtil.toCamelCase(name));
+    }
+
+    /**
+     * 椹煎嘲寮忓懡鍚嶆硶 渚嬪锛歶ser_name->userName
+     */
+    public static String toCamelCase(String s) {
+        return StrUtil.toCamelCase(s);
+    }
+
+    /**
+     * 鏌ユ壘鎸囧畾瀛楃涓叉槸鍚﹀尮閰嶆寚瀹氬瓧绗︿覆鍒楄〃涓殑浠绘剰涓�涓瓧绗︿覆
+     *
+     * @param str  鎸囧畾瀛楃涓�
+     * @param strs 闇�瑕佹鏌ョ殑瀛楃涓叉暟缁�
+     * @return 鏄惁鍖归厤
+     */
+    public static boolean matches(String str, List<String> strs) {
+        if (isEmpty(str) || CollUtil.isEmpty(strs)) {
+            return false;
+        }
+        for (String pattern : strs) {
+            if (isMatch(pattern, str)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 鍒ゆ柇url鏄惁涓庤鍒欓厤缃�:
+     * ? 琛ㄧず鍗曚釜瀛楃;
+     * * 琛ㄧず涓�灞傝矾寰勫唴鐨勪换鎰忓瓧绗︿覆锛屼笉鍙法灞傜骇;
+     * ** 琛ㄧず浠绘剰灞傝矾寰�;
+     *
+     * @param pattern 鍖归厤瑙勫垯
+     * @param url     闇�瑕佸尮閰嶇殑url
+     */
+    public static boolean isMatch(String pattern, String url) {
+        AntPathMatcher matcher = new AntPathMatcher();
+        return matcher.match(pattern, url);
+    }
+
+    /**
+     * 鏁板瓧宸﹁竟琛ラ綈0锛屼娇涔嬭揪鍒版寚瀹氶暱搴︺�傛敞鎰忥紝濡傛灉鏁板瓧杞崲涓哄瓧绗︿覆鍚庯紝闀垮害澶т簬size锛屽垯鍙繚鐣� 鏈�鍚巗ize涓瓧绗︺��
+     *
+     * @param num  鏁板瓧瀵硅薄
+     * @param size 瀛楃涓叉寚瀹氶暱搴�
+     * @return 杩斿洖鏁板瓧鐨勫瓧绗︿覆鏍煎紡锛岃瀛楃涓蹭负鎸囧畾闀垮害銆�
+     */
+    public static String padl(final Number num, final int size) {
+        return padl(num.toString(), size, '0');
+    }
+
+    /**
+     * 瀛楃涓插乏琛ラ綈銆傚鏋滃師濮嬪瓧绗︿覆s闀垮害澶т簬size锛屽垯鍙繚鐣欐渶鍚巗ize涓瓧绗︺��
+     *
+     * @param s    鍘熷瀛楃涓�
+     * @param size 瀛楃涓叉寚瀹氶暱搴�
+     * @param c    鐢ㄤ簬琛ラ綈鐨勫瓧绗�
+     * @return 杩斿洖鎸囧畾闀垮害鐨勫瓧绗︿覆锛岀敱鍘熷瓧绗︿覆宸﹁ˉ榻愭垨鎴彇寰楀埌銆�
+     */
+    public static String padl(final String s, final int size, final char c) {
+        final StringBuilder sb = new StringBuilder(size);
+        if (s != null) {
+            final int len = s.length();
+            if (s.length() <= size) {
+                sb.append(String.valueOf(c).repeat(size - len));
+                sb.append(s);
+            } else {
+                return s.substring(len - size, len);
+            }
+        } else {
+            sb.append(String.valueOf(c).repeat(Math.max(0, size)));
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 鍒囧垎瀛楃涓�(鍒嗛殧绗﹂粯璁ら�楀彿)
+     *
+     * @param str 琚垏鍒嗙殑瀛楃涓�
+     * @return 鍒嗗壊鍚庣殑鏁版嵁鍒楄〃
+     */
+    public static List<String> splitList(String str) {
+        return splitTo(str, Convert::toStr);
+    }
+
+    /**
+     * 鍒囧垎瀛楃涓�
+     *
+     * @param str       琚垏鍒嗙殑瀛楃涓�
+     * @param separator 鍒嗛殧绗�
+     * @return 鍒嗗壊鍚庣殑鏁版嵁鍒楄〃
+     */
+    public static List<String> splitList(String str, String separator) {
+        return splitTo(str, separator, Convert::toStr);
+    }
+
+    /**
+     * 鍒囧垎瀛楃涓茶嚜瀹氫箟杞崲(鍒嗛殧绗﹂粯璁ら�楀彿)
+     *
+     * @param str    琚垏鍒嗙殑瀛楃涓�
+     * @param mapper 鑷畾涔夎浆鎹�
+     * @return 鍒嗗壊鍚庣殑鏁版嵁鍒楄〃
+     */
+    public static <T> List<T> splitTo(String str, Function<? super Object, T> mapper) {
+        return splitTo(str, SEPARATOR, mapper);
+    }
+
+    /**
+     * 鍒囧垎瀛楃涓茶嚜瀹氫箟杞崲
+     *
+     * @param str       琚垏鍒嗙殑瀛楃涓�
+     * @param separator 鍒嗛殧绗�
+     * @param mapper    鑷畾涔夎浆鎹�
+     * @return 鍒嗗壊鍚庣殑鏁版嵁鍒楄〃
+     */
+    public static <T> List<T> splitTo(String str, String separator, Function<? super Object, T> mapper) {
+        if (isBlank(str)) {
+            return new ArrayList<>(0);
+        }
+        return StrUtil.split(str, separator)
+            .stream()
+            .filter(Objects::nonNull)
+            .map(mapper)
+            .collect(Collectors.toList());
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/Threads.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/Threads.java
new file mode 100644
index 0000000..ae6cfa3
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/Threads.java
@@ -0,0 +1,75 @@
+package org.dromara.common.core.utils;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.concurrent.*;
+
+/**
+ * 绾跨▼鐩稿叧宸ュ叿绫�.
+ *
+ * @author ruoyi
+ */
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class Threads {
+
+    /**
+     * sleep绛夊緟,鍗曚綅涓烘绉�
+     */
+    public static void sleep(long milliseconds) {
+        try {
+            Thread.sleep(milliseconds);
+        } catch (InterruptedException e) {
+            return;
+        }
+    }
+
+    /**
+     * 鍋滄绾跨▼姹�
+     * 鍏堜娇鐢╯hutdown, 鍋滄鎺ユ敹鏂颁换鍔″苟灏濊瘯瀹屾垚鎵�鏈夊凡瀛樺湪浠诲姟.
+     * 濡傛灉瓒呮椂, 鍒欒皟鐢╯hutdownNow, 鍙栨秷鍦╳orkQueue涓璓ending鐨勪换鍔�,骞朵腑鏂墍鏈夐樆濉炲嚱鏁�.
+     * 濡傛灉浠嶇劧瓒呮檪锛屽墖寮峰埗閫�鍑�.
+     * 鍙﹀鍦╯hutdown鏃剁嚎绋嬫湰韬璋冪敤涓柇鍋氫簡澶勭悊.
+     */
+    public static void shutdownAndAwaitTermination(ExecutorService pool) {
+        if (pool != null && !pool.isShutdown()) {
+            pool.shutdown();
+            try {
+                if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
+                    pool.shutdownNow();
+                    if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
+                        log.info("Pool did not terminate");
+                    }
+                }
+            } catch (InterruptedException ie) {
+                pool.shutdownNow();
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    /**
+     * 鎵撳嵃绾跨▼寮傚父淇℃伅
+     */
+    public static void printException(Runnable r, Throwable t) {
+        if (t == null && r instanceof Future<?>) {
+            try {
+                Future<?> future = (Future<?>) r;
+                if (future.isDone()) {
+                    future.get();
+                }
+            } catch (CancellationException ce) {
+                t = ce;
+            } catch (ExecutionException ee) {
+                t = ee.getCause();
+            } catch (InterruptedException ie) {
+                Thread.currentThread().interrupt();
+            }
+        }
+        if (t != null) {
+            log.error(t.getMessage(), t);
+        }
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/TreeBuildUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/TreeBuildUtils.java
new file mode 100644
index 0000000..d0163e6
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/TreeBuildUtils.java
@@ -0,0 +1,35 @@
+package org.dromara.common.core.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.tree.Tree;
+import cn.hutool.core.lang.tree.TreeNodeConfig;
+import cn.hutool.core.lang.tree.TreeUtil;
+import cn.hutool.core.lang.tree.parser.NodeParser;
+import org.dromara.common.core.utils.reflect.ReflectUtils;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * 鎵╁睍 hutool TreeUtil 灏佽绯荤粺鏍戞瀯寤�
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class TreeBuildUtils extends TreeUtil {
+
+    /**
+     * 鏍规嵁鍓嶇瀹氬埗宸紓鍖栧瓧娈�
+     */
+    public static final TreeNodeConfig DEFAULT_CONFIG = TreeNodeConfig.DEFAULT_CONFIG.setNameKey("label");
+
+    public static <T, K> List<Tree<K>> build(List<T> list, NodeParser<T, K> nodeParser) {
+        if (CollUtil.isEmpty(list)) {
+            return null;
+        }
+        K k = ReflectUtils.invokeGetter(list.get(0), "parentId");
+        return TreeUtil.build(list, k, DEFAULT_CONFIG, nodeParser);
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ValidatorUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ValidatorUtils.java
new file mode 100644
index 0000000..f94f916
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ValidatorUtils.java
@@ -0,0 +1,28 @@
+package org.dromara.common.core.utils;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.ConstraintViolationException;
+import jakarta.validation.Validator;
+import java.util.Set;
+
+/**
+ * Validator 鏍¢獙妗嗘灦宸ュ叿
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ValidatorUtils {
+
+    private static final Validator VALID = SpringUtils.getBean(Validator.class);
+
+    public static <T> void validate(T object, Class<?>... groups) {
+        Set<ConstraintViolation<T>> validate = VALID.validate(object, groups);
+        if (!validate.isEmpty()) {
+            throw new ConstraintViolationException("鍙傛暟鏍¢獙寮傚父", validate);
+        }
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/FileUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/FileUtils.java
new file mode 100644
index 0000000..573b207
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/FileUtils.java
@@ -0,0 +1,43 @@
+package org.dromara.common.core.utils.file;
+
+import cn.hutool.core.io.FileUtil;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 鏂囦欢澶勭悊宸ュ叿绫�
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class FileUtils extends FileUtil {
+
+    /**
+     * 涓嬭浇鏂囦欢鍚嶉噸鏂扮紪鐮�
+     *
+     * @param response     鍝嶅簲瀵硅薄
+     * @param realFileName 鐪熷疄鏂囦欢鍚�
+     */
+    public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) {
+        String percentEncodedFileName = percentEncode(realFileName);
+        String contentDispositionValue = "attachment; filename=%s;filename*=utf-8''%s".formatted(percentEncodedFileName, percentEncodedFileName);
+        response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename");
+        response.setHeader("Content-disposition", contentDispositionValue);
+        response.setHeader("download-filename", percentEncodedFileName);
+    }
+
+    /**
+     * 鐧惧垎鍙风紪鐮佸伐鍏锋柟娉�
+     *
+     * @param s 闇�瑕佺櫨鍒嗗彿缂栫爜鐨勫瓧绗︿覆
+     * @return 鐧惧垎鍙风紪鐮佸悗鐨勫瓧绗︿覆
+     */
+    public static String percentEncode(String s) {
+        String encode = URLEncoder.encode(s, StandardCharsets.UTF_8);
+        return encode.replaceAll("\\+", "%20");
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/MimeTypeUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/MimeTypeUtils.java
new file mode 100644
index 0000000..23fa2cf
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/file/MimeTypeUtils.java
@@ -0,0 +1,40 @@
+package org.dromara.common.core.utils.file;
+
+/**
+ * 濯掍綋绫诲瀷宸ュ叿绫�
+ *
+ * @author ruoyi
+ */
+public class MimeTypeUtils {
+    public static final String IMAGE_PNG = "image/png";
+
+    public static final String IMAGE_JPG = "image/jpg";
+
+    public static final String IMAGE_JPEG = "image/jpeg";
+
+    public static final String IMAGE_BMP = "image/bmp";
+
+    public static final String IMAGE_GIF = "image/gif";
+
+    public static final String[] IMAGE_EXTENSION = {"bmp", "gif", "jpg", "jpeg", "png"};
+
+    public static final String[] FLASH_EXTENSION = {"swf", "flv"};
+
+    public static final String[] MEDIA_EXTENSION = {"swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg",
+        "asf", "rm", "rmvb"};
+
+    public static final String[] VIDEO_EXTENSION = {"mp4", "avi", "rmvb"};
+
+    public static final String[] DEFAULT_ALLOWED_EXTENSION = {
+        // 鍥剧墖
+        "bmp", "gif", "jpg", "jpeg", "png",
+        // word excel powerpoint
+        "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
+        // 鍘嬬缉鏂囦欢
+        "rar", "zip", "gz", "bz2",
+        // 瑙嗛鏍煎紡
+        "mp4", "avi", "rmvb",
+        // pdf
+        "pdf"};
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/AddressUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/AddressUtils.java
new file mode 100644
index 0000000..3f7cd57
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/AddressUtils.java
@@ -0,0 +1,33 @@
+package org.dromara.common.core.utils.ip;
+
+import cn.hutool.core.net.NetUtil;
+import cn.hutool.http.HtmlUtil;
+import org.dromara.common.core.utils.StringUtils;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 鑾峰彇鍦板潃绫�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class AddressUtils {
+
+    // 鏈煡鍦板潃
+    public static final String UNKNOWN = "XX XX";
+
+    public static String getRealAddressByIP(String ip) {
+        if (StringUtils.isBlank(ip)) {
+            return UNKNOWN;
+        }
+        // 鍐呯綉涓嶆煡璇�
+        ip = StringUtils.contains(ip, "0:0:0:0:0:0:0:1") ? "127.0.0.1" : HtmlUtil.cleanHtmlTag(ip);
+        if (NetUtil.isInnerIP(ip)) {
+            return "鍐呯綉IP";
+        }
+        return RegionUtils.getCityInfo(ip);
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/RegionUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/RegionUtils.java
new file mode 100644
index 0000000..6e2a44e
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/ip/RegionUtils.java
@@ -0,0 +1,67 @@
+package org.dromara.common.core.utils.ip;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.resource.ClassPathResource;
+import cn.hutool.core.util.ObjectUtil;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.file.FileUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.lionsoul.ip2region.xdb.Searcher;
+
+import java.io.File;
+
+/**
+ * 鏍规嵁ip鍦板潃瀹氫綅宸ュ叿绫伙紝绂荤嚎鏂瑰紡
+ * 鍙傝�冨湴鍧�锛�<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">闆嗘垚 ip2region 瀹炵幇绂荤嚎IP鍦板潃瀹氫綅搴�</a>
+ *
+ * @author lishuyan
+ */
+@Slf4j
+public class RegionUtils {
+
+    private static final Searcher SEARCHER;
+
+    static {
+        String fileName = "/ip2region.xdb";
+        File existFile = FileUtils.file(FileUtil.getTmpDir() + FileUtil.FILE_SEPARATOR + fileName);
+        if (!FileUtils.exist(existFile)) {
+            ClassPathResource fileStream = new ClassPathResource(fileName);
+            if (ObjectUtil.isEmpty(fileStream.getStream())) {
+                throw new ServiceException("RegionUtils鍒濆鍖栧け璐ワ紝鍘熷洜锛欼P鍦板潃搴撴暟鎹笉瀛樺湪锛�");
+            }
+            FileUtils.writeFromStream(fileStream.getStream(), existFile);
+        }
+
+        String dbPath = existFile.getPath();
+
+        // 1銆佷粠 dbPath 鍔犺浇鏁翠釜 xdb 鍒板唴瀛樸��
+        byte[] cBuff;
+        try {
+            cBuff = Searcher.loadContentFromFile(dbPath);
+        } catch (Exception e) {
+            throw new ServiceException("RegionUtils鍒濆鍖栧け璐ワ紝鍘熷洜锛氫粠ip2region.xdb鏂囦欢鍔犺浇鍐呭澶辫触锛�" + e.getMessage());
+        }
+        // 2銆佷娇鐢ㄤ笂杩扮殑 cBuff 鍒涘缓涓�涓畬鍏ㄥ熀浜庡唴瀛樼殑鏌ヨ瀵硅薄銆�
+        try {
+            SEARCHER = Searcher.newWithBuffer(cBuff);
+        } catch (Exception e) {
+            throw new ServiceException("RegionUtils鍒濆鍖栧け璐ワ紝鍘熷洜锛�" + e.getMessage());
+        }
+    }
+
+    /**
+     * 鏍规嵁IP鍦板潃绂荤嚎鑾峰彇鍩庡競
+     */
+    public static String getCityInfo(String ip) {
+        try {
+            ip = ip.trim();
+            // 3銆佹墽琛屾煡璇�
+            String region = SEARCHER.search(ip);
+            return region.replace("0|", "").replace("|0", "");
+        } catch (Exception e) {
+            log.error("IP鍦板潃绂荤嚎鑾峰彇鍩庡競寮傚父 {}", ip);
+            return "鏈煡";
+        }
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/reflect/ReflectUtils.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/reflect/ReflectUtils.java
new file mode 100644
index 0000000..367e8c9
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/reflect/ReflectUtils.java
@@ -0,0 +1,56 @@
+package org.dromara.common.core.utils.reflect;
+
+import cn.hutool.core.util.ReflectUtil;
+import org.dromara.common.core.utils.StringUtils;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.lang.reflect.Method;
+
+/**
+ * 鍙嶅皠宸ュ叿绫�. 鎻愪緵璋冪敤getter/setter鏂规硶, 璁块棶绉佹湁鍙橀噺, 璋冪敤绉佹湁鏂规硶, 鑾峰彇娉涘瀷绫诲瀷Class, 琚獳OP杩囩殑鐪熷疄绫荤瓑宸ュ叿鍑芥暟.
+ *
+ * @author Lion Li
+ */
+@SuppressWarnings("rawtypes")
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ReflectUtils extends ReflectUtil {
+
+    private static final String SETTER_PREFIX = "set";
+
+    private static final String GETTER_PREFIX = "get";
+
+    /**
+     * 璋冪敤Getter鏂规硶.
+     * 鏀寔澶氱骇锛屽锛氬璞″悕.瀵硅薄鍚�.鏂规硶
+     */
+    @SuppressWarnings("unchecked")
+    public static <E> E invokeGetter(Object obj, String propertyName) {
+        Object object = obj;
+        for (String name : StringUtils.split(propertyName, ".")) {
+            String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name);
+            object = invoke(object, getterMethodName);
+        }
+        return (E) object;
+    }
+
+    /**
+     * 璋冪敤Setter鏂规硶, 浠呭尮閰嶆柟娉曞悕銆�
+     * 鏀寔澶氱骇锛屽锛氬璞″悕.瀵硅薄鍚�.鏂规硶
+     */
+    public static <E> void invokeSetter(Object obj, String propertyName, E value) {
+        Object object = obj;
+        String[] names = StringUtils.split(propertyName, ".");
+        for (int i = 0; i < names.length; i++) {
+            if (i < names.length - 1) {
+                String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]);
+                object = invoke(object, getterMethodName);
+            } else {
+                String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]);
+                Method method = getMethodByName(object.getClass(), setterMethodName);
+                invoke(object, method, value);
+            }
+        }
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/sql/SqlUtil.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/sql/SqlUtil.java
new file mode 100644
index 0000000..3e109b2
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/sql/SqlUtil.java
@@ -0,0 +1,56 @@
+package org.dromara.common.core.utils.sql;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.utils.StringUtils;
+
+/**
+ * sql鎿嶄綔宸ュ叿绫�
+ *
+ * @author ruoyi
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class SqlUtil {
+
+    /**
+     * 瀹氫箟甯哥敤鐨� sql鍏抽敭瀛�
+     */
+    public static final String SQL_REGEX = "select |insert |delete |update |drop |count |exec |chr |mid |master |truncate |char |and |declare ";
+
+    /**
+     * 浠呮敮鎸佸瓧姣嶃�佹暟瀛椼�佷笅鍒掔嚎銆佺┖鏍笺�侀�楀彿銆佸皬鏁扮偣锛堟敮鎸佸涓瓧娈垫帓搴忥級
+     */
+    public static final String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+";
+
+    /**
+     * 妫�鏌ュ瓧绗︼紝闃叉娉ㄥ叆缁曡繃
+     */
+    public static String escapeOrderBySql(String value) {
+        if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) {
+            throw new IllegalArgumentException("鍙傛暟涓嶇鍚堣鑼冿紝涓嶈兘杩涜鏌ヨ");
+        }
+        return value;
+    }
+
+    /**
+     * 楠岃瘉 order by 璇硶鏄惁绗﹀悎瑙勮寖
+     */
+    public static boolean isValidOrderBySql(String value) {
+        return value.matches(SQL_PATTERN);
+    }
+
+    /**
+     * SQL鍏抽敭瀛楁鏌�
+     */
+    public static void filterKeyword(String value) {
+        if (StringUtils.isEmpty(value)) {
+            return;
+        }
+        String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|");
+        for (String sqlKeyword : sqlKeywords) {
+            if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1) {
+                throw new IllegalArgumentException("鍙傛暟瀛樺湪SQL娉ㄥ叆椋庨櫓");
+            }
+        }
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/AddGroup.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/AddGroup.java
new file mode 100644
index 0000000..0275899
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/AddGroup.java
@@ -0,0 +1,9 @@
+package org.dromara.common.core.validate;
+
+/**
+ * 鏍¢獙鍒嗙粍 add
+ *
+ * @author Lion Li
+ */
+public interface AddGroup {
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/EditGroup.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/EditGroup.java
new file mode 100644
index 0000000..77c5040
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/EditGroup.java
@@ -0,0 +1,9 @@
+package org.dromara.common.core.validate;
+
+/**
+ * 鏍¢獙鍒嗙粍 edit
+ *
+ * @author Lion Li
+ */
+public interface EditGroup {
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/QueryGroup.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/QueryGroup.java
new file mode 100644
index 0000000..02a0ac2
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/QueryGroup.java
@@ -0,0 +1,9 @@
+package org.dromara.common.core.validate;
+
+/**
+ * 鏍¢獙鍒嗙粍 query
+ *
+ * @author Lion Li
+ */
+public interface QueryGroup {
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/xss/Xss.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/xss/Xss.java
new file mode 100644
index 0000000..eed495f
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/xss/Xss.java
@@ -0,0 +1,26 @@
+package org.dromara.common.core.xss;
+
+import jakarta.validation.Constraint;
+import jakarta.validation.Payload;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 鑷畾涔墄ss鏍¢獙娉ㄨВ
+ *
+ * @author Lion Li
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(value = {ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
+@Constraint(validatedBy = {XssValidator.class})
+public @interface Xss {
+
+    String message() default "涓嶅厑璁镐换浣曡剼鏈繍琛�";
+
+    Class<?>[] groups() default {};
+
+    Class<? extends Payload>[] payload() default {};
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/xss/XssValidator.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/xss/XssValidator.java
new file mode 100644
index 0000000..9c32563
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/xss/XssValidator.java
@@ -0,0 +1,21 @@
+package org.dromara.common.core.xss;
+
+import cn.hutool.core.util.ReUtil;
+import cn.hutool.http.HtmlUtil;
+
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+
+/**
+ * 鑷畾涔墄ss鏍¢獙娉ㄨВ瀹炵幇
+ *
+ * @author Lion Li
+ */
+public class XssValidator implements ConstraintValidator<Xss, String> {
+
+    @Override
+    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
+        return !ReUtil.contains(HtmlUtil.RE_HTML_MARK, value);
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..ddd3025
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1,4 @@
+org.dromara.common.core.utils.SpringUtils
+org.dromara.common.core.config.ApplicationConfig
+org.dromara.common.core.config.ValidatorConfig
+org.dromara.common.core.config.AsyncConfig
diff --git a/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages.properties b/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages.properties
new file mode 100644
index 0000000..cce11c8
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages.properties
@@ -0,0 +1,61 @@
+#閿欒娑堟伅
+not.null=* 蹇呴』濉啓
+user.jcaptcha.error=楠岃瘉鐮侀敊璇�
+user.jcaptcha.expire=楠岃瘉鐮佸凡澶辨晥
+user.not.exists=瀵逛笉璧�, 鎮ㄧ殑璐﹀彿锛歿0} 涓嶅瓨鍦�.
+user.password.not.match=鐢ㄦ埛涓嶅瓨鍦�/瀵嗙爜閿欒
+user.password.retry.limit.count=瀵嗙爜杈撳叆閿欒{0}娆�
+user.password.retry.limit.exceed=瀵嗙爜杈撳叆閿欒{0}娆★紝甯愭埛閿佸畾{1}鍒嗛挓
+user.password.delete=瀵逛笉璧凤紝鎮ㄧ殑璐﹀彿锛歿0} 宸茶鍒犻櫎
+user.blocked=瀵逛笉璧凤紝鎮ㄧ殑璐﹀彿锛歿0} 宸茬鐢紝璇疯仈绯荤鐞嗗憳
+role.blocked=瑙掕壊宸插皝绂侊紝璇疯仈绯荤鐞嗗憳
+user.logout.success=閫�鍑烘垚鍔�
+length.not.valid=闀垮害蹇呴』鍦▄min}鍒皗max}涓瓧绗︿箣闂�
+user.username.not.blank=鐢ㄦ埛鍚嶄笉鑳戒负绌�
+user.username.not.valid=* 2鍒�20涓眽瀛椼�佸瓧姣嶃�佹暟瀛楁垨涓嬪垝绾跨粍鎴愶紝涓斿繀椤讳互闈炴暟瀛楀紑澶�
+user.username.length.valid=璐︽埛闀垮害蹇呴』鍦▄min}鍒皗max}涓瓧绗︿箣闂�
+user.password.not.blank=鐢ㄦ埛瀵嗙爜涓嶈兘涓虹┖
+user.password.length.valid=鐢ㄦ埛瀵嗙爜闀垮害蹇呴』鍦▄min}鍒皗max}涓瓧绗︿箣闂�
+user.password.not.valid=* 5-50涓瓧绗�
+user.email.not.valid=閭鏍煎紡閿欒
+user.email.not.blank=閭涓嶈兘涓虹┖
+user.phonenumber.not.blank=鐢ㄦ埛鎵嬫満鍙蜂笉鑳戒负绌�
+user.mobile.phone.number.not.valid=鎵嬫満鍙锋牸寮忛敊璇�
+user.login.success=鐧诲綍鎴愬姛
+user.register.success=娉ㄥ唽鎴愬姛
+user.register.save.error=淇濆瓨鐢ㄦ埛 {0} 澶辫触锛屾敞鍐岃处鍙峰凡瀛樺湪
+user.register.error=娉ㄥ唽澶辫触锛岃鑱旂郴绯荤粺绠$悊浜哄憳
+user.notfound=璇烽噸鏂扮櫥褰�
+user.forcelogout=绠$悊鍛樺己鍒堕��鍑猴紝璇烽噸鏂扮櫥褰�
+user.unknown.error=鏈煡閿欒锛岃閲嶆柊鐧诲綍
+auth.grant.type.error=璁よ瘉鏉冮檺绫诲瀷閿欒
+auth.grant.type.blocked=璁よ瘉鏉冮檺绫诲瀷宸茬鐢�
+auth.grant.type.not.blank=璁よ瘉鏉冮檺绫诲瀷涓嶈兘涓虹┖
+auth.clientid.not.blank=璁よ瘉瀹㈡埛绔痠d涓嶈兘涓虹┖
+##鏂囦欢涓婁紶娑堟伅
+upload.exceed.maxSize=涓婁紶鐨勬枃浠跺ぇ灏忚秴鍑洪檺鍒剁殑鏂囦欢澶у皬锛�<br/>鍏佽鐨勬枃浠舵渶澶уぇ灏忔槸锛歿0}MB锛�
+upload.filename.exceed.length=涓婁紶鐨勬枃浠跺悕鏈�闀縶0}涓瓧绗�
+##鏉冮檺
+no.permission=鎮ㄦ病鏈夋暟鎹殑鏉冮檺锛岃鑱旂郴绠$悊鍛樻坊鍔犳潈闄� [{0}]
+no.create.permission=鎮ㄦ病鏈夊垱寤烘暟鎹殑鏉冮檺锛岃鑱旂郴绠$悊鍛樻坊鍔犳潈闄� [{0}]
+no.update.permission=鎮ㄦ病鏈変慨鏀规暟鎹殑鏉冮檺锛岃鑱旂郴绠$悊鍛樻坊鍔犳潈闄� [{0}]
+no.delete.permission=鎮ㄦ病鏈夊垹闄ゆ暟鎹殑鏉冮檺锛岃鑱旂郴绠$悊鍛樻坊鍔犳潈闄� [{0}]
+no.export.permission=鎮ㄦ病鏈夊鍑烘暟鎹殑鏉冮檺锛岃鑱旂郴绠$悊鍛樻坊鍔犳潈闄� [{0}]
+no.view.permission=鎮ㄦ病鏈夋煡鐪嬫暟鎹殑鏉冮檺锛岃鑱旂郴绠$悊鍛樻坊鍔犳潈闄� [{0}]
+repeat.submit.message=涓嶅厑璁搁噸澶嶆彁浜わ紝璇风◢鍊欏啀璇�
+rate.limiter.message=璁块棶杩囦簬棰戠箒锛岃绋嶅�欏啀璇�
+sms.code.not.blank=鐭俊楠岃瘉鐮佷笉鑳戒负绌�
+sms.code.retry.limit.count=鐭俊楠岃瘉鐮佽緭鍏ラ敊璇瘂0}娆�
+sms.code.retry.limit.exceed=鐭俊楠岃瘉鐮佽緭鍏ラ敊璇瘂0}娆★紝甯愭埛閿佸畾{1}鍒嗛挓
+email.code.not.blank=閭楠岃瘉鐮佷笉鑳戒负绌�
+email.code.retry.limit.count=閭楠岃瘉鐮佽緭鍏ラ敊璇瘂0}娆�
+email.code.retry.limit.exceed=閭楠岃瘉鐮佽緭鍏ラ敊璇瘂0}娆★紝甯愭埛閿佸畾{1}鍒嗛挓
+xcx.code.not.blank=灏忕▼搴廩code]涓嶈兘涓虹┖
+social.source.not.blank=绗笁鏂圭櫥褰曞钩鍙癧source]涓嶈兘涓虹┖
+social.code.not.blank=绗笁鏂圭櫥褰曞钩鍙癧code]涓嶈兘涓虹┖
+social.state.not.blank=绗笁鏂圭櫥褰曞钩鍙癧state]涓嶈兘涓虹┖
+##绉熸埛
+tenant.number.not.blank=绉熸埛缂栧彿涓嶈兘涓虹┖
+tenant.not.exists=瀵逛笉璧�, 鎮ㄧ殑绉熸埛涓嶅瓨鍦紝璇疯仈绯荤鐞嗗憳
+tenant.blocked=瀵逛笉璧凤紝鎮ㄧ殑绉熸埛宸茬鐢紝璇疯仈绯荤鐞嗗憳
+tenant.expired=瀵逛笉璧凤紝鎮ㄧ殑绉熸埛宸茶繃鏈燂紝璇疯仈绯荤鐞嗗憳
diff --git a/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_en_US.properties b/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_en_US.properties
new file mode 100644
index 0000000..f948c4a
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_en_US.properties
@@ -0,0 +1,61 @@
+#閿欒娑堟伅
+not.null=* Required fill in
+user.jcaptcha.error=Captcha error
+user.jcaptcha.expire=Captcha invalid
+user.not.exists=Sorry, your account: {0} does not exist
+user.password.not.match=User does not exist/Password error
+user.password.retry.limit.count=Password input error {0} times
+user.password.retry.limit.exceed=Password input error {0} times, account locked for {1} minutes
+user.password.delete=Sorry, your account锛歿0} has been deleted
+user.blocked=Sorry, your account: {0} has been disabled. Please contact the administrator
+role.blocked=Role disabled锛宲lease contact administrators
+user.logout.success=Exit successful
+length.not.valid=The length must be between {min} and {max} characters
+user.username.not.blank=Username cannot be blank
+user.username.not.valid=* 2 to 20 chinese characters, letters, numbers or underscores, and must start with a non number
+user.username.length.valid=Account length must be between {min} and {max} characters
+user.password.not.blank=Password cannot be empty
+user.password.length.valid=Password length must be between {min} and {max} characters
+user.password.not.valid=* 5-50 characters
+user.email.not.valid=Mailbox format error
+user.email.not.blank=Mailbox cannot be blank
+user.phonenumber.not.blank=Phone number cannot be blank
+user.mobile.phone.number.not.valid=Phone number format error
+user.login.success=Login successful
+user.register.success=Register successful
+user.register.save.error=Failed to save user {0}, The registered account already exists
+user.register.error=Register failed, please contact system administrator
+user.notfound=Please login again
+user.forcelogout=The administrator is forced to exit锛宲lease login again
+user.unknown.error=Unknown error, please login again
+auth.grant.type.error=Auth grant type error
+auth.grant.type.blocked=Auth grant type disabled
+auth.grant.type.not.blank=Auth grant type cannot be blank
+auth.clientid.not.blank=Auth clientid cannot be blank
+##鏂囦欢涓婁紶娑堟伅
+upload.exceed.maxSize=The uploaded file size exceeds the limit file size锛�<br/>the maximum allowed file size is锛歿0}MB锛�
+upload.filename.exceed.length=The maximum length of uploaded file name is {0} characters
+##鏉冮檺
+no.permission=You do not have permission to the data锛宲lease contact your administrator to add permissions [{0}]
+no.create.permission=You do not have permission to create data锛宲lease contact your administrator to add permissions [{0}]
+no.update.permission=You do not have permission to modify data锛宲lease contact your administrator to add permissions [{0}]
+no.delete.permission=You do not have permission to delete data锛宲lease contact your administrator to add permissions [{0}]
+no.export.permission=You do not have permission to export data锛宲lease contact your administrator to add permissions [{0}]
+no.view.permission=You do not have permission to view data锛宲lease contact your administrator to add permissions [{0}]
+repeat.submit.message=Repeat submit is not allowed, please try again later
+rate.limiter.message=Visit too frequently, please try again later
+sms.code.not.blank=Sms code cannot be blank
+sms.code.retry.limit.count=Sms code input error {0} times
+sms.code.retry.limit.exceed=Sms code input error {0} times, account locked for {1} minutes
+email.code.not.blank=Email code cannot be blank
+email.code.retry.limit.count=Email code input error {0} times
+email.code.retry.limit.exceed=Email code input error {0} times, account locked for {1} minutes
+xcx.code.not.blank=Mini program [code] cannot be blank
+social.source.not.blank=Social login platform [source] cannot be blank
+social.code.not.blank=Social login platform [code] cannot be blank
+social.state.not.blank=Social login platform [state] cannot be blank
+##绉熸埛
+tenant.number.not.blank=Tenant number cannot be blank
+tenant.not.exists=Sorry, your tenant does not exist. Please contact the administrator
+tenant.blocked=Sorry, your tenant is disabled. Please contact the administrator
+tenant.expired=Sorry, your tenant has expired. Please contact the administrator.
diff --git a/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_zh_CN.properties b/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_zh_CN.properties
new file mode 100644
index 0000000..cce11c8
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_zh_CN.properties
@@ -0,0 +1,61 @@
+#閿欒娑堟伅
+not.null=* 蹇呴』濉啓
+user.jcaptcha.error=楠岃瘉鐮侀敊璇�
+user.jcaptcha.expire=楠岃瘉鐮佸凡澶辨晥
+user.not.exists=瀵逛笉璧�, 鎮ㄧ殑璐﹀彿锛歿0} 涓嶅瓨鍦�.
+user.password.not.match=鐢ㄦ埛涓嶅瓨鍦�/瀵嗙爜閿欒
+user.password.retry.limit.count=瀵嗙爜杈撳叆閿欒{0}娆�
+user.password.retry.limit.exceed=瀵嗙爜杈撳叆閿欒{0}娆★紝甯愭埛閿佸畾{1}鍒嗛挓
+user.password.delete=瀵逛笉璧凤紝鎮ㄧ殑璐﹀彿锛歿0} 宸茶鍒犻櫎
+user.blocked=瀵逛笉璧凤紝鎮ㄧ殑璐﹀彿锛歿0} 宸茬鐢紝璇疯仈绯荤鐞嗗憳
+role.blocked=瑙掕壊宸插皝绂侊紝璇疯仈绯荤鐞嗗憳
+user.logout.success=閫�鍑烘垚鍔�
+length.not.valid=闀垮害蹇呴』鍦▄min}鍒皗max}涓瓧绗︿箣闂�
+user.username.not.blank=鐢ㄦ埛鍚嶄笉鑳戒负绌�
+user.username.not.valid=* 2鍒�20涓眽瀛椼�佸瓧姣嶃�佹暟瀛楁垨涓嬪垝绾跨粍鎴愶紝涓斿繀椤讳互闈炴暟瀛楀紑澶�
+user.username.length.valid=璐︽埛闀垮害蹇呴』鍦▄min}鍒皗max}涓瓧绗︿箣闂�
+user.password.not.blank=鐢ㄦ埛瀵嗙爜涓嶈兘涓虹┖
+user.password.length.valid=鐢ㄦ埛瀵嗙爜闀垮害蹇呴』鍦▄min}鍒皗max}涓瓧绗︿箣闂�
+user.password.not.valid=* 5-50涓瓧绗�
+user.email.not.valid=閭鏍煎紡閿欒
+user.email.not.blank=閭涓嶈兘涓虹┖
+user.phonenumber.not.blank=鐢ㄦ埛鎵嬫満鍙蜂笉鑳戒负绌�
+user.mobile.phone.number.not.valid=鎵嬫満鍙锋牸寮忛敊璇�
+user.login.success=鐧诲綍鎴愬姛
+user.register.success=娉ㄥ唽鎴愬姛
+user.register.save.error=淇濆瓨鐢ㄦ埛 {0} 澶辫触锛屾敞鍐岃处鍙峰凡瀛樺湪
+user.register.error=娉ㄥ唽澶辫触锛岃鑱旂郴绯荤粺绠$悊浜哄憳
+user.notfound=璇烽噸鏂扮櫥褰�
+user.forcelogout=绠$悊鍛樺己鍒堕��鍑猴紝璇烽噸鏂扮櫥褰�
+user.unknown.error=鏈煡閿欒锛岃閲嶆柊鐧诲綍
+auth.grant.type.error=璁よ瘉鏉冮檺绫诲瀷閿欒
+auth.grant.type.blocked=璁よ瘉鏉冮檺绫诲瀷宸茬鐢�
+auth.grant.type.not.blank=璁よ瘉鏉冮檺绫诲瀷涓嶈兘涓虹┖
+auth.clientid.not.blank=璁よ瘉瀹㈡埛绔痠d涓嶈兘涓虹┖
+##鏂囦欢涓婁紶娑堟伅
+upload.exceed.maxSize=涓婁紶鐨勬枃浠跺ぇ灏忚秴鍑洪檺鍒剁殑鏂囦欢澶у皬锛�<br/>鍏佽鐨勬枃浠舵渶澶уぇ灏忔槸锛歿0}MB锛�
+upload.filename.exceed.length=涓婁紶鐨勬枃浠跺悕鏈�闀縶0}涓瓧绗�
+##鏉冮檺
+no.permission=鎮ㄦ病鏈夋暟鎹殑鏉冮檺锛岃鑱旂郴绠$悊鍛樻坊鍔犳潈闄� [{0}]
+no.create.permission=鎮ㄦ病鏈夊垱寤烘暟鎹殑鏉冮檺锛岃鑱旂郴绠$悊鍛樻坊鍔犳潈闄� [{0}]
+no.update.permission=鎮ㄦ病鏈変慨鏀规暟鎹殑鏉冮檺锛岃鑱旂郴绠$悊鍛樻坊鍔犳潈闄� [{0}]
+no.delete.permission=鎮ㄦ病鏈夊垹闄ゆ暟鎹殑鏉冮檺锛岃鑱旂郴绠$悊鍛樻坊鍔犳潈闄� [{0}]
+no.export.permission=鎮ㄦ病鏈夊鍑烘暟鎹殑鏉冮檺锛岃鑱旂郴绠$悊鍛樻坊鍔犳潈闄� [{0}]
+no.view.permission=鎮ㄦ病鏈夋煡鐪嬫暟鎹殑鏉冮檺锛岃鑱旂郴绠$悊鍛樻坊鍔犳潈闄� [{0}]
+repeat.submit.message=涓嶅厑璁搁噸澶嶆彁浜わ紝璇风◢鍊欏啀璇�
+rate.limiter.message=璁块棶杩囦簬棰戠箒锛岃绋嶅�欏啀璇�
+sms.code.not.blank=鐭俊楠岃瘉鐮佷笉鑳戒负绌�
+sms.code.retry.limit.count=鐭俊楠岃瘉鐮佽緭鍏ラ敊璇瘂0}娆�
+sms.code.retry.limit.exceed=鐭俊楠岃瘉鐮佽緭鍏ラ敊璇瘂0}娆★紝甯愭埛閿佸畾{1}鍒嗛挓
+email.code.not.blank=閭楠岃瘉鐮佷笉鑳戒负绌�
+email.code.retry.limit.count=閭楠岃瘉鐮佽緭鍏ラ敊璇瘂0}娆�
+email.code.retry.limit.exceed=閭楠岃瘉鐮佽緭鍏ラ敊璇瘂0}娆★紝甯愭埛閿佸畾{1}鍒嗛挓
+xcx.code.not.blank=灏忕▼搴廩code]涓嶈兘涓虹┖
+social.source.not.blank=绗笁鏂圭櫥褰曞钩鍙癧source]涓嶈兘涓虹┖
+social.code.not.blank=绗笁鏂圭櫥褰曞钩鍙癧code]涓嶈兘涓虹┖
+social.state.not.blank=绗笁鏂圭櫥褰曞钩鍙癧state]涓嶈兘涓虹┖
+##绉熸埛
+tenant.number.not.blank=绉熸埛缂栧彿涓嶈兘涓虹┖
+tenant.not.exists=瀵逛笉璧�, 鎮ㄧ殑绉熸埛涓嶅瓨鍦紝璇疯仈绯荤鐞嗗憳
+tenant.blocked=瀵逛笉璧凤紝鎮ㄧ殑绉熸埛宸茬鐢紝璇疯仈绯荤鐞嗗憳
+tenant.expired=瀵逛笉璧凤紝鎮ㄧ殑绉熸埛宸茶繃鏈燂紝璇疯仈绯荤鐞嗗憳
diff --git a/ruoyi-common/ruoyi-common-core/src/main/resources/ip2region.xdb b/ruoyi-common/ruoyi-common-core/src/main/resources/ip2region.xdb
new file mode 100644
index 0000000..31f96a1
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/resources/ip2region.xdb
Binary files differ
diff --git a/ruoyi-common/ruoyi-common-dict/pom.xml b/ruoyi-common/ruoyi-common-dict/pom.xml
new file mode 100644
index 0000000..6cf4efc
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dict/pom.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-dict</artifactId>
+
+    <description>
+        ruoyi-common-dict 瀛楀吀
+    </description>
+
+    <dependencies>
+
+        <!-- RuoYi Common Security -->
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-redis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-api-system</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-spring-boot-starter</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-core</artifactId>
+        </dependency>
+
+    </dependencies>
+</project>
diff --git a/ruoyi-common/ruoyi-common-dict/src/main/java/org/dromara/common/dict/service/impl/DictServiceImpl.java b/ruoyi-common/ruoyi-common-dict/src/main/java/org/dromara/common/dict/service/impl/DictServiceImpl.java
new file mode 100644
index 0000000..2de6c55
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dict/src/main/java/org/dromara/common/dict/service/impl/DictServiceImpl.java
@@ -0,0 +1,92 @@
+package org.dromara.common.dict.service.impl;
+
+import cn.dev33.satoken.context.SaHolder;
+import cn.hutool.core.util.ObjectUtil;
+import org.dromara.common.core.constant.CacheConstants;
+import org.dromara.common.core.service.DictService;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.system.api.RemoteDictService;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.system.api.domain.vo.RemoteDictDataVo;
+import org.springframework.stereotype.Service;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 瀛楀吀鏈嶅姟鏈嶅姟
+ *
+ * @author Lion Li
+ */
+@Service
+public class DictServiceImpl implements DictService {
+
+    @DubboReference
+    private RemoteDictService remoteDictService;
+
+    /**
+     * 鏍规嵁瀛楀吀绫诲瀷鍜屽瓧鍏稿�艰幏鍙栧瓧鍏告爣绛�
+     *
+     * @param dictType  瀛楀吀绫诲瀷
+     * @param dictValue 瀛楀吀鍊�
+     * @param separator 鍒嗛殧绗�
+     * @return 瀛楀吀鏍囩
+     */
+    @SuppressWarnings("unchecked cast")
+    @Override
+    public String getDictLabel(String dictType, String dictValue, String separator) {
+        // 浼樺厛浠庢湰鍦扮紦瀛樿幏鍙�
+        List<RemoteDictDataVo> datas = (List<RemoteDictDataVo>) SaHolder.getStorage().get(CacheConstants.SYS_DICT_KEY + dictType);
+        if (ObjectUtil.isNull(datas)) {
+            datas = remoteDictService.selectDictDataByType(dictType);
+            SaHolder.getStorage().set(CacheConstants.SYS_DICT_KEY + dictType, datas);
+        }
+
+        Map<String, String> map = StreamUtils.toMap(datas, RemoteDictDataVo::getDictValue, RemoteDictDataVo::getDictLabel);
+        if (StringUtils.containsAny(dictValue, separator)) {
+            return Arrays.stream(dictValue.split(separator))
+                .map(v -> map.getOrDefault(v, StringUtils.EMPTY))
+                .collect(Collectors.joining(separator));
+        } else {
+            return map.getOrDefault(dictValue, StringUtils.EMPTY);
+        }
+    }
+
+    /**
+     * 鏍规嵁瀛楀吀绫诲瀷鍜屽瓧鍏告爣绛捐幏鍙栧瓧鍏稿��
+     *
+     * @param dictType  瀛楀吀绫诲瀷
+     * @param dictLabel 瀛楀吀鏍囩
+     * @param separator 鍒嗛殧绗�
+     * @return 瀛楀吀鍊�
+     */
+    @SuppressWarnings("unchecked cast")
+    @Override
+    public String getDictValue(String dictType, String dictLabel, String separator) {
+        // 浼樺厛浠庢湰鍦扮紦瀛樿幏鍙�
+        List<RemoteDictDataVo> datas = (List<RemoteDictDataVo>) SaHolder.getStorage().get(CacheConstants.SYS_DICT_KEY + dictType);
+        if (ObjectUtil.isNull(datas)) {
+            datas = remoteDictService.selectDictDataByType(dictType);
+            SaHolder.getStorage().set(CacheConstants.SYS_DICT_KEY + dictType, datas);
+        }
+
+        Map<String, String> map = StreamUtils.toMap(datas, RemoteDictDataVo::getDictLabel, RemoteDictDataVo::getDictValue);
+        if (StringUtils.containsAny(dictLabel, separator)) {
+            return Arrays.stream(dictLabel.split(separator))
+                .map(l -> map.getOrDefault(l, StringUtils.EMPTY))
+                .collect(Collectors.joining(separator));
+        } else {
+            return map.getOrDefault(dictLabel, StringUtils.EMPTY);
+        }
+    }
+
+    @Override
+    public Map<String, String> getAllDictByDictType(String dictType) {
+        List<RemoteDictDataVo> list = remoteDictService.selectDictDataByType(dictType);
+        return StreamUtils.toMap(list, RemoteDictDataVo::getDictValue, RemoteDictDataVo::getDictLabel);
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-dict/src/main/java/org/dromara/common/dict/utils/DictUtils.java b/ruoyi-common/ruoyi-common-dict/src/main/java/org/dromara/common/dict/utils/DictUtils.java
new file mode 100644
index 0000000..93b2403
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dict/src/main/java/org/dromara/common/dict/utils/DictUtils.java
@@ -0,0 +1,51 @@
+package org.dromara.common.dict.utils;
+
+import org.dromara.common.core.constant.CacheNames;
+import org.dromara.common.redis.utils.CacheUtils;
+import org.dromara.system.api.domain.vo.RemoteDictDataVo;
+
+import java.util.List;
+
+/**
+ * 瀛楀吀宸ュ叿绫�
+ *
+ * @author ruoyi
+ */
+public class DictUtils {
+    /**
+     * 璁剧疆瀛楀吀缂撳瓨
+     *
+     * @param key       鍙傛暟閿�
+     * @param dictDatas 瀛楀吀鏁版嵁鍒楄〃
+     */
+    public static void setDictCache(String key, List<RemoteDictDataVo> dictDatas) {
+        CacheUtils.put(CacheNames.SYS_DICT, key, dictDatas);
+    }
+
+    /**
+     * 鑾峰彇瀛楀吀缂撳瓨
+     *
+     * @param key 鍙傛暟閿�
+     * @return dictDatas 瀛楀吀鏁版嵁鍒楄〃
+     */
+    public static List<RemoteDictDataVo> getDictCache(String key) {
+        return CacheUtils.get(CacheNames.SYS_DICT, key);
+    }
+
+    /**
+     * 鍒犻櫎鎸囧畾瀛楀吀缂撳瓨
+     *
+     * @param key 瀛楀吀閿�
+     */
+    public static void removeDictCache(String key) {
+        CacheUtils.evict(CacheNames.SYS_DICT, key);
+    }
+
+    /**
+     * 娓呯┖瀛楀吀缂撳瓨
+     */
+    public static void clearDictCache() {
+        CacheUtils.clear(CacheNames.SYS_DICT);
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-dict/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-dict/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..2b0acfe
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dict/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.dict.service.impl.DictServiceImpl
diff --git a/ruoyi-common/ruoyi-common-doc/pom.xml b/ruoyi-common/ruoyi-common-doc/pom.xml
new file mode 100644
index 0000000..26a35fc
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-doc/pom.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-doc</artifactId>
+
+    <description>
+        ruoyi-common-doc 绯荤粺鎺ュ彛
+    </description>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.therapi</groupId>
+            <artifactId>therapi-runtime-javadoc</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.module</groupId>
+            <artifactId>jackson-module-kotlin</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>
diff --git a/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/PlusPaths.java b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/PlusPaths.java
new file mode 100644
index 0000000..23075d2
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/PlusPaths.java
@@ -0,0 +1,15 @@
+package org.dromara.common.doc.config;
+
+import io.swagger.v3.oas.models.Paths;
+
+/**
+ * 鍗曠嫭浣跨敤涓�涓被渚夸簬鍒ゆ柇 瑙e喅springdoc璺緞鎷兼帴閲嶅闂
+ *
+ * @author Lion Li
+ */
+public class PlusPaths extends Paths {
+
+    public PlusPaths() {
+        super();
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/SpringDocAutoConfiguration.java b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/SpringDocAutoConfiguration.java
new file mode 100644
index 0000000..a6adaec
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/SpringDocAutoConfiguration.java
@@ -0,0 +1,117 @@
+package org.dromara.common.doc.config;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.Paths;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.security.SecurityRequirement;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.doc.config.properties.SpringDocProperties;
+import org.dromara.common.doc.handler.OpenApiHandler;
+import org.springdoc.core.configuration.SpringDocConfiguration;
+import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
+import org.springdoc.core.customizers.OpenApiCustomizer;
+import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
+import org.springdoc.core.properties.SpringDocConfigProperties;
+import org.springdoc.core.providers.JavadocProvider;
+import org.springdoc.core.service.OpenAPIService;
+import org.springdoc.core.service.SecurityService;
+import org.springdoc.core.utils.PropertyResolverUtils;
+import org.springframework.beans.factory.annotation.Value;
+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.web.ServerProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+import java.util.*;
+
+/**
+ * Swagger 鏂囨。閰嶇疆
+ *
+ * @author Lion Li
+ */
+@RequiredArgsConstructor
+@AutoConfiguration(before = SpringDocConfiguration.class)
+@EnableConfigurationProperties(SpringDocProperties.class)
+@ConditionalOnProperty(name = "springdoc.api-docs.enabled", havingValue = "true", matchIfMissing = true)
+public class SpringDocAutoConfiguration {
+
+    private final ServerProperties serverProperties;
+
+    @Value("${spring.application.name}")
+    private String appName;
+
+    @Bean
+    @ConditionalOnMissingBean(OpenAPI.class)
+    public OpenAPI openApi(SpringDocProperties properties) {
+        OpenAPI openApi = new OpenAPI();
+        // 鏂囨。鍩烘湰淇℃伅
+        SpringDocProperties.InfoProperties infoProperties = properties.getInfo();
+        Info info = convertInfo(infoProperties);
+        openApi.info(info);
+        // 鎵╁睍鏂囨。淇℃伅
+        openApi.externalDocs(properties.getExternalDocs());
+        openApi.tags(properties.getTags());
+        openApi.paths(properties.getPaths());
+        openApi.components(properties.getComponents());
+        Set<String> keySet = properties.getComponents().getSecuritySchemes().keySet();
+        List<SecurityRequirement> list = new ArrayList<>();
+        SecurityRequirement securityRequirement = new SecurityRequirement();
+        keySet.forEach(securityRequirement::addList);
+        list.add(securityRequirement);
+        openApi.security(list);
+
+        return openApi;
+    }
+
+    private Info convertInfo(SpringDocProperties.InfoProperties infoProperties) {
+        Info info = new Info();
+        info.setTitle(infoProperties.getTitle());
+        info.setDescription(infoProperties.getDescription());
+        info.setContact(infoProperties.getContact());
+        info.setLicense(infoProperties.getLicense());
+        info.setVersion(infoProperties.getVersion());
+        return info;
+    }
+
+    /**
+     * 鑷畾涔� openapi 澶勭悊鍣�
+     */
+    @Bean
+    public OpenAPIService openApiBuilder(Optional<OpenAPI> openAPI,
+                                         SecurityService securityParser,
+                                         SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils,
+                                         Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomisers,
+                                         Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomisers, Optional<JavadocProvider> javadocProvider) {
+        return new OpenApiHandler(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomisers, serverBaseUrlCustomisers, javadocProvider);
+    }
+
+    /**
+     * 瀵瑰凡缁忕敓鎴愬ソ鐨� OpenApi 杩涜鑷畾涔夋搷浣�
+     */
+    @Bean
+    public OpenApiCustomizer openApiCustomizer() {
+        // 鎷兼帴鏈嶅姟璺緞
+        String appPath = "/" + StringUtils.substring(appName, appName.indexOf("-") + 1);
+        String contextPath = serverProperties.getServlet().getContextPath();
+        String finalContextPath;
+        if (StringUtils.isBlank(contextPath) || "/".equals(contextPath)) {
+            finalContextPath = appPath;
+        } else {
+            finalContextPath = appPath + contextPath;
+        }
+        // 瀵规墍鏈夎矾寰勫鍔犲墠缃笂涓嬫枃璺緞
+        return openApi -> {
+            Paths oldPaths = openApi.getPaths();
+            if (oldPaths instanceof PlusPaths) {
+                return;
+            }
+            PlusPaths newPaths = new PlusPaths();
+            oldPaths.forEach((k, v) -> newPaths.addPathItem(finalContextPath + k, v));
+            openApi.setPaths(newPaths);
+        };
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/properties/SpringDocProperties.java b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/properties/SpringDocProperties.java
new file mode 100644
index 0000000..eae3b4c
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/config/properties/SpringDocProperties.java
@@ -0,0 +1,94 @@
+package org.dromara.common.doc.config.properties;
+
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.ExternalDocumentation;
+import io.swagger.v3.oas.models.Paths;
+import io.swagger.v3.oas.models.info.Contact;
+import io.swagger.v3.oas.models.info.License;
+import io.swagger.v3.oas.models.tags.Tag;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.NestedConfigurationProperty;
+
+import java.util.List;
+
+/**
+ * swagger 閰嶇疆灞炴��
+ *
+ * @author Lion Li
+ */
+@Data
+@ConfigurationProperties(prefix = "springdoc")
+public class SpringDocProperties {
+
+    /**
+     * 鏂囨。鍩烘湰淇℃伅
+     */
+    @NestedConfigurationProperty
+    private InfoProperties info = new InfoProperties();
+
+    /**
+     * 鎵╁睍鏂囨。鍦板潃
+     */
+    @NestedConfigurationProperty
+    private ExternalDocumentation externalDocs;
+
+    /**
+     * 鏍囩
+     */
+    private List<Tag> tags = null;
+
+    /**
+     * 璺緞
+     */
+    @NestedConfigurationProperty
+    private Paths paths = null;
+
+    /**
+     * 缁勪欢
+     */
+    @NestedConfigurationProperty
+    private Components components = null;
+
+    /**
+     * <p>
+     * 鏂囨。鐨勫熀纭�灞炴�т俊鎭�
+     * </p>
+     *
+     * @see io.swagger.v3.oas.models.info.Info
+     *
+     * 涓轰簡 springboot 鑷姩鐢熶骇閰嶇疆鎻愮ず淇℃伅锛屾墍浠ヨ繖閲屽鍒朵竴涓被鍑烘潵
+     */
+    @Data
+    public static class InfoProperties {
+
+        /**
+         * 鏍囬
+         */
+        private String title = null;
+
+        /**
+         * 鎻忚堪
+         */
+        private String description = null;
+
+        /**
+         * 鑱旂郴浜轰俊鎭�
+         */
+        @NestedConfigurationProperty
+        private Contact contact = null;
+
+        /**
+         * 璁稿彲璇�
+         */
+        @NestedConfigurationProperty
+        private License license = null;
+
+        /**
+         * 鐗堟湰
+         */
+        private String version = null;
+
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/handler/OpenApiHandler.java b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/handler/OpenApiHandler.java
new file mode 100644
index 0000000..1ee9e73
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-doc/src/main/java/org/dromara/common/doc/handler/OpenApiHandler.java
@@ -0,0 +1,252 @@
+package org.dromara.common.doc.handler;
+
+import cn.hutool.core.io.IoUtil;
+import io.swagger.v3.core.jackson.TypeNameResolver;
+import io.swagger.v3.core.util.AnnotationsUtils;
+import io.swagger.v3.oas.annotations.tags.Tags;
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.Operation;
+import io.swagger.v3.oas.models.Paths;
+import io.swagger.v3.oas.models.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
+import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
+import org.springdoc.core.properties.SpringDocConfigProperties;
+import org.springdoc.core.providers.JavadocProvider;
+import org.springdoc.core.service.OpenAPIService;
+import org.springdoc.core.service.SecurityService;
+import org.springdoc.core.utils.PropertyResolverUtils;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.method.HandlerMethod;
+
+import java.io.StringReader;
+import java.lang.reflect.Method;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 鑷畾涔� openapi 澶勭悊鍣�
+ * 瀵规簮鐮佸姛鑳借繘琛屼慨鏀� 澧炲己浣跨敤
+ */
+@Slf4j
+@SuppressWarnings("all")
+public class OpenApiHandler extends OpenAPIService {
+
+    /**
+     * The Basic error controller.
+     */
+    private static Class<?> basicErrorController;
+
+    /**
+     * The Security parser.
+     */
+    private final SecurityService securityParser;
+
+    /**
+     * The Mappings map.
+     */
+    private final Map<String, Object> mappingsMap = new HashMap<>();
+
+    /**
+     * The Springdoc tags.
+     */
+    private final Map<HandlerMethod, Tag> springdocTags = new HashMap<>();
+
+    /**
+     * The Open api builder customisers.
+     */
+    private final Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomisers;
+
+    /**
+     * The server base URL customisers.
+     */
+    private final Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomizers;
+
+    /**
+     * The Spring doc config properties.
+     */
+    private final SpringDocConfigProperties springDocConfigProperties;
+
+    /**
+     * The Cached open api map.
+     */
+    private final Map<String, OpenAPI> cachedOpenAPI = new HashMap<>();
+
+    /**
+     * The Property resolver utils.
+     */
+    private final PropertyResolverUtils propertyResolverUtils;
+
+    /**
+     * The javadoc provider.
+     */
+    private final Optional<JavadocProvider> javadocProvider;
+
+    /**
+     * The Context.
+     */
+    private ApplicationContext context;
+
+    /**
+     * The Open api.
+     */
+    private OpenAPI openAPI;
+
+    /**
+     * The Is servers present.
+     */
+    private boolean isServersPresent;
+
+    /**
+     * The Server base url.
+     */
+    private String serverBaseUrl;
+
+    /**
+     * Instantiates a new Open api builder.
+     *
+     * @param openAPI                   the open api
+     * @param securityParser            the security parser
+     * @param springDocConfigProperties the spring doc config properties
+     * @param propertyResolverUtils     the property resolver utils
+     * @param openApiBuilderCustomizers the open api builder customisers
+     * @param serverBaseUrlCustomizers  the server base url customizers
+     * @param javadocProvider           the javadoc provider
+     */
+    public OpenApiHandler(Optional<OpenAPI> openAPI, SecurityService securityParser,
+                          SpringDocConfigProperties springDocConfigProperties, PropertyResolverUtils propertyResolverUtils,
+                          Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomizers,
+                          Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomizers,
+                          Optional<JavadocProvider> javadocProvider) {
+        super(openAPI, securityParser, springDocConfigProperties, propertyResolverUtils, openApiBuilderCustomizers, serverBaseUrlCustomizers, javadocProvider);
+        if (openAPI.isPresent()) {
+            this.openAPI = openAPI.get();
+            if (this.openAPI.getComponents() == null)
+                this.openAPI.setComponents(new Components());
+            if (this.openAPI.getPaths() == null)
+                this.openAPI.setPaths(new Paths());
+            if (!CollectionUtils.isEmpty(this.openAPI.getServers()))
+                this.isServersPresent = true;
+        }
+        this.propertyResolverUtils = propertyResolverUtils;
+        this.securityParser = securityParser;
+        this.springDocConfigProperties = springDocConfigProperties;
+        this.openApiBuilderCustomisers = openApiBuilderCustomizers;
+        this.serverBaseUrlCustomizers = serverBaseUrlCustomizers;
+        this.javadocProvider = javadocProvider;
+        if (springDocConfigProperties.isUseFqn())
+            TypeNameResolver.std.setUseFqn(true);
+    }
+
+    @Override
+    public Operation buildTags(HandlerMethod handlerMethod, Operation operation, OpenAPI openAPI, Locale locale) {
+
+        Set<Tag> tags = new HashSet<>();
+        Set<String> tagsStr = new HashSet<>();
+
+        buildTagsFromMethod(handlerMethod.getMethod(), tags, tagsStr, locale);
+        buildTagsFromClass(handlerMethod.getBeanType(), tags, tagsStr, locale);
+
+        if (!CollectionUtils.isEmpty(tagsStr))
+            tagsStr = tagsStr.stream()
+                .map(str -> propertyResolverUtils.resolve(str, locale))
+                .collect(Collectors.toSet());
+
+        if (springdocTags.containsKey(handlerMethod)) {
+            Tag tag = springdocTags.get(handlerMethod);
+            tagsStr.add(tag.getName());
+            if (openAPI.getTags() == null || !openAPI.getTags().contains(tag)) {
+                openAPI.addTagsItem(tag);
+            }
+        }
+
+        if (!CollectionUtils.isEmpty(tagsStr)) {
+            if (CollectionUtils.isEmpty(operation.getTags()))
+                operation.setTags(new ArrayList<>(tagsStr));
+            else {
+                Set<String> operationTagsSet = new HashSet<>(operation.getTags());
+                operationTagsSet.addAll(tagsStr);
+                operation.getTags().clear();
+                operation.getTags().addAll(operationTagsSet);
+            }
+        }
+
+        if (isAutoTagClasses(operation)) {
+
+
+            if (javadocProvider.isPresent()) {
+                String description = javadocProvider.get().getClassJavadoc(handlerMethod.getBeanType());
+                if (StringUtils.isNotBlank(description)) {
+                    Tag tag = new Tag();
+
+                    // 鑷畾涔夐儴鍒� 淇敼浣跨敤java娉ㄩ噴褰搕ag鍚�
+                    List<String> list = IoUtil.readLines(new StringReader(description), new ArrayList<>());
+                    // tag.setName(tagAutoName);
+                    tag.setName(list.get(0));
+                    operation.addTagsItem(list.get(0));
+
+                    tag.setDescription(description);
+                    if (openAPI.getTags() == null || !openAPI.getTags().contains(tag)) {
+                        openAPI.addTagsItem(tag);
+                    }
+                }
+            } else {
+                String tagAutoName = splitCamelCase(handlerMethod.getBeanType().getSimpleName());
+                operation.addTagsItem(tagAutoName);
+            }
+        }
+
+        if (!CollectionUtils.isEmpty(tags)) {
+            // Existing tags
+            List<Tag> openApiTags = openAPI.getTags();
+            if (!CollectionUtils.isEmpty(openApiTags))
+                tags.addAll(openApiTags);
+            openAPI.setTags(new ArrayList<>(tags));
+        }
+
+        // Handle SecurityRequirement at operation level
+        io.swagger.v3.oas.annotations.security.SecurityRequirement[] securityRequirements = securityParser
+            .getSecurityRequirements(handlerMethod);
+        if (securityRequirements != null) {
+            if (securityRequirements.length == 0)
+                operation.setSecurity(Collections.emptyList());
+            else
+                securityParser.buildSecurityRequirement(securityRequirements, operation);
+        }
+
+        return operation;
+    }
+
+    private void buildTagsFromMethod(Method method, Set<Tag> tags, Set<String> tagsStr, Locale locale) {
+        // method tags
+        Set<Tags> tagsSet = AnnotatedElementUtils
+            .findAllMergedAnnotations(method, Tags.class);
+        Set<io.swagger.v3.oas.annotations.tags.Tag> methodTags = tagsSet.stream()
+            .flatMap(x -> Stream.of(x.value())).collect(Collectors.toSet());
+        methodTags.addAll(AnnotatedElementUtils.findAllMergedAnnotations(method, io.swagger.v3.oas.annotations.tags.Tag.class));
+        if (!CollectionUtils.isEmpty(methodTags)) {
+            tagsStr.addAll(methodTags.stream().map(tag -> propertyResolverUtils.resolve(tag.name(), locale)).collect(Collectors.toSet()));
+            List<io.swagger.v3.oas.annotations.tags.Tag> allTags = new ArrayList<>(methodTags);
+            addTags(allTags, tags, locale);
+        }
+    }
+
+    private void addTags(List<io.swagger.v3.oas.annotations.tags.Tag> sourceTags, Set<Tag> tags, Locale locale) {
+        Optional<Set<Tag>> optionalTagSet = AnnotationsUtils
+            .getTags(sourceTags.toArray(new io.swagger.v3.oas.annotations.tags.Tag[0]), true);
+        optionalTagSet.ifPresent(tagsSet -> {
+            tagsSet.forEach(tag -> {
+                tag.name(propertyResolverUtils.resolve(tag.getName(), locale));
+                tag.description(propertyResolverUtils.resolve(tag.getDescription(), locale));
+                if (tags.stream().noneMatch(t -> t.getName().equals(tag.getName())))
+                    tags.add(tag);
+            });
+        });
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-doc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-doc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..f039b2b
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-doc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.doc.config.SpringDocAutoConfiguration
diff --git a/ruoyi-common/ruoyi-common-dubbo/pom.xml b/ruoyi-common/ruoyi-common-dubbo/pom.xml
new file mode 100644
index 0000000..ce5bb3e
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dubbo/pom.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-dubbo</artifactId>
+
+    <description>
+        ruoyi-common-dubbo
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-json</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-context</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-spring-boot-actuator</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-spring-boot3-starter</artifactId>
+        </dependency>
+
+        <!-- Sa-Token 鏁村悎 Dubbo -->
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-dubbo3</artifactId>
+            <version>${satoken.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.dubbo</groupId>
+                    <artifactId>dubbo</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+    </dependencies>
+</project>
diff --git a/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/config/DubboConfiguration.java b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/config/DubboConfiguration.java
new file mode 100644
index 0000000..6fc622e
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/config/DubboConfiguration.java
@@ -0,0 +1,17 @@
+package org.dromara.common.dubbo.config;
+
+import org.dromara.common.core.factory.YmlPropertySourceFactory;
+import org.dromara.common.dubbo.properties.DubboCustomProperties;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.PropertySource;
+
+/**
+ * dubbo 閰嶇疆绫�
+ */
+@AutoConfiguration
+@EnableConfigurationProperties(DubboCustomProperties.class)
+@PropertySource(value = "classpath:common-dubbo.yml", factory = YmlPropertySourceFactory.class)
+public class DubboConfiguration {
+
+}
diff --git a/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/enumd/RequestLogEnum.java b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/enumd/RequestLogEnum.java
new file mode 100644
index 0000000..f238eeb
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/enumd/RequestLogEnum.java
@@ -0,0 +1,18 @@
+package org.dromara.common.dubbo.enumd;
+
+import lombok.AllArgsConstructor;
+
+/**
+ * 璇锋眰鏃ュ織娉涘瀷
+ *
+ * @author Lion Li
+ */
+@AllArgsConstructor
+public enum RequestLogEnum {
+
+    /**
+     * info 鍩虹淇℃伅 param 鍙傛暟淇℃伅 full 鍏ㄩ儴
+     */
+    INFO, PARAM, FULL;
+
+}
diff --git a/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/filter/DubboRequestFilter.java b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/filter/DubboRequestFilter.java
new file mode 100644
index 0000000..382dc94
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/filter/DubboRequestFilter.java
@@ -0,0 +1,58 @@
+package org.dromara.common.dubbo.filter;
+
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.dubbo.enumd.RequestLogEnum;
+import org.dromara.common.dubbo.properties.DubboCustomProperties;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.common.extension.Activate;
+import org.apache.dubbo.rpc.*;
+import org.apache.dubbo.rpc.service.GenericService;
+import org.dromara.common.json.utils.JsonUtils;
+
+/**
+ * dubbo鏃ュ織杩囨护鍣�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@Activate(group = {CommonConstants.PROVIDER, CommonConstants.CONSUMER}, order = Integer.MAX_VALUE)
+public class DubboRequestFilter implements Filter {
+
+    @Override
+    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
+        DubboCustomProperties properties = SpringUtils.getBean(DubboCustomProperties.class);
+        if (!properties.getRequestLog()) {
+            // 鏈紑鍚垯璺宠繃鏃ュ織閫昏緫
+            return invoker.invoke(invocation);
+        }
+        String client = CommonConstants.PROVIDER;
+        if (RpcContext.getServiceContext().isConsumerSide()) {
+            client = CommonConstants.CONSUMER;
+        }
+        String baselog = "Client[" + client + "],InterfaceName=[" + invocation.getInvoker().getInterface().getSimpleName() + "],MethodName=[" + invocation.getMethodName() + "]";
+        if (properties.getLogLevel() == RequestLogEnum.INFO) {
+            log.info("DUBBO - 鏈嶅姟璋冪敤: {}", baselog);
+        } else {
+            log.info("DUBBO - 鏈嶅姟璋冪敤: {},Parameter={}", baselog, invocation.getArguments());
+        }
+
+        long startTime = System.currentTimeMillis();
+        // 鎵ц鎺ュ彛璋冪敤閫昏緫
+        Result result = invoker.invoke(invocation);
+        // 璋冪敤鑰楁椂
+        long elapsed = System.currentTimeMillis() - startTime;
+        // 濡傛灉鍙戠敓寮傚父 鍒欐墦鍗板紓甯告棩蹇�
+        if (result.hasException() && invoker.getInterface().equals(GenericService.class)) {
+            log.error("DUBBO - 鏈嶅姟寮傚父: {},Exception={}", baselog, result.getException());
+        } else {
+            if (properties.getLogLevel() == RequestLogEnum.INFO) {
+                log.info("DUBBO - 鏈嶅姟鍝嶅簲: {},SpendTime=[{}ms]", baselog, elapsed);
+            } else if (properties.getLogLevel() == RequestLogEnum.FULL) {
+                log.info("DUBBO - 鏈嶅姟鍝嶅簲: {},SpendTime=[{}ms],Response={}", baselog, elapsed, JsonUtils.toJsonString(new Object[]{result.getValue()}));
+            }
+        }
+        return result;
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/properties/DubboCustomProperties.java b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/properties/DubboCustomProperties.java
new file mode 100644
index 0000000..8918088
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dubbo/src/main/java/org/dromara/common/dubbo/properties/DubboCustomProperties.java
@@ -0,0 +1,22 @@
+package org.dromara.common.dubbo.properties;
+
+import lombok.Data;
+import org.dromara.common.dubbo.enumd.RequestLogEnum;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+
+/**
+ * 鑷畾涔夐厤缃�
+ *
+ * @author Lion Li
+ */
+@Data
+@RefreshScope
+@ConfigurationProperties(prefix = "dubbo.custom")
+public class DubboCustomProperties {
+
+    private Boolean requestLog;
+
+    private RequestLogEnum logLevel;
+
+}
diff --git a/ruoyi-common/ruoyi-common-dubbo/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter b/ruoyi-common/ruoyi-common-dubbo/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter
new file mode 100644
index 0000000..6f766ab
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dubbo/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter
@@ -0,0 +1 @@
+dubboRequestFilter=org.dromara.common.dubbo.filter.DubboRequestFilter
diff --git a/ruoyi-common/ruoyi-common-dubbo/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-dubbo/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..f60bd3a
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dubbo/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.dubbo.config.DubboConfiguration
diff --git a/ruoyi-common/ruoyi-common-dubbo/src/main/resources/common-dubbo.yml b/ruoyi-common/ruoyi-common-dubbo/src/main/resources/common-dubbo.yml
new file mode 100644
index 0000000..fdc21f6
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-dubbo/src/main/resources/common-dubbo.yml
@@ -0,0 +1,30 @@
+# 鍐呯疆閰嶇疆 涓嶅厑璁镐慨鏀� 濡傞渶淇敼璇峰湪 nacos 涓婂啓鐩稿悓閰嶇疆瑕嗙洊
+dubbo:
+  application:
+    logger: slf4j
+    # 鍏冩暟鎹腑蹇� local 鏈湴 remote 杩滅▼ 杩欓噷浣跨敤杩滅▼渚夸簬鍏朵粬鏈嶅姟鑾峰彇
+    metadataType: remote
+    # 鍙�夊�� interface銆乮nstance銆乤ll锛岄粯璁ゆ槸 all锛屽嵆鎺ュ彛绾у湴鍧�銆佸簲鐢ㄧ骇鍦板潃閮芥敞鍐�
+    register-mode: instance
+    service-discovery:
+      # FORCE_INTERFACE锛屽彧娑堣垂鎺ュ彛绾у湴鍧�锛屽鏃犲湴鍧�鍒欐姤閿欙紝鍗曡闃� 2.x 鍦板潃
+      # APPLICATION_FIRST锛屾櫤鑳藉喅绛栨帴鍙g骇/搴旂敤绾у湴鍧�锛屽弻璁㈤槄
+      # FORCE_APPLICATION锛屽彧娑堣垂搴旂敤绾у湴鍧�锛屽鏃犲湴鍧�鍒欐姤閿欙紝鍗曡闃� 3.x 鍦板潃
+      migration: FORCE_APPLICATION
+  # 娉ㄥ唽涓績閰嶇疆
+  registry:
+    address: nacos://${spring.cloud.nacos.server-addr}
+    group: DUBBO_GROUP
+    parameters:
+      namespace: ${spring.profiles.active}
+  # 娑堣垂鑰呯浉鍏抽厤缃�
+  consumer:
+    # 缁撴灉缂撳瓨(LRU绠楁硶)
+    # 浼氭湁鏁版嵁涓嶄竴鑷撮棶棰� 寤鸿鍦ㄦ敞瑙e眬閮ㄥ紑鍚�
+    cache: false
+    # 鏀寔鏍¢獙娉ㄨВ
+    validation: jvalidationNew
+    # 璋冪敤閲嶈瘯 涓嶅寘鎷涓�娆� 0涓轰笉闇�瑕侀噸璇�
+    retries: 0
+    # 鍒濆鍖栨鏌�
+    check: false
diff --git a/ruoyi-common/ruoyi-common-elasticsearch/pom.xml b/ruoyi-common/ruoyi-common-elasticsearch/pom.xml
new file mode 100644
index 0000000..401320d
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-elasticsearch/pom.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-elasticsearch</artifactId>
+
+    <description>
+        ruoyi-common-elasticsearch ES鎼滅储寮曟搸鏈嶅姟
+    </description>
+
+	<dependencies>
+        <dependency>
+            <groupId>org.dromara.easy-es</groupId>
+            <artifactId>easy-es-boot-starter</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/ruoyi-common/ruoyi-common-elasticsearch/src/main/java/org/dromara/common/elasticsearch/config/ActuatorEnvironmentPostProcessor.java b/ruoyi-common/ruoyi-common-elasticsearch/src/main/java/org/dromara/common/elasticsearch/config/ActuatorEnvironmentPostProcessor.java
new file mode 100644
index 0000000..f2fbc5f
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-elasticsearch/src/main/java/org/dromara/common/elasticsearch/config/ActuatorEnvironmentPostProcessor.java
@@ -0,0 +1,25 @@
+package org.dromara.common.elasticsearch.config;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.env.EnvironmentPostProcessor;
+import org.springframework.core.Ordered;
+import org.springframework.core.env.ConfigurableEnvironment;
+
+/**
+ * 鍋ュ悍妫�鏌ラ厤缃敞鍏�
+ *
+ * @author Lion Li
+ */
+public class ActuatorEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
+
+	@Override
+	public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
+        System.setProperty("management.health.elasticsearch.enabled", "false");
+	}
+
+	@Override
+	public int getOrder() {
+		return Ordered.HIGHEST_PRECEDENCE;
+	}
+
+}
diff --git a/ruoyi-common/ruoyi-common-elasticsearch/src/main/java/org/dromara/common/elasticsearch/config/EasyEsConfiguration.java b/ruoyi-common/ruoyi-common-elasticsearch/src/main/java/org/dromara/common/elasticsearch/config/EasyEsConfiguration.java
new file mode 100644
index 0000000..3d6354a
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-elasticsearch/src/main/java/org/dromara/common/elasticsearch/config/EasyEsConfiguration.java
@@ -0,0 +1,17 @@
+package org.dromara.common.elasticsearch.config;
+
+import org.dromara.easyes.starter.register.EsMapperScan;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+
+/**
+ * easy-es 閰嶇疆
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration
+@ConditionalOnProperty(value = "easy-es.enable", havingValue = "true")
+@EsMapperScan("org.dromara.**.esmapper")
+public class EasyEsConfiguration {
+
+}
diff --git a/ruoyi-common/ruoyi-common-elasticsearch/src/main/resources/META-INF/spring.factories b/ruoyi-common/ruoyi-common-elasticsearch/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..1a18290
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-elasticsearch/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.boot.env.EnvironmentPostProcessor=\
+  org.dromara.common.elasticsearch.config.ActuatorEnvironmentPostProcessor
diff --git a/ruoyi-common/ruoyi-common-elasticsearch/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-elasticsearch/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..b1a5918
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-elasticsearch/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.elasticsearch.config.EasyEsConfiguration
diff --git a/ruoyi-common/ruoyi-common-encrypt/pom.xml b/ruoyi-common/ruoyi-common-encrypt/pom.xml
new file mode 100644
index 0000000..df3222b
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/pom.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-encrypt</artifactId>
+
+    <description>
+        ruoyi-common-encrypt 鏁版嵁鍔犺В瀵嗘ā鍧�
+    </description>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.mybatis.spring.boot</groupId>
+            <artifactId>mybatis-spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcprov-jdk15to18</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-crypto</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-webmvc</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/annotation/ApiEncrypt.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/annotation/ApiEncrypt.java
new file mode 100644
index 0000000..7f52de8
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/annotation/ApiEncrypt.java
@@ -0,0 +1,20 @@
+package org.dromara.common.encrypt.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 寮哄埗鍔犲瘑娉ㄨВ
+ *
+ * @author Michelle.Chung
+ */
+@Documented
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ApiEncrypt {
+
+    /**
+     * 鍝嶅簲鍔犲瘑蹇界暐锛岄粯璁や笉鍔犲瘑锛屼负 true 鏃跺姞瀵�
+     */
+    boolean response() default false;
+
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/annotation/EncryptField.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/annotation/EncryptField.java
new file mode 100644
index 0000000..d357d72
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/annotation/EncryptField.java
@@ -0,0 +1,44 @@
+package org.dromara.common.encrypt.annotation;
+
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+
+import java.lang.annotation.*;
+
+/**
+ * 瀛楁鍔犲瘑娉ㄨВ
+ *
+ * @author 鑰侀┈
+ */
+@Documented
+@Inherited
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EncryptField {
+
+    /**
+     * 鍔犲瘑绠楁硶
+     */
+    AlgorithmType algorithm() default AlgorithmType.DEFAULT;
+
+    /**
+     * 绉橀挜銆侫ES銆丼M4闇�瑕�
+     */
+    String password() default "";
+
+    /**
+     * 鍏挜銆俁SA銆丼M2闇�瑕�
+     */
+    String publicKey() default "";
+
+    /**
+     * 绉侀挜銆俁SA銆丼M2闇�瑕�
+     */
+    String privateKey() default "";
+
+    /**
+     * 缂栫爜鏂瑰紡銆傚鍔犲瘑绠楁硶涓築ASE64鐨勪笉璧蜂綔鐢�
+     */
+    EncodeType encode() default EncodeType.DEFAULT;
+
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/ApiDecryptAutoConfiguration.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/ApiDecryptAutoConfiguration.java
new file mode 100644
index 0000000..098f6bc
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/ApiDecryptAutoConfiguration.java
@@ -0,0 +1,32 @@
+package org.dromara.common.encrypt.config;
+
+import jakarta.servlet.DispatcherType;
+import org.dromara.common.encrypt.filter.CryptoFilter;
+import org.dromara.common.encrypt.properties.ApiDecryptProperties;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * api 瑙e瘑鑷姩閰嶇疆
+ *
+ * @author wdhcr
+ */
+@AutoConfiguration
+@EnableConfigurationProperties(ApiDecryptProperties.class)
+@ConditionalOnProperty(value = "api-decrypt.enabled", havingValue = "true")
+public class ApiDecryptAutoConfiguration {
+
+    @Bean
+    public FilterRegistrationBean<CryptoFilter> cryptoFilterRegistration(ApiDecryptProperties properties) {
+        FilterRegistrationBean<CryptoFilter> registration = new FilterRegistrationBean<>();
+        registration.setDispatcherTypes(DispatcherType.REQUEST);
+        registration.setFilter(new CryptoFilter(properties));
+        registration.addUrlPatterns("/*");
+        registration.setName("cryptoFilter");
+        registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
+        return registration;
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/EncryptorAutoConfiguration.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/EncryptorAutoConfiguration.java
new file mode 100644
index 0000000..e988a3a
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/EncryptorAutoConfiguration.java
@@ -0,0 +1,41 @@
+package org.dromara.common.encrypt.config;
+
+import org.dromara.common.encrypt.core.EncryptorManager;
+import org.dromara.common.encrypt.interceptor.MybatisDecryptInterceptor;
+import org.dromara.common.encrypt.interceptor.MybatisEncryptInterceptor;
+import org.dromara.common.encrypt.properties.EncryptorProperties;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * 鍔犺В瀵嗛厤缃�
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+@AutoConfiguration
+@EnableConfigurationProperties(EncryptorProperties.class)
+@ConditionalOnProperty(value = "mybatis-encryptor.enable", havingValue = "true")
+public class EncryptorAutoConfiguration {
+
+    @Autowired
+    private EncryptorProperties properties;
+
+    @Bean
+    public EncryptorManager encryptorManager() {
+        return new EncryptorManager();
+    }
+
+    @Bean
+    public MybatisEncryptInterceptor mybatisEncryptInterceptor(EncryptorManager encryptorManager) {
+        return new MybatisEncryptInterceptor(encryptorManager, properties);
+    }
+
+    @Bean
+    public MybatisDecryptInterceptor mybatisDecryptInterceptor(EncryptorManager encryptorManager) {
+        return new MybatisDecryptInterceptor(encryptorManager, properties);
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptContext.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptContext.java
new file mode 100644
index 0000000..2f02eaf
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptContext.java
@@ -0,0 +1,41 @@
+package org.dromara.common.encrypt.core;
+
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+import lombok.Data;
+
+/**
+ * 鍔犲瘑涓婁笅鏂� 鐢ㄤ簬encryptor浼犻�掑繀瑕佺殑鍙傛暟銆�
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+@Data
+public class EncryptContext {
+
+    /**
+     * 榛樿绠楁硶
+     */
+    private AlgorithmType algorithm;
+
+    /**
+     * 瀹夊叏绉橀挜
+     */
+    private String password;
+
+    /**
+     * 鍏挜
+     */
+    private String publicKey;
+
+    /**
+     * 绉侀挜
+     */
+    private String privateKey;
+
+    /**
+     * 缂栫爜鏂瑰紡锛宐ase64/hex
+     */
+    private EncodeType encode;
+
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptorManager.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptorManager.java
new file mode 100644
index 0000000..782995d
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/EncryptorManager.java
@@ -0,0 +1,100 @@
+package org.dromara.common.encrypt.core;
+
+import cn.hutool.core.util.ReflectUtil;
+import org.dromara.common.encrypt.annotation.EncryptField;
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+/**
+ * 鍔犲瘑绠$悊绫�
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+@Slf4j
+public class EncryptorManager {
+
+    /**
+     * 缂撳瓨鍔犲瘑鍣�
+     */
+    Map<EncryptContext, IEncryptor> encryptorMap = new ConcurrentHashMap<>();
+
+    /**
+     * 绫诲姞瀵嗗瓧娈电紦瀛�
+     */
+    Map<Class<?>, Set<Field>> fieldCache = new ConcurrentHashMap<>();
+
+    /**
+     * 鑾峰彇绫诲姞瀵嗗瓧娈电紦瀛�
+     */
+    public Set<Field> getFieldCache(Class<?> sourceClazz) {
+        return fieldCache.computeIfAbsent(sourceClazz, clazz -> {
+            Set<Field> fieldSet = new HashSet<>();
+            while (clazz != null) {
+                Field[] fields = clazz.getDeclaredFields();
+                fieldSet.addAll(Arrays.asList(fields));
+                clazz = clazz.getSuperclass();
+            }
+            fieldSet = fieldSet.stream().filter(field ->
+                    field.isAnnotationPresent(EncryptField.class) && field.getType() == String.class)
+                .collect(Collectors.toSet());
+            for (Field field : fieldSet) {
+                field.setAccessible(true);
+            }
+            return fieldSet;
+        });
+    }
+
+    /**
+     * 娉ㄥ唽鍔犲瘑鎵ц鑰呭埌缂撳瓨
+     *
+     * @param encryptContext 鍔犲瘑鎵ц鑰呴渶瑕佺殑鐩稿叧閰嶇疆鍙傛暟
+     */
+    public IEncryptor registAndGetEncryptor(EncryptContext encryptContext) {
+        if (encryptorMap.containsKey(encryptContext)) {
+            return encryptorMap.get(encryptContext);
+        }
+        IEncryptor encryptor = ReflectUtil.newInstance(encryptContext.getAlgorithm().getClazz(), encryptContext);
+        encryptorMap.put(encryptContext, encryptor);
+        return encryptor;
+    }
+
+    /**
+     * 绉婚櫎缂撳瓨涓殑鍔犲瘑鎵ц鑰�
+     *
+     * @param encryptContext 鍔犲瘑鎵ц鑰呴渶瑕佺殑鐩稿叧閰嶇疆鍙傛暟
+     */
+    public void removeEncryptor(EncryptContext encryptContext) {
+        this.encryptorMap.remove(encryptContext);
+    }
+
+    /**
+     * 鏍规嵁閰嶇疆杩涜鍔犲瘑銆備細杩涜鏈湴缂撳瓨瀵瑰簲鐨勭畻娉曞拰瀵瑰簲鐨勭閽ヤ俊鎭��
+     *
+     * @param value          寰呭姞瀵嗙殑鍊�
+     * @param encryptContext 鍔犲瘑鐩稿叧鐨勯厤缃俊鎭�
+     */
+    public String encrypt(String value, EncryptContext encryptContext) {
+        IEncryptor encryptor = this.registAndGetEncryptor(encryptContext);
+        return encryptor.encrypt(value, encryptContext.getEncode());
+    }
+
+    /**
+     * 鏍规嵁閰嶇疆杩涜瑙e瘑
+     *
+     * @param value          寰呰В瀵嗙殑鍊�
+     * @param encryptContext 鍔犲瘑鐩稿叧鐨勯厤缃俊鎭�
+     */
+    public String decrypt(String value, EncryptContext encryptContext) {
+        IEncryptor encryptor = this.registAndGetEncryptor(encryptContext);
+        return encryptor.decrypt(value);
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/IEncryptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/IEncryptor.java
new file mode 100644
index 0000000..dbc4420
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/IEncryptor.java
@@ -0,0 +1,35 @@
+package org.dromara.common.encrypt.core;
+
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+
+/**
+ * 鍔犺В鑰�
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+public interface IEncryptor {
+
+    /**
+     * 鑾峰緱褰撳墠绠楁硶
+     */
+    AlgorithmType algorithm();
+
+    /**
+     * 鍔犲瘑
+     *
+     * @param value      寰呭姞瀵嗗瓧绗︿覆
+     * @param encodeType 鍔犲瘑鍚庣殑缂栫爜鏍煎紡
+     * @return 鍔犲瘑鍚庣殑瀛楃涓�
+     */
+    String encrypt(String value, EncodeType encodeType);
+
+    /**
+     * 瑙e瘑
+     *
+     * @param value      寰呭姞瀵嗗瓧绗︿覆
+     * @return 瑙e瘑鍚庣殑瀛楃涓�
+     */
+    String decrypt(String value);
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/AbstractEncryptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/AbstractEncryptor.java
new file mode 100644
index 0000000..858d229
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/AbstractEncryptor.java
@@ -0,0 +1,18 @@
+package org.dromara.common.encrypt.core.encryptor;
+
+import org.dromara.common.encrypt.core.EncryptContext;
+import org.dromara.common.encrypt.core.IEncryptor;
+
+/**
+ * 鎵�鏈夊姞瀵嗘墽琛岃�呯殑鍩虹被
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+public abstract class AbstractEncryptor implements IEncryptor {
+
+    public AbstractEncryptor(EncryptContext context) {
+        // 鐢ㄦ埛閰嶇疆鏍¢獙涓庨厤缃敞鍏�
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/AesEncryptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/AesEncryptor.java
new file mode 100644
index 0000000..e4dc597
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/AesEncryptor.java
@@ -0,0 +1,55 @@
+package org.dromara.common.encrypt.core.encryptor;
+
+import org.dromara.common.encrypt.core.EncryptContext;
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+import org.dromara.common.encrypt.utils.EncryptUtils;
+
+/**
+ * AES绠楁硶瀹炵幇
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+public class AesEncryptor extends AbstractEncryptor {
+
+    private final EncryptContext context;
+
+    public AesEncryptor(EncryptContext context) {
+        super(context);
+        this.context = context;
+    }
+
+    /**
+     * 鑾峰緱褰撳墠绠楁硶
+     */
+    @Override
+    public AlgorithmType algorithm() {
+        return AlgorithmType.AES;
+    }
+
+    /**
+     * 鍔犲瘑
+     *
+     * @param value      寰呭姞瀵嗗瓧绗︿覆
+     * @param encodeType 鍔犲瘑鍚庣殑缂栫爜鏍煎紡
+     */
+    @Override
+    public String encrypt(String value, EncodeType encodeType) {
+        if (encodeType == EncodeType.HEX) {
+            return EncryptUtils.encryptByAesHex(value, context.getPassword());
+        } else {
+            return EncryptUtils.encryptByAes(value, context.getPassword());
+        }
+    }
+
+    /**
+     * 瑙e瘑
+     *
+     * @param value      寰呭姞瀵嗗瓧绗︿覆
+     */
+    @Override
+    public String decrypt(String value) {
+        return EncryptUtils.decryptByAes(value, context.getPassword());
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Base64Encryptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Base64Encryptor.java
new file mode 100644
index 0000000..0028548
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Base64Encryptor.java
@@ -0,0 +1,48 @@
+package org.dromara.common.encrypt.core.encryptor;
+
+import org.dromara.common.encrypt.core.EncryptContext;
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+import org.dromara.common.encrypt.utils.EncryptUtils;
+
+/**
+ * Base64绠楁硶瀹炵幇
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+public class Base64Encryptor extends AbstractEncryptor {
+
+    public Base64Encryptor(EncryptContext context) {
+        super(context);
+    }
+
+    /**
+     * 鑾峰緱褰撳墠绠楁硶
+     */
+    @Override
+    public AlgorithmType algorithm() {
+        return AlgorithmType.BASE64;
+    }
+
+    /**
+     * 鍔犲瘑
+     *
+     * @param value      寰呭姞瀵嗗瓧绗︿覆
+     * @param encodeType 鍔犲瘑鍚庣殑缂栫爜鏍煎紡
+     */
+    @Override
+    public String encrypt(String value, EncodeType encodeType) {
+        return EncryptUtils.encryptByBase64(value);
+    }
+
+    /**
+     * 瑙e瘑
+     *
+     * @param value      寰呭姞瀵嗗瓧绗︿覆
+     */
+    @Override
+    public String decrypt(String value) {
+        return EncryptUtils.decryptByBase64(value);
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/RsaEncryptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/RsaEncryptor.java
new file mode 100644
index 0000000..5f03a4b
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/RsaEncryptor.java
@@ -0,0 +1,62 @@
+package org.dromara.common.encrypt.core.encryptor;
+
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.encrypt.core.EncryptContext;
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+import org.dromara.common.encrypt.utils.EncryptUtils;
+
+
+/**
+ * RSA绠楁硶瀹炵幇
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+public class RsaEncryptor extends AbstractEncryptor {
+
+    private final EncryptContext context;
+
+    public RsaEncryptor(EncryptContext context) {
+        super(context);
+        String privateKey = context.getPrivateKey();
+        String publicKey = context.getPublicKey();
+        if (StringUtils.isAnyEmpty(privateKey, publicKey)) {
+            throw new IllegalArgumentException("RSA鍏閽ュ潎闇�瑕佹彁渚涳紝鍏挜鍔犲瘑锛岀閽ヨВ瀵嗐��");
+        }
+        this.context = context;
+    }
+
+    /**
+     * 鑾峰緱褰撳墠绠楁硶
+     */
+    @Override
+    public AlgorithmType algorithm() {
+        return AlgorithmType.RSA;
+    }
+
+    /**
+     * 鍔犲瘑
+     *
+     * @param value      寰呭姞瀵嗗瓧绗︿覆
+     * @param encodeType 鍔犲瘑鍚庣殑缂栫爜鏍煎紡
+     */
+    @Override
+    public String encrypt(String value, EncodeType encodeType) {
+        if (encodeType == EncodeType.HEX) {
+            return EncryptUtils.encryptByRsaHex(value, context.getPublicKey());
+        } else {
+            return EncryptUtils.encryptByRsa(value, context.getPublicKey());
+        }
+    }
+
+    /**
+     * 瑙e瘑
+     *
+     * @param value      寰呭姞瀵嗗瓧绗︿覆
+     */
+    @Override
+    public String decrypt(String value) {
+        return EncryptUtils.decryptByRsa(value, context.getPrivateKey());
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Sm2Encryptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Sm2Encryptor.java
new file mode 100644
index 0000000..aec5d82
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Sm2Encryptor.java
@@ -0,0 +1,61 @@
+package org.dromara.common.encrypt.core.encryptor;
+
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.encrypt.core.EncryptContext;
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+import org.dromara.common.encrypt.utils.EncryptUtils;
+
+/**
+ * sm2绠楁硶瀹炵幇
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+public class Sm2Encryptor extends AbstractEncryptor {
+
+    private final EncryptContext context;
+
+    public Sm2Encryptor(EncryptContext context) {
+        super(context);
+        String privateKey = context.getPrivateKey();
+        String publicKey = context.getPublicKey();
+        if (StringUtils.isAnyEmpty(privateKey, publicKey)) {
+            throw new IllegalArgumentException("SM2鍏閽ュ潎闇�瑕佹彁渚涳紝鍏挜鍔犲瘑锛岀閽ヨВ瀵嗐��");
+        }
+        this.context = context;
+    }
+
+    /**
+     * 鑾峰緱褰撳墠绠楁硶
+     */
+    @Override
+    public AlgorithmType algorithm() {
+        return AlgorithmType.SM2;
+    }
+
+    /**
+     * 鍔犲瘑
+     *
+     * @param value      寰呭姞瀵嗗瓧绗︿覆
+     * @param encodeType 鍔犲瘑鍚庣殑缂栫爜鏍煎紡
+     */
+    @Override
+    public String encrypt(String value, EncodeType encodeType) {
+        if (encodeType == EncodeType.HEX) {
+            return EncryptUtils.encryptBySm2Hex(value, context.getPublicKey());
+        } else {
+            return EncryptUtils.encryptBySm2(value, context.getPublicKey());
+        }
+    }
+
+    /**
+     * 瑙e瘑
+     *
+     * @param value      寰呭姞瀵嗗瓧绗︿覆
+     */
+    @Override
+    public String decrypt(String value) {
+        return EncryptUtils.decryptBySm2(value, context.getPrivateKey());
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Sm4Encryptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Sm4Encryptor.java
new file mode 100644
index 0000000..adaf674
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/core/encryptor/Sm4Encryptor.java
@@ -0,0 +1,55 @@
+package org.dromara.common.encrypt.core.encryptor;
+
+import org.dromara.common.encrypt.core.EncryptContext;
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+import org.dromara.common.encrypt.utils.EncryptUtils;
+
+/**
+ * sm4绠楁硶瀹炵幇
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+public class Sm4Encryptor extends AbstractEncryptor {
+
+    private final EncryptContext context;
+
+    public Sm4Encryptor(EncryptContext context) {
+        super(context);
+        this.context = context;
+    }
+
+    /**
+     * 鑾峰緱褰撳墠绠楁硶
+     */
+    @Override
+    public AlgorithmType algorithm() {
+        return AlgorithmType.SM4;
+    }
+
+    /**
+     * 鍔犲瘑
+     *
+     * @param value      寰呭姞瀵嗗瓧绗︿覆
+     * @param encodeType 鍔犲瘑鍚庣殑缂栫爜鏍煎紡
+     */
+    @Override
+    public String encrypt(String value, EncodeType encodeType) {
+        if (encodeType == EncodeType.HEX) {
+            return EncryptUtils.encryptBySm4Hex(value, context.getPassword());
+        } else {
+            return EncryptUtils.encryptBySm4(value, context.getPassword());
+        }
+    }
+
+    /**
+     * 瑙e瘑
+     *
+     * @param value      寰呭姞瀵嗗瓧绗︿覆
+     */
+    @Override
+    public String decrypt(String value) {
+        return EncryptUtils.decryptBySm4(value, context.getPassword());
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/enumd/AlgorithmType.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/enumd/AlgorithmType.java
new file mode 100644
index 0000000..7f54939
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/enumd/AlgorithmType.java
@@ -0,0 +1,48 @@
+package org.dromara.common.encrypt.enumd;
+
+import org.dromara.common.encrypt.core.encryptor.*;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 绠楁硶鍚嶇О
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+@Getter
+@AllArgsConstructor
+public enum AlgorithmType {
+
+    /**
+     * 榛樿璧皔ml閰嶇疆
+     */
+    DEFAULT(null),
+
+    /**
+     * base64
+     */
+    BASE64(Base64Encryptor.class),
+
+    /**
+     * aes
+     */
+    AES(AesEncryptor.class),
+
+    /**
+     * rsa
+     */
+    RSA(RsaEncryptor.class),
+
+    /**
+     * sm2
+     */
+    SM2(Sm2Encryptor.class),
+
+    /**
+     * sm4
+     */
+    SM4(Sm4Encryptor.class);
+
+    private final Class<? extends AbstractEncryptor> clazz;
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/enumd/EncodeType.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/enumd/EncodeType.java
new file mode 100644
index 0000000..f471221
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/enumd/EncodeType.java
@@ -0,0 +1,26 @@
+package org.dromara.common.encrypt.enumd;
+
+/**
+ * 缂栫爜绫诲瀷
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+public enum EncodeType {
+
+    /**
+     * 榛樿浣跨敤yml閰嶇疆
+     */
+    DEFAULT,
+
+    /**
+     * base64缂栫爜
+     */
+    BASE64,
+
+    /**
+     * 16杩涘埗缂栫爜
+     */
+    HEX;
+
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/CryptoFilter.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/CryptoFilter.java
new file mode 100644
index 0000000..8d898c0
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/CryptoFilter.java
@@ -0,0 +1,115 @@
+package org.dromara.common.encrypt.filter;
+
+import cn.hutool.core.util.ObjectUtil;
+import jakarta.servlet.*;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.dromara.common.core.constant.HttpStatus;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.encrypt.annotation.ApiEncrypt;
+import org.dromara.common.encrypt.properties.ApiDecryptProperties;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.HandlerExceptionResolver;
+import org.springframework.web.servlet.HandlerExecutionChain;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+import java.io.IOException;
+
+
+/**
+ * Crypto 杩囨护鍣�
+ *
+ * @author wdhcr
+ */
+public class CryptoFilter implements Filter {
+    private final ApiDecryptProperties properties;
+
+    public CryptoFilter(ApiDecryptProperties properties) {
+        this.properties = properties;
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+        HttpServletRequest servletRequest = (HttpServletRequest) request;
+        HttpServletResponse servletResponse = (HttpServletResponse) response;
+
+        boolean responseFlag = false;
+        ServletRequest requestWrapper = null;
+        ServletResponse responseWrapper = null;
+        EncryptResponseBodyWrapper responseBodyWrapper = null;
+
+        // 鏄惁涓� json 璇锋眰
+        if (StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) {
+            // 鏄惁涓� put 鎴栬�� post 璇锋眰
+            if (HttpMethod.PUT.matches(servletRequest.getMethod()) || HttpMethod.POST.matches(servletRequest.getMethod())) {
+                // 鏄惁瀛樺湪鍔犲瘑鏍囧ご
+                String headerValue = servletRequest.getHeader(properties.getHeaderFlag());
+                // 鑾峰彇鍔犲瘑娉ㄨВ
+                ApiEncrypt apiEncrypt = this.getApiEncryptAnnotation(servletRequest);
+                responseFlag = apiEncrypt != null && apiEncrypt.response();
+                if (StringUtils.isNotBlank(headerValue)) {
+                    // 璇锋眰瑙e瘑
+                    requestWrapper = new DecryptRequestBodyWrapper(servletRequest, properties.getPrivateKey(), properties.getHeaderFlag());
+                } else {
+                    // 鏄惁鏈夋敞瑙o紝鏈夊氨鎶ラ敊锛屾病鏈夋斁琛�
+                    if (ObjectUtil.isNotNull(apiEncrypt)) {
+                        HandlerExceptionResolver exceptionResolver = SpringUtils.getBean("handlerExceptionResolver", HandlerExceptionResolver.class);
+                        exceptionResolver.resolveException(
+                            servletRequest, servletResponse, null,
+                            new ServiceException("娌℃湁璁块棶鏉冮檺锛岃鑱旂郴绠$悊鍛樻巿鏉�", HttpStatus.FORBIDDEN));
+                        return;
+                    }
+                }
+                // 鍒ゆ柇鏄惁鍝嶅簲鍔犲瘑
+                if (responseFlag) {
+                    responseBodyWrapper = new EncryptResponseBodyWrapper(servletResponse);
+                    responseWrapper = responseBodyWrapper;
+                }
+            }
+        }
+
+        chain.doFilter(
+            ObjectUtil.defaultIfNull(requestWrapper, request),
+            ObjectUtil.defaultIfNull(responseWrapper, response));
+
+        if (responseFlag) {
+            servletResponse.reset();
+            // 瀵瑰師濮嬪唴瀹瑰姞瀵�
+            String encryptContent = responseBodyWrapper.getEncryptContent(
+                servletResponse, properties.getPublicKey(), properties.getHeaderFlag());
+            // 瀵瑰姞瀵嗗悗鐨勫唴瀹瑰啓鍑�
+            servletResponse.getWriter().write(encryptContent);
+        }
+    }
+
+    /**
+     * 鑾峰彇 ApiEncrypt 娉ㄨВ
+     */
+    private ApiEncrypt getApiEncryptAnnotation(HttpServletRequest servletRequest) {
+        RequestMappingHandlerMapping handlerMapping = SpringUtils.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
+        // 鑾峰彇娉ㄨВ
+        try {
+            HandlerExecutionChain mappingHandler = handlerMapping.getHandler(servletRequest);
+            if (ObjectUtil.isNotNull(mappingHandler)) {
+                Object handler = mappingHandler.getHandler();
+                if (ObjectUtil.isNotNull(handler)) {
+                    // 浠巋andler鑾峰彇娉ㄨВ
+                    if (handler instanceof HandlerMethod handlerMethod) {
+                        return handlerMethod.getMethodAnnotation(ApiEncrypt.class);
+                    }
+                }
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return null;
+    }
+
+    @Override
+    public void destroy() {
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/DecryptRequestBodyWrapper.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/DecryptRequestBodyWrapper.java
new file mode 100644
index 0000000..98f4bc7
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/DecryptRequestBodyWrapper.java
@@ -0,0 +1,94 @@
+package org.dromara.common.encrypt.filter;
+
+import cn.hutool.core.io.IoUtil;
+import jakarta.servlet.ReadListener;
+import jakarta.servlet.ServletInputStream;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletRequestWrapper;
+import org.dromara.common.core.constant.Constants;
+import org.dromara.common.encrypt.utils.EncryptUtils;
+import org.springframework.http.MediaType;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 瑙e瘑璇锋眰鍙傛暟宸ュ叿绫�
+ *
+ * @author wdhcr
+ */
+public class DecryptRequestBodyWrapper extends HttpServletRequestWrapper {
+
+    private final byte[] body;
+
+    public DecryptRequestBodyWrapper(HttpServletRequest request, String privateKey, String headerFlag) throws IOException {
+        super(request);
+        // 鑾峰彇 AES 瀵嗙爜 閲囩敤 RSA 鍔犲瘑
+        String headerRsa = request.getHeader(headerFlag);
+        String decryptAes = EncryptUtils.decryptByRsa(headerRsa, privateKey);
+        // 瑙e瘑 AES 瀵嗙爜
+        String aesPassword = EncryptUtils.decryptByBase64(decryptAes);
+        request.setCharacterEncoding(Constants.UTF8);
+        byte[] readBytes = IoUtil.readBytes(request.getInputStream(), false);
+        String requestBody = new String(readBytes, StandardCharsets.UTF_8);
+        // 瑙e瘑 body 閲囩敤 AES 鍔犲瘑
+        String decryptBody = EncryptUtils.decryptByAes(requestBody, aesPassword);
+        body = decryptBody.getBytes(StandardCharsets.UTF_8);
+    }
+
+    @Override
+    public BufferedReader getReader() {
+        return new BufferedReader(new InputStreamReader(getInputStream()));
+    }
+
+
+    @Override
+    public int getContentLength() {
+        return body.length;
+    }
+
+    @Override
+    public long getContentLengthLong() {
+        return body.length;
+    }
+
+    @Override
+    public String getContentType() {
+        return MediaType.APPLICATION_JSON_VALUE;
+    }
+
+
+    @Override
+    public ServletInputStream getInputStream() {
+        final ByteArrayInputStream bais = new ByteArrayInputStream(body);
+        return new ServletInputStream() {
+            @Override
+            public int read() {
+                return bais.read();
+            }
+
+            @Override
+            public int available() {
+                return body.length;
+            }
+
+            @Override
+            public boolean isFinished() {
+                return false;
+            }
+
+            @Override
+            public boolean isReady() {
+                return false;
+            }
+
+            @Override
+            public void setReadListener(ReadListener readListener) {
+
+            }
+        };
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/EncryptResponseBodyWrapper.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/EncryptResponseBodyWrapper.java
new file mode 100644
index 0000000..5eb34a7
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/EncryptResponseBodyWrapper.java
@@ -0,0 +1,123 @@
+package org.dromara.common.encrypt.filter;
+
+import cn.hutool.core.util.RandomUtil;
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.WriteListener;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpServletResponseWrapper;
+import org.dromara.common.encrypt.utils.EncryptUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 鍔犲瘑鍝嶅簲鍙傛暟鍖呰绫�
+ *
+ * @author Michelle.Chung
+ */
+public class EncryptResponseBodyWrapper extends HttpServletResponseWrapper {
+
+    private final ByteArrayOutputStream byteArrayOutputStream;
+    private final ServletOutputStream servletOutputStream;
+    private final PrintWriter printWriter;
+
+    public EncryptResponseBodyWrapper(HttpServletResponse response) throws IOException {
+        super(response);
+        this.byteArrayOutputStream = new ByteArrayOutputStream();
+        this.servletOutputStream = this.getOutputStream();
+        this.printWriter = new PrintWriter(new OutputStreamWriter(byteArrayOutputStream));
+    }
+
+    @Override
+    public PrintWriter getWriter() {
+        return printWriter;
+    }
+
+    @Override
+    public void flushBuffer() throws IOException {
+        if (servletOutputStream != null) {
+            servletOutputStream.flush();
+        }
+        if (printWriter != null) {
+            printWriter.flush();
+        }
+    }
+
+    @Override
+    public void reset() {
+        byteArrayOutputStream.reset();
+    }
+
+    public byte[] getResponseData() throws IOException {
+        flushBuffer();
+        return byteArrayOutputStream.toByteArray();
+    }
+
+    public String getContent() throws IOException {
+        flushBuffer();
+        return byteArrayOutputStream.toString();
+    }
+
+    /**
+     * 鑾峰彇鍔犲瘑鍐呭
+     *
+     * @param servletResponse response
+     * @param publicKey       RSA鍏挜 (鐢ㄤ簬鍔犲瘑 AES 绉橀挜)
+     * @param headerFlag      璇锋眰澶存爣蹇�
+     * @return 鍔犲瘑鍐呭
+     * @throws IOException
+     */
+    public String getEncryptContent(HttpServletResponse servletResponse, String publicKey, String headerFlag) throws IOException {
+        // 鐢熸垚绉橀挜
+        String aesPassword = RandomUtil.randomString(32);
+        // 绉橀挜浣跨敤 Base64 缂栫爜
+        String encryptAes = EncryptUtils.encryptByBase64(aesPassword);
+        // Rsa 鍏挜鍔犲瘑 Base64 缂栫爜
+        String encryptPassword = EncryptUtils.encryptByRsa(encryptAes, publicKey);
+
+        // 璁剧疆鍝嶅簲澶�
+        servletResponse.setHeader(headerFlag, encryptPassword);
+        servletResponse.setHeader("Access-Control-Allow-Origin", "*");
+        servletResponse.setHeader("Access-Control-Allow-Methods", "*");
+        servletResponse.setCharacterEncoding(StandardCharsets.UTF_8.toString());
+
+        // 鑾峰彇鍘熷鍐呭
+        String originalBody = this.getContent();
+        // 瀵瑰唴瀹硅繘琛屽姞瀵�
+        return EncryptUtils.encryptByAes(originalBody, aesPassword);
+    }
+
+    @Override
+    public ServletOutputStream getOutputStream() throws IOException {
+        return new ServletOutputStream() {
+            @Override
+            public boolean isReady() {
+                return false;
+            }
+
+            @Override
+            public void setWriteListener(WriteListener writeListener) {
+
+            }
+
+            @Override
+            public void write(int b) throws IOException {
+                byteArrayOutputStream.write(b);
+            }
+
+            @Override
+            public void write(byte[] b) throws IOException {
+                byteArrayOutputStream.write(b);
+            }
+
+            @Override
+            public void write(byte[] b, int off, int len) throws IOException {
+                byteArrayOutputStream.write(b, off, len);
+            }
+        };
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisDecryptInterceptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisDecryptInterceptor.java
new file mode 100644
index 0000000..7c2508f
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisDecryptInterceptor.java
@@ -0,0 +1,116 @@
+package org.dromara.common.encrypt.interceptor;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.executor.resultset.ResultSetHandler;
+import org.apache.ibatis.plugin.*;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.encrypt.annotation.EncryptField;
+import org.dromara.common.encrypt.core.EncryptContext;
+import org.dromara.common.encrypt.core.EncryptorManager;
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+import org.dromara.common.encrypt.properties.EncryptorProperties;
+
+import java.lang.reflect.Field;
+import java.sql.Statement;
+import java.util.*;
+
+/**
+ * 鍑哄弬瑙e瘑鎷︽埅鍣�
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+@Slf4j
+@Intercepts({@Signature(
+    type = ResultSetHandler.class,
+    method = "handleResultSets",
+    args = {Statement.class})
+})
+@AllArgsConstructor
+public class MybatisDecryptInterceptor implements Interceptor {
+
+    private final EncryptorManager encryptorManager;
+    private final EncryptorProperties defaultProperties;
+
+    @Override
+    public Object intercept(Invocation invocation) throws Throwable {
+        // 鑾峰彇鎵цmysql鎵ц缁撴灉
+        Object result = invocation.proceed();
+        if (result == null) {
+            return null;
+        }
+        decryptHandler(result);
+        return result;
+    }
+
+    /**
+     * 瑙e瘑瀵硅薄
+     *
+     * @param sourceObject 寰呭姞瀵嗗璞�
+     */
+    private void decryptHandler(Object sourceObject) {
+        if (ObjectUtil.isNull(sourceObject)) {
+            return;
+        }
+        if (sourceObject instanceof Map<?, ?> map) {
+            new HashSet<>(map.values()).forEach(this::decryptHandler);
+            return;
+        }
+        if (sourceObject instanceof List<?> list) {
+            if(CollUtil.isEmpty(list)) {
+                return;
+            }
+            // 鍒ゆ柇绗竴涓厓绱犳槸鍚﹀惈鏈夋敞瑙c�傚鏋滄病鏈夌洿鎺ヨ繑鍥烇紝鎻愰珮鏁堢巼
+            Object firstItem = list.get(0);
+            if (ObjectUtil.isNull(firstItem) || CollUtil.isEmpty(encryptorManager.getFieldCache(firstItem.getClass()))) {
+                return;
+            }
+            list.forEach(this::decryptHandler);
+            return;
+        }
+        Set<Field> fields = encryptorManager.getFieldCache(sourceObject.getClass());
+        try {
+            for (Field field : fields) {
+                field.set(sourceObject, this.decryptField(Convert.toStr(field.get(sourceObject)), field));
+            }
+        } catch (Exception e) {
+            log.error("澶勭悊瑙e瘑瀛楁鏃跺嚭閿�", e);
+        }
+    }
+
+    /**
+     * 瀛楁鍊艰繘琛屽姞瀵嗐�傞�氳繃瀛楁鐨勬壒娉ㄦ敞鍐屾柊鐨勫姞瀵嗙畻娉�
+     *
+     * @param value 寰呭姞瀵嗙殑鍊�
+     * @param field 寰呭姞瀵嗗瓧娈�
+     * @return 鍔犲瘑鍚庣粨鏋�
+     */
+    private String decryptField(String value, Field field) {
+        if (ObjectUtil.isNull(value)) {
+            return null;
+        }
+        EncryptField encryptField = field.getAnnotation(EncryptField.class);
+        EncryptContext encryptContext = new EncryptContext();
+        encryptContext.setAlgorithm(encryptField.algorithm() == AlgorithmType.DEFAULT ? defaultProperties.getAlgorithm() : encryptField.algorithm());
+        encryptContext.setEncode(encryptField.encode() == EncodeType.DEFAULT ? defaultProperties.getEncode() : encryptField.encode());
+        encryptContext.setPassword(StringUtils.isBlank(encryptField.password()) ? defaultProperties.getPassword() : encryptField.password());
+        encryptContext.setPrivateKey(StringUtils.isBlank(encryptField.privateKey()) ? defaultProperties.getPrivateKey() : encryptField.privateKey());
+        encryptContext.setPublicKey(StringUtils.isBlank(encryptField.publicKey()) ? defaultProperties.getPublicKey() : encryptField.publicKey());
+        return this.encryptorManager.decrypt(value, encryptContext);
+    }
+
+    @Override
+    public Object plugin(Object target) {
+        return Plugin.wrap(target, this);
+    }
+
+    @Override
+    public void setProperties(Properties properties) {
+
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisEncryptInterceptor.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisEncryptInterceptor.java
new file mode 100644
index 0000000..152f7db
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/interceptor/MybatisEncryptInterceptor.java
@@ -0,0 +1,120 @@
+package org.dromara.common.encrypt.interceptor;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.executor.parameter.ParameterHandler;
+import org.apache.ibatis.plugin.Interceptor;
+import org.apache.ibatis.plugin.Intercepts;
+import org.apache.ibatis.plugin.Invocation;
+import org.apache.ibatis.plugin.Signature;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.encrypt.annotation.EncryptField;
+import org.dromara.common.encrypt.core.EncryptContext;
+import org.dromara.common.encrypt.core.EncryptorManager;
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+import org.dromara.common.encrypt.properties.EncryptorProperties;
+
+import java.lang.reflect.Field;
+import java.sql.PreparedStatement;
+import java.util.*;
+
+/**
+ * 鍏ュ弬鍔犲瘑鎷︽埅鍣�
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+@Slf4j
+@Intercepts({@Signature(
+    type = ParameterHandler.class,
+    method = "setParameters",
+    args = {PreparedStatement.class})
+})
+@AllArgsConstructor
+public class MybatisEncryptInterceptor implements Interceptor {
+
+    private final EncryptorManager encryptorManager;
+    private final EncryptorProperties defaultProperties;
+
+    @Override
+    public Object intercept(Invocation invocation) throws Throwable {
+        return invocation;
+    }
+
+    @Override
+    public Object plugin(Object target) {
+        if (target instanceof ParameterHandler parameterHandler) {
+            // 杩涜鍔犲瘑鎿嶄綔
+            Object parameterObject = parameterHandler.getParameterObject();
+            if (ObjectUtil.isNotNull(parameterObject) && !(parameterObject instanceof String)) {
+                this.encryptHandler(parameterObject);
+            }
+        }
+        return target;
+    }
+
+    /**
+     * 鍔犲瘑瀵硅薄
+     *
+     * @param sourceObject 寰呭姞瀵嗗璞�
+     */
+    private void encryptHandler(Object sourceObject) {
+        if (ObjectUtil.isNull(sourceObject)) {
+            return;
+        }
+        if (sourceObject instanceof Map<?, ?> map) {
+            new HashSet<>(map.values()).forEach(this::encryptHandler);
+            return;
+        }
+        if (sourceObject instanceof List<?> list) {
+            if(CollUtil.isEmpty(list)) {
+                return;
+            }
+            // 鍒ゆ柇绗竴涓厓绱犳槸鍚﹀惈鏈夋敞瑙c�傚鏋滄病鏈夌洿鎺ヨ繑鍥烇紝鎻愰珮鏁堢巼
+            Object firstItem = list.get(0);
+            if (ObjectUtil.isNull(firstItem) || CollUtil.isEmpty(encryptorManager.getFieldCache(firstItem.getClass()))) {
+                return;
+            }
+            list.forEach(this::encryptHandler);
+            return;
+        }
+        Set<Field> fields = encryptorManager.getFieldCache(sourceObject.getClass());
+        try {
+            for (Field field : fields) {
+                field.set(sourceObject, this.encryptField(Convert.toStr(field.get(sourceObject)), field));
+            }
+        } catch (Exception e) {
+            log.error("澶勭悊鍔犲瘑瀛楁鏃跺嚭閿�", e);
+        }
+    }
+
+    /**
+     * 瀛楁鍊艰繘琛屽姞瀵嗐�傞�氳繃瀛楁鐨勬壒娉ㄦ敞鍐屾柊鐨勫姞瀵嗙畻娉�
+     *
+     * @param value 寰呭姞瀵嗙殑鍊�
+     * @param field 寰呭姞瀵嗗瓧娈�
+     * @return 鍔犲瘑鍚庣粨鏋�
+     */
+    private String encryptField(String value, Field field) {
+        if (ObjectUtil.isNull(value)) {
+            return null;
+        }
+        EncryptField encryptField = field.getAnnotation(EncryptField.class);
+        EncryptContext encryptContext = new EncryptContext();
+        encryptContext.setAlgorithm(encryptField.algorithm() == AlgorithmType.DEFAULT ? defaultProperties.getAlgorithm() : encryptField.algorithm());
+        encryptContext.setEncode(encryptField.encode() == EncodeType.DEFAULT ? defaultProperties.getEncode() : encryptField.encode());
+        encryptContext.setPassword(StringUtils.isBlank(encryptField.password()) ? defaultProperties.getPassword() : encryptField.password());
+        encryptContext.setPrivateKey(StringUtils.isBlank(encryptField.privateKey()) ? defaultProperties.getPrivateKey() : encryptField.privateKey());
+        encryptContext.setPublicKey(StringUtils.isBlank(encryptField.publicKey()) ? defaultProperties.getPublicKey() : encryptField.publicKey());
+        return this.encryptorManager.encrypt(value, encryptContext);
+    }
+
+
+    @Override
+    public void setProperties(Properties properties) {
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/ApiDecryptProperties.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/ApiDecryptProperties.java
new file mode 100644
index 0000000..6aadb3e
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/ApiDecryptProperties.java
@@ -0,0 +1,34 @@
+package org.dromara.common.encrypt.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * api瑙e瘑灞炴�ч厤缃被
+ * @author wdhcr
+ */
+@Data
+@ConfigurationProperties(prefix = "api-decrypt")
+public class ApiDecryptProperties {
+
+    /**
+     * 鍔犲瘑寮�鍏�
+     */
+    private Boolean enabled;
+
+    /**
+     * 澶撮儴鏍囪瘑
+     */
+    private String headerFlag;
+
+    /**
+     * 鍝嶅簲鍔犲瘑鍏挜
+     */
+    private String publicKey;
+
+    /**
+     * 璇锋眰瑙e瘑绉侀挜
+     */
+    private String privateKey;
+
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/EncryptorProperties.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/EncryptorProperties.java
new file mode 100644
index 0000000..ba445c1
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/EncryptorProperties.java
@@ -0,0 +1,48 @@
+package org.dromara.common.encrypt.properties;
+
+import org.dromara.common.encrypt.enumd.AlgorithmType;
+import org.dromara.common.encrypt.enumd.EncodeType;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * 鍔犺В瀵嗗睘鎬ч厤缃被
+ *
+ * @author 鑰侀┈
+ * @version 4.6.0
+ */
+@Data
+@ConfigurationProperties(prefix = "mybatis-encryptor")
+public class EncryptorProperties {
+
+    /**
+     * 杩囨护寮�鍏�
+     */
+    private Boolean enable;
+
+    /**
+     * 榛樿绠楁硶
+     */
+    private AlgorithmType algorithm;
+
+    /**
+     * 瀹夊叏绉橀挜
+     */
+    private String password;
+
+    /**
+     * 鍏挜
+     */
+    private String publicKey;
+
+    /**
+     * 绉侀挜
+     */
+    private String privateKey;
+
+    /**
+     * 缂栫爜鏂瑰紡锛宐ase64/hex
+     */
+    private EncodeType encode;
+
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/utils/EncryptUtils.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/utils/EncryptUtils.java
new file mode 100644
index 0000000..8e34843
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/utils/EncryptUtils.java
@@ -0,0 +1,311 @@
+package org.dromara.common.encrypt.utils;
+
+import cn.hutool.core.codec.Base64;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.SecureUtil;
+import cn.hutool.crypto.SmUtil;
+import cn.hutool.crypto.asymmetric.KeyType;
+import cn.hutool.crypto.asymmetric.RSA;
+import cn.hutool.crypto.asymmetric.SM2;
+
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 瀹夊叏鐩稿叧宸ュ叿绫�
+ *
+ * @author 鑰侀┈
+ */
+public class EncryptUtils {
+    /**
+     * 鍏挜
+     */
+    public static final String PUBLIC_KEY = "publicKey";
+    /**
+     * 绉侀挜
+     */
+    public static final String PRIVATE_KEY = "privateKey";
+
+    /**
+     * Base64鍔犲瘑
+     *
+     * @param data 寰呭姞瀵嗘暟鎹�
+     * @return 鍔犲瘑鍚庡瓧绗︿覆
+     */
+    public static String encryptByBase64(String data) {
+        return Base64.encode(data, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * Base64瑙e瘑
+     *
+     * @param data 寰呰В瀵嗘暟鎹�
+     * @return 瑙e瘑鍚庡瓧绗︿覆
+     */
+    public static String decryptByBase64(String data) {
+        return Base64.decodeStr(data, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * AES鍔犲瘑
+     *
+     * @param data     寰呰В瀵嗘暟鎹�
+     * @param password 绉橀挜瀛楃涓�
+     * @return 鍔犲瘑鍚庡瓧绗︿覆, 閲囩敤Base64缂栫爜
+     */
+    public static String encryptByAes(String data, String password) {
+        if (StrUtil.isBlank(password)) {
+            throw new IllegalArgumentException("AES闇�瑕佷紶鍏ョ閽ヤ俊鎭�");
+        }
+        // aes绠楁硶鐨勭閽ヨ姹傛槸16浣嶃��24浣嶃��32浣�
+        int[] array = {16, 24, 32};
+        if (!ArrayUtil.contains(array, password.length())) {
+            throw new IllegalArgumentException("AES绉橀挜闀垮害瑕佹眰涓�16浣嶃��24浣嶃��32浣�");
+        }
+        return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).encryptBase64(data, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * AES鍔犲瘑
+     *
+     * @param data     寰呰В瀵嗘暟鎹�
+     * @param password 绉橀挜瀛楃涓�
+     * @return 鍔犲瘑鍚庡瓧绗︿覆, 閲囩敤Hex缂栫爜
+     */
+    public static String encryptByAesHex(String data, String password) {
+        if (StrUtil.isBlank(password)) {
+            throw new IllegalArgumentException("AES闇�瑕佷紶鍏ョ閽ヤ俊鎭�");
+        }
+        // aes绠楁硶鐨勭閽ヨ姹傛槸16浣嶃��24浣嶃��32浣�
+        int[] array = {16, 24, 32};
+        if (!ArrayUtil.contains(array, password.length())) {
+            throw new IllegalArgumentException("AES绉橀挜闀垮害瑕佹眰涓�16浣嶃��24浣嶃��32浣�");
+        }
+        return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).encryptHex(data, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * AES瑙e瘑
+     *
+     * @param data     寰呰В瀵嗘暟鎹�
+     * @param password 绉橀挜瀛楃涓�
+     * @return 瑙e瘑鍚庡瓧绗︿覆
+     */
+    public static String decryptByAes(String data, String password) {
+        if (StrUtil.isBlank(password)) {
+            throw new IllegalArgumentException("AES闇�瑕佷紶鍏ョ閽ヤ俊鎭�");
+        }
+        // aes绠楁硶鐨勭閽ヨ姹傛槸16浣嶃��24浣嶃��32浣�
+        int[] array = {16, 24, 32};
+        if (!ArrayUtil.contains(array, password.length())) {
+            throw new IllegalArgumentException("AES绉橀挜闀垮害瑕佹眰涓�16浣嶃��24浣嶃��32浣�");
+        }
+        return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).decryptStr(data, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * sm4鍔犲瘑
+     *
+     * @param data     寰呭姞瀵嗘暟鎹�
+     * @param password 绉橀挜瀛楃涓�
+     * @return 鍔犲瘑鍚庡瓧绗︿覆, 閲囩敤Base64缂栫爜
+     */
+    public static String encryptBySm4(String data, String password) {
+        if (StrUtil.isBlank(password)) {
+            throw new IllegalArgumentException("SM4闇�瑕佷紶鍏ョ閽ヤ俊鎭�");
+        }
+        // sm4绠楁硶鐨勭閽ヨ姹傛槸16浣嶉暱搴�
+        int sm4PasswordLength = 16;
+        if (sm4PasswordLength != password.length()) {
+            throw new IllegalArgumentException("SM4绉橀挜闀垮害瑕佹眰涓�16浣�");
+        }
+        return SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8)).encryptBase64(data, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * sm4鍔犲瘑
+     *
+     * @param data     寰呭姞瀵嗘暟鎹�
+     * @param password 绉橀挜瀛楃涓�
+     * @return 鍔犲瘑鍚庡瓧绗︿覆, 閲囩敤Base64缂栫爜
+     */
+    public static String encryptBySm4Hex(String data, String password) {
+        if (StrUtil.isBlank(password)) {
+            throw new IllegalArgumentException("SM4闇�瑕佷紶鍏ョ閽ヤ俊鎭�");
+        }
+        // sm4绠楁硶鐨勭閽ヨ姹傛槸16浣嶉暱搴�
+        int sm4PasswordLength = 16;
+        if (sm4PasswordLength != password.length()) {
+            throw new IllegalArgumentException("SM4绉橀挜闀垮害瑕佹眰涓�16浣�");
+        }
+        return SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8)).encryptHex(data, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * sm4瑙e瘑
+     *
+     * @param data     寰呰В瀵嗘暟鎹�
+     * @param password 绉橀挜瀛楃涓�
+     * @return 瑙e瘑鍚庡瓧绗︿覆
+     */
+    public static String decryptBySm4(String data, String password) {
+        if (StrUtil.isBlank(password)) {
+            throw new IllegalArgumentException("SM4闇�瑕佷紶鍏ョ閽ヤ俊鎭�");
+        }
+        // sm4绠楁硶鐨勭閽ヨ姹傛槸16浣嶉暱搴�
+        int sm4PasswordLength = 16;
+        if (sm4PasswordLength != password.length()) {
+            throw new IllegalArgumentException("SM4绉橀挜闀垮害瑕佹眰涓�16浣�");
+        }
+        return SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8)).decryptStr(data, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * 浜х敓sm2鍔犺В瀵嗛渶瑕佺殑鍏挜鍜岀閽�
+     *
+     * @return 鍏閽ap
+     */
+    public static Map<String, String> generateSm2Key() {
+        Map<String, String> keyMap = new HashMap<>(2);
+        SM2 sm2 = SmUtil.sm2();
+        keyMap.put(PRIVATE_KEY, sm2.getPrivateKeyBase64());
+        keyMap.put(PUBLIC_KEY, sm2.getPublicKeyBase64());
+        return keyMap;
+    }
+
+    /**
+     * sm2鍏挜鍔犲瘑
+     *
+     * @param data      寰呭姞瀵嗘暟鎹�
+     * @param publicKey 鍏挜
+     * @return 鍔犲瘑鍚庡瓧绗︿覆, 閲囩敤Base64缂栫爜
+     */
+    public static String encryptBySm2(String data, String publicKey) {
+        if (StrUtil.isBlank(publicKey)) {
+            throw new IllegalArgumentException("SM2闇�瑕佷紶鍏ュ叕閽ヨ繘琛屽姞瀵�");
+        }
+        SM2 sm2 = SmUtil.sm2(null, publicKey);
+        return sm2.encryptBase64(data, StandardCharsets.UTF_8, KeyType.PublicKey);
+    }
+
+    /**
+     * sm2鍏挜鍔犲瘑
+     *
+     * @param data      寰呭姞瀵嗘暟鎹�
+     * @param publicKey 鍏挜
+     * @return 鍔犲瘑鍚庡瓧绗︿覆, 閲囩敤Hex缂栫爜
+     */
+    public static String encryptBySm2Hex(String data, String publicKey) {
+        if (StrUtil.isBlank(publicKey)) {
+            throw new IllegalArgumentException("SM2闇�瑕佷紶鍏ュ叕閽ヨ繘琛屽姞瀵�");
+        }
+        SM2 sm2 = SmUtil.sm2(null, publicKey);
+        return sm2.encryptHex(data, StandardCharsets.UTF_8, KeyType.PublicKey);
+    }
+
+    /**
+     * sm2绉侀挜瑙e瘑
+     *
+     * @param data       寰呭姞瀵嗘暟鎹�
+     * @param privateKey 绉侀挜
+     * @return 瑙e瘑鍚庡瓧绗︿覆
+     */
+    public static String decryptBySm2(String data, String privateKey) {
+        if (StrUtil.isBlank(privateKey)) {
+            throw new IllegalArgumentException("SM2闇�瑕佷紶鍏ョ閽ヨ繘琛岃В瀵�");
+        }
+        SM2 sm2 = SmUtil.sm2(privateKey, null);
+        return sm2.decryptStr(data, KeyType.PrivateKey, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * 浜х敓RSA鍔犺В瀵嗛渶瑕佺殑鍏挜鍜岀閽�
+     *
+     * @return 鍏閽ap
+     */
+    public static Map<String, String> generateRsaKey() {
+        Map<String, String> keyMap = new HashMap<>(2);
+        RSA rsa = SecureUtil.rsa();
+        keyMap.put(PRIVATE_KEY, rsa.getPrivateKeyBase64());
+        keyMap.put(PUBLIC_KEY, rsa.getPublicKeyBase64());
+        return keyMap;
+    }
+
+    /**
+     * rsa鍏挜鍔犲瘑
+     *
+     * @param data      寰呭姞瀵嗘暟鎹�
+     * @param publicKey 鍏挜
+     * @return 鍔犲瘑鍚庡瓧绗︿覆, 閲囩敤Base64缂栫爜
+     */
+    public static String encryptByRsa(String data, String publicKey) {
+        if (StrUtil.isBlank(publicKey)) {
+            throw new IllegalArgumentException("RSA闇�瑕佷紶鍏ュ叕閽ヨ繘琛屽姞瀵�");
+        }
+        RSA rsa = SecureUtil.rsa(null, publicKey);
+        return rsa.encryptBase64(data, StandardCharsets.UTF_8, KeyType.PublicKey);
+    }
+
+    /**
+     * rsa鍏挜鍔犲瘑
+     *
+     * @param data      寰呭姞瀵嗘暟鎹�
+     * @param publicKey 鍏挜
+     * @return 鍔犲瘑鍚庡瓧绗︿覆, 閲囩敤Hex缂栫爜
+     */
+    public static String encryptByRsaHex(String data, String publicKey) {
+        if (StrUtil.isBlank(publicKey)) {
+            throw new IllegalArgumentException("RSA闇�瑕佷紶鍏ュ叕閽ヨ繘琛屽姞瀵�");
+        }
+        RSA rsa = SecureUtil.rsa(null, publicKey);
+        return rsa.encryptHex(data, StandardCharsets.UTF_8, KeyType.PublicKey);
+    }
+
+    /**
+     * rsa绉侀挜瑙e瘑
+     *
+     * @param data       寰呭姞瀵嗘暟鎹�
+     * @param privateKey 绉侀挜
+     * @return 瑙e瘑鍚庡瓧绗︿覆
+     */
+    public static String decryptByRsa(String data, String privateKey) {
+        if (StrUtil.isBlank(privateKey)) {
+            throw new IllegalArgumentException("RSA闇�瑕佷紶鍏ョ閽ヨ繘琛岃В瀵�");
+        }
+        RSA rsa = SecureUtil.rsa(privateKey, null);
+        return rsa.decryptStr(data, KeyType.PrivateKey, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * md5鍔犲瘑
+     *
+     * @param data 寰呭姞瀵嗘暟鎹�
+     * @return 鍔犲瘑鍚庡瓧绗︿覆, 閲囩敤Hex缂栫爜
+     */
+    public static String encryptByMd5(String data) {
+        return SecureUtil.md5(data);
+    }
+
+    /**
+     * sha256鍔犲瘑
+     *
+     * @param data 寰呭姞瀵嗘暟鎹�
+     * @return 鍔犲瘑鍚庡瓧绗︿覆, 閲囩敤Hex缂栫爜
+     */
+    public static String encryptBySha256(String data) {
+        return SecureUtil.sha256(data);
+    }
+
+    /**
+     * sm3鍔犲瘑
+     *
+     * @param data 寰呭姞瀵嗘暟鎹�
+     * @return 鍔犲瘑鍚庡瓧绗︿覆, 閲囩敤Hex缂栫爜
+     */
+    public static String encryptBySm3(String data) {
+        return SmUtil.sm3(data);
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-encrypt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..b73a234
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1,2 @@
+org.dromara.common.encrypt.config.EncryptorAutoConfiguration
+org.dromara.common.encrypt.config.ApiDecryptAutoConfiguration
diff --git a/ruoyi-common/ruoyi-common-excel/pom.xml b/ruoyi-common/ruoyi-common-excel/pom.xml
new file mode 100644
index 0000000..dd4a5ee
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-excel/pom.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-excel</artifactId>
+
+    <description>
+        ruoyi-common-excel
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-json</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>easyexcel</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/CellMerge.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/CellMerge.java
new file mode 100644
index 0000000..bbdaaa1
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/CellMerge.java
@@ -0,0 +1,24 @@
+package org.dromara.common.excel.annotation;
+
+import org.dromara.common.excel.core.CellMergeStrategy;
+
+import java.lang.annotation.*;
+
+/**
+ * excel 鍒楀崟鍏冩牸鍚堝苟(鍚堝苟鍒楃浉鍚岄」)
+ *
+ * 闇�鎼厤 {@link CellMergeStrategy} 绛栫暐浣跨敤
+ *
+ * @author Lion Li
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface CellMerge {
+
+	/**
+	 * col index
+	 */
+	int index() default -1;
+
+}
diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelDictFormat.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelDictFormat.java
new file mode 100644
index 0000000..5c51842
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelDictFormat.java
@@ -0,0 +1,32 @@
+package org.dromara.common.excel.annotation;
+
+import org.dromara.common.core.utils.StringUtils;
+
+import java.lang.annotation.*;
+
+/**
+ * 瀛楀吀鏍煎紡鍖�
+ *
+ * @author Lion Li
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface ExcelDictFormat {
+
+    /**
+     * 濡傛灉鏄瓧鍏哥被鍨嬶紝璇疯缃瓧鍏哥殑type鍊� (濡�: sys_user_sex)
+     */
+    String dictType() default "";
+
+    /**
+     * 璇诲彇鍐呭杞〃杈惧紡 (濡�: 0=鐢�,1=濂�,2=鏈煡)
+     */
+    String readConverterExp() default "";
+
+    /**
+     * 鍒嗛殧绗︼紝璇诲彇瀛楃涓茬粍鍐呭
+     */
+    String separator() default StringUtils.SEPARATOR;
+
+}
diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelEnumFormat.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelEnumFormat.java
new file mode 100644
index 0000000..290379d
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/annotation/ExcelEnumFormat.java
@@ -0,0 +1,30 @@
+package org.dromara.common.excel.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 鏋氫妇鏍煎紡鍖�
+ *
+ * @author Liang
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface ExcelEnumFormat {
+
+    /**
+     * 瀛楀吀鏋氫妇绫诲瀷
+     */
+    Class<? extends Enum<?>> enumClass();
+
+    /**
+     * 瀛楀吀鏋氫妇绫讳腑瀵瑰簲鐨刢ode灞炴�у悕绉帮紝榛樿涓篶ode
+     */
+    String codeField() default "code";
+
+    /**
+     * 瀛楀吀鏋氫妇绫讳腑瀵瑰簲鐨則ext灞炴�у悕绉帮紝榛樿涓簍ext
+     */
+    String textField() default "text";
+
+}
diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelBigNumberConvert.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelBigNumberConvert.java
new file mode 100644
index 0000000..07cc4c4
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelBigNumberConvert.java
@@ -0,0 +1,52 @@
+package org.dromara.common.excel.convert;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.excel.converters.Converter;
+import com.alibaba.excel.enums.CellDataTypeEnum;
+import com.alibaba.excel.metadata.GlobalConfiguration;
+import com.alibaba.excel.metadata.data.ReadCellData;
+import com.alibaba.excel.metadata.data.WriteCellData;
+import com.alibaba.excel.metadata.property.ExcelContentProperty;
+import lombok.extern.slf4j.Slf4j;
+
+import java.math.BigDecimal;
+
+/**
+ * 澶ф暟鍊艰浆鎹�
+ * Excel 鏁板�奸暱搴︿綅15浣� 澶т簬15浣嶇殑鏁板�艰浆鎹綅瀛楃涓�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class ExcelBigNumberConvert implements Converter<Long> {
+
+    @Override
+    public Class<Long> supportJavaTypeKey() {
+        return Long.class;
+    }
+
+    @Override
+    public CellDataTypeEnum supportExcelTypeKey() {
+        return CellDataTypeEnum.STRING;
+    }
+
+    @Override
+    public Long convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+        return Convert.toLong(cellData.getData());
+    }
+
+    @Override
+    public WriteCellData<Object> convertToExcelData(Long object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+        if (ObjectUtil.isNotNull(object)) {
+            String str = Convert.toStr(object);
+            if (str.length() > 15) {
+                return new WriteCellData<>(str);
+            }
+        }
+        WriteCellData<Object> cellData = new WriteCellData<>(new BigDecimal(object));
+        cellData.setType(CellDataTypeEnum.NUMBER);
+        return cellData;
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelDictConvert.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelDictConvert.java
new file mode 100644
index 0000000..61eeabf
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelDictConvert.java
@@ -0,0 +1,73 @@
+package org.dromara.common.excel.convert;
+
+import cn.hutool.core.annotation.AnnotationUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.excel.converters.Converter;
+import com.alibaba.excel.enums.CellDataTypeEnum;
+import com.alibaba.excel.metadata.GlobalConfiguration;
+import com.alibaba.excel.metadata.data.ReadCellData;
+import com.alibaba.excel.metadata.data.WriteCellData;
+import com.alibaba.excel.metadata.property.ExcelContentProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.core.service.DictService;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.excel.utils.ExcelUtil;
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.Field;
+
+/**
+ * 瀛楀吀鏍煎紡鍖栬浆鎹㈠鐞�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class ExcelDictConvert implements Converter<Object> {
+
+    @Override
+    public Class<Object> supportJavaTypeKey() {
+        return Object.class;
+    }
+
+    @Override
+    public CellDataTypeEnum supportExcelTypeKey() {
+        return null;
+    }
+
+    @Override
+    public Object convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+        ExcelDictFormat anno = getAnnotation(contentProperty.getField());
+        String type = anno.dictType();
+        String label = cellData.getStringValue();
+        String value;
+        if (StringUtils.isBlank(type)) {
+            value = ExcelUtil.reverseByExp(label, anno.readConverterExp(), anno.separator());
+        } else {
+            value = SpringUtils.getBean(DictService.class).getDictValue(type, label, anno.separator());
+        }
+        return Convert.convert(contentProperty.getField().getType(), value);
+    }
+
+    @Override
+    public WriteCellData<String> convertToExcelData(Object object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+        if (ObjectUtil.isNull(object)) {
+            return new WriteCellData<>("");
+        }
+        ExcelDictFormat anno = getAnnotation(contentProperty.getField());
+        String type = anno.dictType();
+        String value = Convert.toStr(object);
+        String label;
+        if (StringUtils.isBlank(type)) {
+            label = ExcelUtil.convertByExp(value, anno.readConverterExp(), anno.separator());
+        } else {
+            label = SpringUtils.getBean(DictService.class).getDictLabel(type, value, anno.separator());
+        }
+        return new WriteCellData<>(label);
+    }
+
+    private ExcelDictFormat getAnnotation(Field field) {
+        return AnnotationUtil.getAnnotation(field, ExcelDictFormat.class);
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelEnumConvert.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelEnumConvert.java
new file mode 100644
index 0000000..b948ea7
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelEnumConvert.java
@@ -0,0 +1,87 @@
+package org.dromara.common.excel.convert;
+
+import cn.hutool.core.annotation.AnnotationUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.excel.converters.Converter;
+import com.alibaba.excel.enums.CellDataTypeEnum;
+import com.alibaba.excel.metadata.GlobalConfiguration;
+import com.alibaba.excel.metadata.data.ReadCellData;
+import com.alibaba.excel.metadata.data.WriteCellData;
+import com.alibaba.excel.metadata.property.ExcelContentProperty;
+import org.dromara.common.core.utils.reflect.ReflectUtils;
+import org.dromara.common.excel.annotation.ExcelEnumFormat;
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 鏋氫妇鏍煎紡鍖栬浆鎹㈠鐞�
+ *
+ * @author Liang
+ */
+@Slf4j
+public class ExcelEnumConvert implements Converter<Object> {
+
+    @Override
+    public Class<Object> supportJavaTypeKey() {
+        return Object.class;
+    }
+
+    @Override
+    public CellDataTypeEnum supportExcelTypeKey() {
+        return null;
+    }
+
+    @Override
+    public Object convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+        cellData.checkEmpty();
+        // Excel涓~鍏ョ殑鏄灇涓句腑鎸囧畾鐨勬弿杩�
+        Object textValue = switch (cellData.getType()) {
+            case STRING, DIRECT_STRING, RICH_TEXT_STRING -> cellData.getStringValue();
+            case NUMBER -> cellData.getNumberValue();
+            case BOOLEAN -> cellData.getBooleanValue();
+            default -> throw new IllegalArgumentException("鍗曞厓鏍肩被鍨嬪紓甯�!");
+        };
+        // 濡傛灉鏄┖鍊�
+        if (ObjectUtil.isNull(textValue)) {
+            return null;
+        }
+        Map<Object, String> enumCodeToTextMap = beforeConvert(contentProperty);
+        // 浠嶫ava杈撳嚭鑷矱xcel鏄痗ode杞瑃ext
+        // 鍥犳浠嶦xcel杞琂ava搴旇灏唗ext涓巆ode瀵硅皟
+        Map<Object, Object> enumTextToCodeMap = new HashMap<>();
+        enumCodeToTextMap.forEach((key, value) -> enumTextToCodeMap.put(value, key));
+        // 搴旇浠巘ext -> code涓煡鎵�
+        Object codeValue = enumTextToCodeMap.get(textValue);
+        return Convert.convert(contentProperty.getField().getType(), codeValue);
+    }
+
+    @Override
+    public WriteCellData<String> convertToExcelData(Object object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
+        if (ObjectUtil.isNull(object)) {
+            return new WriteCellData<>("");
+        }
+        Map<Object, String> enumValueMap = beforeConvert(contentProperty);
+        String value = Convert.toStr(enumValueMap.get(object), "");
+        return new WriteCellData<>(value);
+    }
+
+    private Map<Object, String> beforeConvert(ExcelContentProperty contentProperty) {
+        ExcelEnumFormat anno = getAnnotation(contentProperty.getField());
+        Map<Object, String> enumValueMap = new HashMap<>();
+        Enum<?>[] enumConstants = anno.enumClass().getEnumConstants();
+        for (Enum<?> enumConstant : enumConstants) {
+            Object codeValue = ReflectUtils.invokeGetter(enumConstant, anno.codeField());
+            String textValue = ReflectUtils.invokeGetter(enumConstant, anno.textField());
+            enumValueMap.put(codeValue, textValue);
+        }
+        return enumValueMap;
+    }
+
+    private ExcelEnumFormat getAnnotation(Field field) {
+        return AnnotationUtil.getAnnotation(field, ExcelEnumFormat.class);
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java
new file mode 100644
index 0000000..bcb5be7
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeStrategy.java
@@ -0,0 +1,142 @@
+package org.dromara.common.excel.core;
+
+import cn.hutool.core.collection.CollUtil;
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.alibaba.excel.metadata.Head;
+import com.alibaba.excel.write.merge.AbstractMergeStrategy;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.dromara.common.core.utils.reflect.ReflectUtils;
+import org.dromara.common.excel.annotation.CellMerge;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 鍒楀�奸噸澶嶅悎骞剁瓥鐣�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class CellMergeStrategy extends AbstractMergeStrategy {
+
+    private final List<CellRangeAddress> cellList;
+    private final boolean hasTitle;
+    private int rowIndex;
+
+    public CellMergeStrategy(List<?> list, boolean hasTitle) {
+        this.hasTitle = hasTitle;
+        // 琛屽悎骞跺紑濮嬩笅鏍�
+        this.rowIndex = hasTitle ? 1 : 0;
+        this.cellList = handle(list, hasTitle);
+    }
+
+    @Override
+    protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
+        // judge the list is not null
+        if (CollUtil.isNotEmpty(cellList)) {
+            // the judge is necessary
+            if (cell.getRowIndex() == rowIndex && cell.getColumnIndex() == 0) {
+                for (CellRangeAddress item : cellList) {
+                    sheet.addMergedRegion(item);
+                }
+            }
+        }
+    }
+
+    @SneakyThrows
+    private List<CellRangeAddress> handle(List<?> list, boolean hasTitle) {
+        List<CellRangeAddress> cellList = new ArrayList<>();
+        if (CollUtil.isEmpty(list)) {
+            return cellList;
+        }
+        Field[] fields = ReflectUtils.getFields(list.get(0).getClass(), field -> !"serialVersionUID".equals(field.getName()));
+
+        // 鏈夋敞瑙g殑瀛楁
+        List<Field> mergeFields = new ArrayList<>();
+        List<Integer> mergeFieldsIndex = new ArrayList<>();
+        for (int i = 0; i < fields.length; i++) {
+            Field field = fields[i];
+            if (field.isAnnotationPresent(CellMerge.class)) {
+                CellMerge cm = field.getAnnotation(CellMerge.class);
+                mergeFields.add(field);
+                mergeFieldsIndex.add(cm.index() == -1 ? i : cm.index());
+                if (hasTitle) {
+                    ExcelProperty property = field.getAnnotation(ExcelProperty.class);
+                    rowIndex = Math.max(rowIndex, property.value().length);
+                }
+            }
+        }
+
+        Map<Field, RepeatCell> map = new HashMap<>();
+        // 鐢熸垚涓や袱鍚堝苟鍗曞厓鏍�
+        for (int i = 0; i < list.size(); i++) {
+            for (int j = 0; j < mergeFields.size(); j++) {
+                Field field = mergeFields.get(j);
+                Object val = ReflectUtils.invokeGetter(list.get(i), field.getName());
+
+                int colNum = mergeFieldsIndex.get(j);
+                if (!map.containsKey(field)) {
+                    map.put(field, new RepeatCell(val, i));
+                } else {
+                    RepeatCell repeatCell = map.get(field);
+                    Object cellValue = repeatCell.getValue();
+                    if (cellValue == null || "".equals(cellValue)) {
+                        // 绌哄�艰烦杩囦笉鍚堝苟
+                        continue;
+                    }
+                    if (!cellValue.equals(val)) {
+                        if (i - repeatCell.getCurrent() > 1) {
+                            cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
+                        }
+                        map.put(field, new RepeatCell(val, i));
+                    } else if (j == 0) {
+                        if (i == list.size() - 1) {
+                            if (i > repeatCell.getCurrent()) {
+                                cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum));
+                            }
+                        }
+                    } else {
+                        // 鍒ゆ柇鍓嶉潰鐨勬槸鍚﹀悎骞朵簡
+                        RepeatCell firstCell = map.get(mergeFields.get(0));
+                        if (repeatCell.getCurrent() != firstCell.getCurrent()) {
+                            if (i == list.size() - 1) {
+                                if (i > repeatCell.getCurrent()) {
+                                    cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum));
+                                }
+                            } else if (repeatCell.getCurrent() < firstCell.getCurrent()) {
+                                if (i - repeatCell.getCurrent() > 1) {
+                                    cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
+                                }
+                                map.put(field, new RepeatCell(val, i));
+                            }
+                        } else if (i == list.size() - 1) {
+                            if (i > repeatCell.getCurrent()) {
+                                cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum));
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return cellList;
+    }
+
+    @Data
+    @AllArgsConstructor
+    static class RepeatCell {
+
+        private Object value;
+
+        private int current;
+
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DefaultExcelListener.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DefaultExcelListener.java
new file mode 100644
index 0000000..b6fa0b4
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DefaultExcelListener.java
@@ -0,0 +1,104 @@
+package org.dromara.common.excel.core;
+
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.excel.context.AnalysisContext;
+import com.alibaba.excel.event.AnalysisEventListener;
+import com.alibaba.excel.exception.ExcelAnalysisException;
+import com.alibaba.excel.exception.ExcelDataConvertException;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.ValidatorUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.ConstraintViolationException;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Excel 瀵煎叆鐩戝惉
+ *
+ * @author Yjoioooo
+ * @author Lion Li
+ */
+@Slf4j
+@NoArgsConstructor
+public class DefaultExcelListener<T> extends AnalysisEventListener<T> implements ExcelListener<T> {
+
+    /**
+     * 鏄惁Validator妫�楠岋紝榛樿涓烘槸
+     */
+    private Boolean isValidate = Boolean.TRUE;
+
+    /**
+     * excel 琛ㄥご鏁版嵁
+     */
+    private Map<Integer, String> headMap;
+
+    /**
+     * 瀵煎叆鍥炴墽
+     */
+    private ExcelResult<T> excelResult;
+
+    public DefaultExcelListener(boolean isValidate) {
+        this.excelResult = new DefaultExcelResult<>();
+        this.isValidate = isValidate;
+    }
+
+    /**
+     * 澶勭悊寮傚父
+     *
+     * @param exception ExcelDataConvertException
+     * @param context   Excel 涓婁笅鏂�
+     */
+    @Override
+    public void onException(Exception exception, AnalysisContext context) throws Exception {
+        String errMsg = null;
+        if (exception instanceof ExcelDataConvertException excelDataConvertException) {
+            // 濡傛灉鏄煇涓�涓崟鍏冩牸鐨勮浆鎹㈠紓甯� 鑳借幏鍙栧埌鍏蜂綋琛屽彿
+            Integer rowIndex = excelDataConvertException.getRowIndex();
+            Integer columnIndex = excelDataConvertException.getColumnIndex();
+            errMsg = StrUtil.format("绗瑊}琛�-绗瑊}鍒�-琛ㄥご{}: 瑙f瀽寮傚父<br/>",
+                rowIndex + 1, columnIndex + 1, headMap.get(columnIndex));
+            if (log.isDebugEnabled()) {
+                log.error(errMsg);
+            }
+        }
+        if (exception instanceof ConstraintViolationException constraintViolationException) {
+            Set<ConstraintViolation<?>> constraintViolations = constraintViolationException.getConstraintViolations();
+            String constraintViolationsMsg = StreamUtils.join(constraintViolations, ConstraintViolation::getMessage, ", ");
+            errMsg = StrUtil.format("绗瑊}琛屾暟鎹牎楠屽紓甯�: {}", context.readRowHolder().getRowIndex() + 1, constraintViolationsMsg);
+            if (log.isDebugEnabled()) {
+                log.error(errMsg);
+            }
+        }
+        excelResult.getErrorList().add(errMsg);
+        throw new ExcelAnalysisException(errMsg);
+    }
+
+    @Override
+    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
+        this.headMap = headMap;
+        log.debug("瑙f瀽鍒颁竴鏉¤〃澶存暟鎹�: {}", JsonUtils.toJsonString(headMap));
+    }
+
+    @Override
+    public void invoke(T data, AnalysisContext context) {
+        if (isValidate) {
+            ValidatorUtils.validate(data);
+        }
+        excelResult.getList().add(data);
+    }
+
+    @Override
+    public void doAfterAllAnalysed(AnalysisContext context) {
+        log.debug("鎵�鏈夋暟鎹В鏋愬畬鎴愶紒");
+    }
+
+    @Override
+    public ExcelResult<T> getExcelResult() {
+        return excelResult;
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DefaultExcelResult.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DefaultExcelResult.java
new file mode 100644
index 0000000..7373e12
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DefaultExcelResult.java
@@ -0,0 +1,73 @@
+package org.dromara.common.excel.core;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.Setter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 榛樿excel杩斿洖瀵硅薄
+ *
+ * @author Yjoioooo
+ * @author Lion Li
+ */
+public class DefaultExcelResult<T> implements ExcelResult<T> {
+
+    /**
+     * 鏁版嵁瀵硅薄list
+     */
+    @Setter
+    private List<T> list;
+
+    /**
+     * 閿欒淇℃伅鍒楄〃
+     */
+    @Setter
+    private List<String> errorList;
+
+    public DefaultExcelResult() {
+        this.list = new ArrayList<>();
+        this.errorList = new ArrayList<>();
+    }
+
+    public DefaultExcelResult(List<T> list, List<String> errorList) {
+        this.list = list;
+        this.errorList = errorList;
+    }
+
+    public DefaultExcelResult(ExcelResult<T> excelResult) {
+        this.list = excelResult.getList();
+        this.errorList = excelResult.getErrorList();
+    }
+
+    @Override
+    public List<T> getList() {
+        return list;
+    }
+
+    @Override
+    public List<String> getErrorList() {
+        return errorList;
+    }
+
+    /**
+     * 鑾峰彇瀵煎叆鍥炴墽
+     *
+     * @return 瀵煎叆鍥炴墽
+     */
+    @Override
+    public String getAnalysis() {
+        int successCount = list.size();
+        int errorCount = errorList.size();
+        if (successCount == 0) {
+            return "璇诲彇澶辫触锛屾湭瑙f瀽鍒版暟鎹�";
+        } else {
+            if (errorCount == 0) {
+                return StrUtil.format("鎭枩鎮紝鍏ㄩ儴璇诲彇鎴愬姛锛佸叡{}鏉�", successCount);
+            } else {
+                return "";
+            }
+        }
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DropDownOptions.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DropDownOptions.java
new file mode 100644
index 0000000..8b53a0c
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DropDownOptions.java
@@ -0,0 +1,149 @@
+package org.dromara.common.excel.core;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.exception.ServiceException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * <h1>Excel涓嬫媺鍙�夐」</h1>
+ * 娉ㄦ剰锛氫负纭繚涓嬫媺妗嗚В鏋愭纭紝浼犲�煎姟蹇呬娇鐢╟reateOptionValue()鍋氫负鍊肩殑鎷兼帴
+ *
+ * @author Emil.Zhang
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@SuppressWarnings("unused")
+public class DropDownOptions {
+    /**
+     * 涓�绾т笅鎷夋墍鍦ㄥ垪index锛屼粠0寮�濮嬬畻
+     */
+    private int index = 0;
+    /**
+     * 浜岀骇涓嬫媺鎵�鍦ㄧ殑index锛屼粠0寮�濮嬬畻锛屼笉鑳戒笌涓�绾х浉鍚�
+     */
+    private int nextIndex = 0;
+    /**
+     * 涓�绾т笅鎷夋墍鍖呭惈鐨勬暟鎹�
+     */
+    private List<String> options = new ArrayList<>();
+    /**
+     * 浜岀骇涓嬫媺鎵�鍖呭惈鐨勬暟鎹甅ap
+     * <p>浠ユ瘡涓�涓竴绾ч�夐」鍊间负Key锛屾瘡涓竴绾ч�夐」瀵瑰簲鐨勪簩绾ф暟鎹负Value</p>
+     */
+    private Map<String, List<String>> nextOptions = new HashMap<>();
+    /**
+     * 鍒嗛殧绗�
+     */
+    private static final String DELIMITER = "_";
+
+    /**
+     * 鍒涘缓鍙湁涓�绾х殑涓嬫媺閫�
+     */
+    public DropDownOptions(int index, List<String> options) {
+        this.index = index;
+        this.options = options;
+    }
+
+    /**
+     * <h2>鍒涘缓姣忎釜閫夐」鍙�夊��</h2>
+     * <p>娉ㄦ剰锛氫笉鑳戒互鏁板瓧锛岀壒娈婄鍙峰紑澶达紝閫夐」涓笉鍙互鍖呭惈浠讳綍杩愮畻绗﹀彿</p>
+     *
+     * @param vars 鍙�夊�煎唴鍖呭惈鐨勫弬鏁�
+     * @return 鍚堣鐨勫彲閫夊��
+     */
+    public static String createOptionValue(Object... vars) {
+        StringBuilder stringBuffer = new StringBuilder();
+        String regex = "^[\\S\\d\\u4e00-\\u9fa5]+$";
+        for (int i = 0; i < vars.length; i++) {
+            String var = StrUtil.trimToEmpty(String.valueOf(vars[i]));
+            if (!var.matches(regex)) {
+                throw new ServiceException("閫夐」鏁版嵁涓嶇鍚堣鍒欙紝浠呭厑璁镐娇鐢ㄤ腑鑻辨枃瀛楃浠ュ強鏁板瓧");
+            }
+            stringBuffer.append(var);
+            if (i < vars.length - 1) {
+                // 鐩磋嚦鏈�鍚庝竴涓墠锛岄兘浠浣滀负鍒囧壊绾�
+                stringBuffer.append(DELIMITER);
+            }
+        }
+        if (stringBuffer.toString().matches("^\\d_*$")) {
+            throw new ServiceException("绂佹浠ユ暟瀛楀紑澶�");
+        }
+        return stringBuffer.toString();
+    }
+
+    /**
+     * 灏嗗鐞嗗悗鍚堢悊鐨勫彲閫夊�艰В鏋愪负鍘熷鐨勫弬鏁�
+     *
+     * @param option 缁忚繃澶勭悊鍚庣殑鍚堢悊鐨勫彲閫夐」
+     * @return 鍘熷鐨勫弬鏁�
+     */
+    public static List<String> analyzeOptionValue(String option) {
+        return StrUtil.split(option, DELIMITER, true, true);
+    }
+
+    /**
+     * 鍒涘缓绾ц仈涓嬫媺閫夐」
+     *
+     * @param parentList                  鐖跺疄浣撳彲閫夐」鍘熷鏁版嵁
+     * @param parentIndex                 鐖朵笅鎷夐�変綅缃�
+     * @param sonList                     瀛愬疄浣撳彲閫夐」鍘熷鏁版嵁
+     * @param sonIndex                    瀛愪笅鎷夐�変綅缃�
+     * @param parentHowToGetIdFunction    鐖剁被濡備綍鑾峰彇鍞竴鏍囪瘑
+     * @param sonHowToGetParentIdFunction 瀛愮被濡備綍鑾峰彇鐖剁被鐨勫敮涓�鏍囪瘑
+     * @param howToBuildEveryOption       濡備綍鐢熸垚涓嬫媺閫夊唴瀹�
+     * @return 绾ц仈涓嬫媺閫夐」
+     */
+    public static <T> DropDownOptions buildLinkedOptions(List<T> parentList,
+                                                         int parentIndex,
+                                                         List<T> sonList,
+                                                         int sonIndex,
+                                                         Function<T, Number> parentHowToGetIdFunction,
+                                                         Function<T, Number> sonHowToGetParentIdFunction,
+                                                         Function<T, String> howToBuildEveryOption) {
+        DropDownOptions parentLinkSonOptions = new DropDownOptions();
+        // 鍏堝垱寤虹埗绫荤殑涓嬫媺
+        parentLinkSonOptions.setIndex(parentIndex);
+        parentLinkSonOptions.setOptions(
+            parentList.stream()
+                .map(howToBuildEveryOption)
+                .collect(Collectors.toList())
+        );
+        // 鎻愬彇鐖�-瀛愮骇鑱斾笅鎷�
+        Map<String, List<String>> sonOptions = new HashMap<>();
+        // 鐖剁骇渚濇嵁鑷繁鐨処D鍒嗙粍
+        Map<Number, List<T>> parentGroupByIdMap =
+            parentList.stream().collect(Collectors.groupingBy(parentHowToGetIdFunction));
+        // 閬嶅巻姣忎釜瀛愰泦锛屾彁鍙栧埌Map涓�
+        sonList.forEach(everySon -> {
+            if (parentGroupByIdMap.containsKey(sonHowToGetParentIdFunction.apply(everySon))) {
+                // 鎵惧埌瀵瑰簲鐨勪笂绾�
+                T parentObj = parentGroupByIdMap.get(sonHowToGetParentIdFunction.apply(everySon)).get(0);
+                // 鎻愬彇鍚嶇О鍜孖D浣滀负Key
+                String key = howToBuildEveryOption.apply(parentObj);
+                // Key瀵瑰簲鐨刅alue
+                List<String> thisParentSonOptionList;
+                if (sonOptions.containsKey(key)) {
+                    thisParentSonOptionList = sonOptions.get(key);
+                } else {
+                    thisParentSonOptionList = new ArrayList<>();
+                    sonOptions.put(key, thisParentSonOptionList);
+                }
+                // 寰�Value涓坊鍔犲綋鍓嶅瓙闆嗛�夐」
+                thisParentSonOptionList.add(howToBuildEveryOption.apply(everySon));
+            }
+        });
+        parentLinkSonOptions.setNextIndex(sonIndex);
+        parentLinkSonOptions.setNextOptions(sonOptions);
+        return parentLinkSonOptions;
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java
new file mode 100644
index 0000000..3b791ea
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java
@@ -0,0 +1,371 @@
+package org.dromara.common.excel.core;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.EnumUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.excel.metadata.FieldCache;
+import com.alibaba.excel.metadata.FieldWrapper;
+import com.alibaba.excel.util.ClassUtils;
+import com.alibaba.excel.write.handler.SheetWriteHandler;
+import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
+import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddressList;
+import org.apache.poi.ss.util.WorkbookUtil;
+import org.apache.poi.xssf.usermodel.XSSFDataValidation;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.service.DictService;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.annotation.ExcelEnumFormat;
+
+import java.lang.reflect.Field;
+import java.util.*;
+
+/**
+ * <h1>Excel琛ㄦ牸涓嬫媺閫夋搷浣�</h1>
+ * 鑰冭檻鍒颁笅鎷夐�夎繃澶氬彲鑳藉鑷碋xcel鎵撳紑缂撴參鐨勯棶棰橈紝鍙牎楠屽墠1000琛�
+ * <p>
+ * 鍗冲彧鏈夊墠1000琛岀殑鏁版嵁鍙互鐢ㄤ笅鎷夋锛岃秴鍑虹殑鑷閫氳繃闄愬埗鏁版嵁閲忕殑褰㈠紡锛岀浜屾杈撳嚭
+ *
+ * @author Emil.Zhang
+ */
+@Slf4j
+public class ExcelDownHandler implements SheetWriteHandler {
+
+    /**
+     * Excel琛ㄦ牸涓殑鍒楀悕鑻辨枃
+     * 浠呬负浜嗚В鏋愬垪鑻辨枃锛岀姝慨鏀�
+     */
+    private static final String EXCEL_COLUMN_NAME = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+    /**
+     * 鍗曢�夋暟鎹甋heet鍚�
+     */
+    private static final String OPTIONS_SHEET_NAME = "options";
+    /**
+     * 鑱斿姩閫夋嫨鏁版嵁Sheet鍚嶇殑澶�
+     */
+    private static final String LINKED_OPTIONS_SHEET_NAME = "linkedOptions";
+    /**
+     * 涓嬫媺鍙�夐」
+     */
+    private final List<DropDownOptions> dropDownOptions;
+    /**
+     * 褰撳墠鍗曢�夎繘搴�
+     */
+    private int currentOptionsColumnIndex;
+    /**
+     * 褰撳墠鑱斿姩閫夋嫨杩涘害
+     */
+    private int currentLinkedOptionsSheetIndex;
+    private final DictService dictService;
+
+    public ExcelDownHandler(List<DropDownOptions> options) {
+        this.dropDownOptions = options;
+        this.currentOptionsColumnIndex = 0;
+        this.currentLinkedOptionsSheetIndex = 0;
+        this.dictService = SpringUtils.getBean(DictService.class);
+    }
+
+    /**
+     * <h2>寮�濮嬪垱寤轰笅鎷夋暟鎹�</h2>
+     * 1.閫氳繃瑙f瀽浼犲叆鐨凘ExcelProperty鍚岀骇鏄惁鏍囨敞鏈堾DropDown閫夐」
+     * 濡傛灉鏈変笖璁剧疆浜唙alue鍊硷紝鍒欏皢鍏剁洿鎺ョ疆涓轰笅鎷夊彲閫夐」
+     * <p>
+     * 2.鎴栬�呭湪璋冪敤ExcelUtil鏃舵寚瀹氫簡鍙�夐」锛屽皢渚濇嵁浼犲叆鐨勫彲閫夐」鍋氫笅鎷�
+     * <p>
+     * 3.浜岃�呭苟瀛橈紝娉ㄦ剰璋冪敤鏂瑰紡
+     */
+    @Override
+    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
+        Sheet sheet = writeSheetHolder.getSheet();
+        // 寮�濮嬭缃笅鎷夋 HSSFWorkbook
+        DataValidationHelper helper = sheet.getDataValidationHelper();
+        Workbook workbook = writeWorkbookHolder.getWorkbook();
+        FieldCache fieldCache = ClassUtils.declaredFields(writeWorkbookHolder.getClazz(), writeWorkbookHolder);
+        for (Map.Entry<Integer, FieldWrapper> entry : fieldCache.getSortedFieldMap().entrySet()) {
+            Integer index = entry.getKey();
+            FieldWrapper wrapper = entry.getValue();
+            Field field = wrapper.getField();
+            // 寰幆瀹炰綋涓殑姣忎釜灞炴��
+            // 鍙�夌殑涓嬫媺鍊�
+            List<String> options = new ArrayList<>();
+            if (field.isAnnotationPresent(ExcelDictFormat.class)) {
+                // 濡傛灉鎸囧畾浜咢ExcelDictFormat锛屽垯浣跨敤瀛楀吀鐨勯�昏緫
+                ExcelDictFormat format = field.getDeclaredAnnotation(ExcelDictFormat.class);
+                String dictType = format.dictType();
+                String converterExp = format.readConverterExp();
+                if (StrUtil.isNotBlank(dictType)) {
+                    // 濡傛灉浼犻�掍簡瀛楀吀鍚嶏紝鍒欎緷鎹瓧鍏稿缓绔嬩笅鎷�
+                    Collection<String> values = Optional.ofNullable(dictService.getAllDictByDictType(dictType))
+                        .orElseThrow(() -> new ServiceException(String.format("瀛楀吀 %s 涓嶅瓨鍦�", dictType)))
+                        .values();
+                    options = new ArrayList<>(values);
+                } else if (StrUtil.isNotBlank(converterExp)) {
+                    // 濡傛灉鎸囧畾浜嗙‘鍒囩殑鍊硷紝鍒欑洿鎺ヨВ鏋愮‘鍒囩殑鍊�
+                    options = StrUtil.split(converterExp, format.separator(), true, true);
+                }
+            } else if (field.isAnnotationPresent(ExcelEnumFormat.class)) {
+                // 鍚﹀垯濡傛灉鎸囧畾浜咢ExcelEnumFormat锛屽垯浣跨敤鏋氫妇鐨勯�昏緫
+                ExcelEnumFormat format = field.getDeclaredAnnotation(ExcelEnumFormat.class);
+                List<Object> values = EnumUtil.getFieldValues(format.enumClass(), format.textField());
+                options = StreamUtils.toList(values, String::valueOf);
+            }
+            if (ObjectUtil.isNotEmpty(options)) {
+                // 浠呭綋涓嬫媺鍙�夐」涓嶄负绌烘椂鎵ц
+                if (options.size() > 20) {
+                    // 杩欓噷闄愬埗濡傛灉鍙�夐」澶т簬20锛屽垯浣跨敤棰濆琛ㄥ舰寮�
+                    dropDownWithSheet(helper, workbook, sheet, index, options);
+                } else {
+                    // 鍚﹀垯浣跨敤鍥哄畾鍊煎舰寮�
+                    dropDownWithSimple(helper, sheet, index, options);
+                }
+            }
+        }
+        if (CollUtil.isEmpty(dropDownOptions)) {
+            return;
+        }
+        dropDownOptions.forEach(everyOptions -> {
+            // 濡傛灉浼犻�掍簡涓嬫媺妗嗛�夋嫨鍣ㄥ弬鏁�
+            if (!everyOptions.getNextOptions().isEmpty()) {
+                // 褰撲簩绾ч�夐」涓嶄负绌烘椂锛屼娇鐢ㄩ澶栧叧鑱旇〃鐨勫舰寮�
+                dropDownLinkedOptions(helper, workbook, sheet, everyOptions);
+            } else if (everyOptions.getOptions().size() > 10) {
+                // 褰撲竴绾ч�夐」鍙傛暟涓暟澶т簬10锛屼娇鐢ㄩ澶栬〃鐨勫舰寮�
+                dropDownWithSheet(helper, workbook, sheet, everyOptions.getIndex(), everyOptions.getOptions());
+            } else if (everyOptions.getOptions().size() != 0) {
+                // 褰撲竴绾ч�夐」涓暟涓嶄负绌猴紝浣跨敤榛樿褰㈠紡
+                dropDownWithSimple(helper, sheet, everyOptions.getIndex(), everyOptions.getOptions());
+            }
+        });
+    }
+
+    /**
+     * <h2>绠�鍗曚笅鎷夋</h2>
+     * 鐩存帴灏嗗彲閫夐」鎷兼帴涓烘寚瀹氬垪鐨勬暟鎹牎楠屽��
+     *
+     * @param celIndex 鍒梚ndex
+     * @param value    涓嬫媺閫夊彲閫夊��
+     */
+    private void dropDownWithSimple(DataValidationHelper helper, Sheet sheet, Integer celIndex, List<String> value) {
+        if (ObjectUtil.isEmpty(value)) {
+            return;
+        }
+        this.markOptionsToSheet(helper, sheet, celIndex, helper.createExplicitListConstraint(ArrayUtil.toArray(value, String.class)));
+    }
+
+    /**
+     * <h2>棰濆琛ㄦ牸褰㈠紡鐨勭骇鑱斾笅鎷夋</h2>
+     *
+     * @param options 棰濆琛ㄦ牸褰㈠紡瀛樺偍鐨勪笅鎷夊彲閫夐」
+     */
+    private void dropDownLinkedOptions(DataValidationHelper helper, Workbook workbook, Sheet sheet, DropDownOptions options) {
+        String linkedOptionsSheetName = String.format("%s_%d", LINKED_OPTIONS_SHEET_NAME, currentLinkedOptionsSheetIndex);
+        // 鍒涘缓鑱斿姩涓嬫媺鏁版嵁琛�
+        Sheet linkedOptionsDataSheet = workbook.createSheet(WorkbookUtil.createSafeSheetName(linkedOptionsSheetName));
+        // 灏嗕笅鎷夎〃闅愯棌
+        workbook.setSheetHidden(workbook.getSheetIndex(linkedOptionsDataSheet), true);
+        // 瀹屽杽妯悜鐨勪竴绾ч�夐」鏁版嵁琛�
+        List<String> firstOptions = options.getOptions();
+        Map<String, List<String>> secoundOptionsMap = options.getNextOptions();
+
+        // 鍒涘缓鍚嶇О绠$悊鍣�
+        Name name = workbook.createName();
+        // 璁剧疆鍚嶇О绠$悊鍣ㄧ殑鍒悕
+        name.setNameName(linkedOptionsSheetName);
+        // 浠ユí鍚戠涓�琛屽垱寤轰竴绾т笅鎷夋嫾鎺ュ紩鐢ㄤ綅缃�
+        String firstOptionsFunction = String.format("%s!$%s$1:$%s$1",
+            linkedOptionsSheetName,
+            getExcelColumnName(0),
+            getExcelColumnName(firstOptions.size())
+        );
+        // 璁剧疆鍚嶇О绠$悊鍣ㄧ殑寮曠敤浣嶇疆
+        name.setRefersToFormula(firstOptionsFunction);
+        // 璁剧疆鏁版嵁鏍¢獙涓哄簭鍒楁ā寮忥紝寮曠敤鐨勬槸鍚嶇О绠$悊鍣ㄤ腑鐨勫埆鍚�
+        this.markOptionsToSheet(helper, sheet, options.getIndex(), helper.createFormulaListConstraint(linkedOptionsSheetName));
+
+        for (int columIndex = 0; columIndex < firstOptions.size(); columIndex++) {
+            // 鍏堟彁鍙栦富琛ㄤ腑涓�绾т笅鎷夌殑鍒楀悕
+            String firstOptionsColumnName = getExcelColumnName(columIndex);
+            // 涓�娆″惊鐜槸姣忎竴涓竴绾ч�夐」
+            int finalI = columIndex;
+            // 鏈寰幆鐨勪竴绾ч�夐」鍊�
+            String thisFirstOptionsValue = firstOptions.get(columIndex);
+            // 鍒涘缓绗竴琛岀殑鏁版嵁
+            Optional.ofNullable(linkedOptionsDataSheet.getRow(0))
+                // 濡傛灉涓嶅瓨鍦ㄥ垯鍒涘缓绗竴琛�
+                .orElseGet(() -> linkedOptionsDataSheet.createRow(finalI))
+                // 绗竴琛屽綋鍓嶅垪
+                .createCell(columIndex)
+                // 璁剧疆鍊间负褰撳墠涓�绾ч�夐」鍊�
+                .setCellValue(thisFirstOptionsValue);
+
+            // 绗簩琛屽紑濮嬶紝璁剧疆绗簩绾у埆閫夐」鍙傛暟
+            List<String> secondOptions = secoundOptionsMap.get(thisFirstOptionsValue);
+            if (CollUtil.isEmpty(secondOptions)) {
+                // 蹇呴』淇濊瘉鑷冲皯鏈変竴涓叧鑱旈�夐」锛屽惁鍒欏皢瀵艰嚧Excel瑙f瀽閿欒
+                secondOptions = Collections.singletonList("鏆傛棤_0");
+            }
+
+            // 浠ヨ涓�绾ч�夐」鍊煎垱寤哄瓙鍚嶇О绠$悊鍣�
+            Name sonName = workbook.createName();
+            // 璁剧疆鍚嶇О绠$悊鍣ㄧ殑鍒悕
+            sonName.setNameName(thisFirstOptionsValue);
+            // 浠ョ浜岃璇ュ垪鏁版嵁鎷兼帴寮曠敤浣嶇疆
+            String sonFunction = String.format("%s!$%s$2:$%s$%d",
+                linkedOptionsSheetName,
+                firstOptionsColumnName,
+                firstOptionsColumnName,
+                secondOptions.size() + 1
+            );
+            // 璁剧疆鍚嶇О绠$悊鍣ㄧ殑寮曠敤浣嶇疆
+            sonName.setRefersToFormula(sonFunction);
+            // 鏁版嵁楠岃瘉涓哄簭鍒楁ā寮忥紝寮曠敤鍒版瘡涓�涓富琛ㄤ腑鐨勪簩绾ч�夐」浣嶇疆
+            // 鍒涘缓瀛愰」鐨勫悕绉扮鐞嗗櫒锛屽彧鏄负浜嗕娇寰桬xcel鍙互璇嗗埆鍒版暟鎹�
+            String mainSheetFirstOptionsColumnName = getExcelColumnName(options.getIndex());
+            for (int i = 0; i < 100; i++) {
+                // 浠ヤ竴绾ч�夐」瀵瑰簲鐨勪富浣撴墍鍦ㄤ綅缃垱寤轰簩绾т笅鎷�
+                String secondOptionsFunction = String.format("=INDIRECT(%s%d)", mainSheetFirstOptionsColumnName, i + 1);
+                // 浜岀骇鍙兘涓昏〃姣忎竴琛岀殑姣忎竴鍒楁坊鍔犱簩绾ф牎楠�
+                markLinkedOptionsToSheet(helper, sheet, i, options.getNextIndex(), helper.createFormulaListConstraint(secondOptionsFunction));
+            }
+
+            for (int rowIndex = 0; rowIndex < secondOptions.size(); rowIndex++) {
+                // 浠庣浜岃寮�濮嬪~鍏呬簩绾ч�夐」
+                int finalRowIndex = rowIndex + 1;
+                int finalColumIndex = columIndex;
+
+                Row row = Optional.ofNullable(linkedOptionsDataSheet.getRow(finalRowIndex))
+                    // 娌℃湁鍒欏垱寤�
+                    .orElseGet(() -> linkedOptionsDataSheet.createRow(finalRowIndex));
+                Optional
+                    // 鍦ㄦ湰绾т竴绾ч�夐」鎵�鍦ㄧ殑鍒�
+                    .ofNullable(row.getCell(finalColumIndex))
+                    // 涓嶅瓨鍦ㄥ垯鍒涘缓
+                    .orElseGet(() -> row.createCell(finalColumIndex))
+                    // 璁剧疆浜岀骇閫夐」鍊�
+                    .setCellValue(secondOptions.get(rowIndex));
+            }
+        }
+
+        currentLinkedOptionsSheetIndex++;
+    }
+
+    /**
+     * <h2>棰濆琛ㄦ牸褰㈠紡鐨勬櫘閫氫笅鎷夋</h2>
+     * 鐢变簬涓嬫媺妗嗗彲閫夊�兼暟閲忚繃澶氾紝涓烘彁鍗嘐xcel鎵撳紑鏁堢巼锛屼娇鐢ㄩ澶栬〃鏍煎舰寮忓仛涓嬫媺
+     *
+     * @param celIndex 涓嬫媺閫�
+     * @param value    涓嬫媺閫夊彲閫夊��
+     */
+    private void dropDownWithSheet(DataValidationHelper helper, Workbook workbook, Sheet sheet, Integer celIndex, List<String> value) {
+        // 鍒涘缓涓嬫媺鏁版嵁琛�
+        Sheet simpleDataSheet = Optional.ofNullable(workbook.getSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME)))
+            .orElseGet(() -> workbook.createSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME)));
+        // 灏嗕笅鎷夎〃闅愯棌
+        workbook.setSheetHidden(workbook.getSheetIndex(simpleDataSheet), true);
+        // 瀹屽杽绾靛悜鐨勪竴绾ч�夐」鏁版嵁琛�
+        for (int i = 0; i < value.size(); i++) {
+            int finalI = i;
+            // 鑾峰彇姣忎竴閫夐」琛岋紝濡傛灉娌℃湁鍒欏垱寤�
+            Row row = Optional.ofNullable(simpleDataSheet.getRow(i))
+                .orElseGet(() -> simpleDataSheet.createRow(finalI));
+            // 鑾峰彇鏈骇閫夐」瀵瑰簲鐨勯�夐」鍒楋紝濡傛灉娌℃湁鍒欏垱寤�
+            Cell cell = Optional.ofNullable(row.getCell(currentOptionsColumnIndex))
+                .orElseGet(() -> row.createCell(currentOptionsColumnIndex));
+            // 璁剧疆鍊�
+            cell.setCellValue(value.get(i));
+        }
+
+        // 鍒涘缓鍚嶇О绠$悊鍣�
+        Name name = workbook.createName();
+        // 璁剧疆鍚嶇О绠$悊鍣ㄧ殑鍒悕
+        String nameName = String.format("%s_%d", OPTIONS_SHEET_NAME, celIndex);
+        name.setNameName(nameName);
+        // 浠ョ旱鍚戠涓�鍒楀垱寤轰竴绾т笅鎷夋嫾鎺ュ紩鐢ㄤ綅缃�
+        String function = String.format("%s!$%s$1:$%s$%d",
+            OPTIONS_SHEET_NAME,
+            getExcelColumnName(currentOptionsColumnIndex),
+            getExcelColumnName(currentOptionsColumnIndex),
+            value.size());
+        // 璁剧疆鍚嶇О绠$悊鍣ㄧ殑寮曠敤浣嶇疆
+        name.setRefersToFormula(function);
+        // 璁剧疆鏁版嵁鏍¢獙涓哄簭鍒楁ā寮忥紝寮曠敤鐨勬槸鍚嶇О绠$悊鍣ㄤ腑鐨勫埆鍚�
+        this.markOptionsToSheet(helper, sheet, celIndex, helper.createFormulaListConstraint(nameName));
+        currentOptionsColumnIndex++;
+    }
+
+    /**
+     * 鎸傝浇涓嬫媺鐨勫垪锛屼粎闄愪竴绾ч�夐」
+     */
+    private void markOptionsToSheet(DataValidationHelper helper, Sheet sheet, Integer celIndex,
+                                    DataValidationConstraint constraint) {
+        // 璁剧疆鏁版嵁鏈夋晥鎬у姞杞藉湪鍝釜鍗曞厓鏍间笂,鍥涗釜鍙傛暟鍒嗗埆鏄細璧峰琛屻�佺粓姝㈣銆佽捣濮嬪垪銆佺粓姝㈠垪
+        CellRangeAddressList addressList = new CellRangeAddressList(1, 1000, celIndex, celIndex);
+        markDataValidationToSheet(helper, sheet, constraint, addressList);
+    }
+
+    /**
+     * 鎸傝浇涓嬫媺鐨勫垪锛屼粎闄愪簩绾ч�夐」
+     */
+    private void markLinkedOptionsToSheet(DataValidationHelper helper, Sheet sheet, Integer rowIndex,
+                                          Integer celIndex, DataValidationConstraint constraint) {
+        // 璁剧疆鏁版嵁鏈夋晥鎬у姞杞藉湪鍝釜鍗曞厓鏍间笂,鍥涗釜鍙傛暟鍒嗗埆鏄細璧峰琛屻�佺粓姝㈣銆佽捣濮嬪垪銆佺粓姝㈠垪
+        CellRangeAddressList addressList = new CellRangeAddressList(rowIndex, rowIndex, celIndex, celIndex);
+        markDataValidationToSheet(helper, sheet, constraint, addressList);
+    }
+
+    /**
+     * 搴旂敤鏁版嵁鏍¢獙
+     */
+    private void markDataValidationToSheet(DataValidationHelper helper, Sheet sheet,
+                                           DataValidationConstraint constraint, CellRangeAddressList addressList) {
+        // 鏁版嵁鏈夋晥鎬у璞�
+        DataValidation dataValidation = helper.createValidation(constraint, addressList);
+        // 澶勭悊Excel鍏煎鎬ч棶棰�
+        if (dataValidation instanceof XSSFDataValidation) {
+            //鏁版嵁鏍¢獙
+            dataValidation.setSuppressDropDownArrow(true);
+            //閿欒鎻愮ず
+            dataValidation.setErrorStyle(DataValidation.ErrorStyle.STOP);
+            dataValidation.createErrorBox("鎻愮ず", "姝ゅ�间笌鍗曞厓鏍煎畾涔夋暟鎹笉涓�鑷�");
+            dataValidation.setShowErrorBox(true);
+            //閫夊畾鎻愮ず
+            dataValidation.createPromptBox("濉啓璇存槑锛�", "濉啓鍐呭鍙兘涓轰笅鎷変腑鏁版嵁锛屽叾浠栨暟鎹皢瀵艰嚧瀵煎叆澶辫触");
+            dataValidation.setShowPromptBox(true);
+            sheet.addValidationData(dataValidation);
+        } else {
+            dataValidation.setSuppressDropDownArrow(false);
+        }
+        sheet.addValidationData(dataValidation);
+    }
+
+    /**
+     * <h2>渚濇嵁鍒梚ndex鑾峰彇鍒楀悕鑻辨枃</h2>
+     * 渚濇嵁鍒梚ndex杞崲涓篍xcel涓殑鍒楀悕鑻辨枃
+     * <p>渚嬪绗�1鍒楋紝index涓�0锛岃В鏋愬嚭鏉ヤ负A鍒�</p>
+     * 绗�27鍒楋紝index涓�26锛岃В鏋愪负AA鍒�
+     * <p>绗�28鍒楋紝index涓�27锛岃В鏋愪负AB鍒�</p>
+     *
+     * @param columnIndex 鍒梚ndex
+     * @return 鍒梚ndex鎵�鍦ㄥ緱鑻辨枃鍚�
+     */
+    private String getExcelColumnName(int columnIndex) {
+        // 26涓�寰幆鐨勬鏁�
+        int columnCircleCount = columnIndex / 26;
+        // 26涓�寰幆鍐呯殑浣嶇疆
+        int thisCircleColumnIndex = columnIndex % 26;
+        // 26涓�寰幆鐨勬鏁板ぇ浜�0锛屽垯瑙嗕负鏍忓悕鑷冲皯涓や綅
+        String columnPrefix = columnCircleCount == 0
+            ? StrUtil.EMPTY
+            : StrUtil.subWithLength(EXCEL_COLUMN_NAME, columnCircleCount - 1, 1);
+        // 浠�26涓�寰幆鍐呭彇瀵瑰簲鐨勬爮浣嶅悕
+        String columnNext = StrUtil.subWithLength(EXCEL_COLUMN_NAME, thisCircleColumnIndex, 1);
+        // 灏嗕簩鑰呮嫾鎺ュ嵆涓烘渶缁堢殑鏍忎綅鍚�
+        return columnPrefix + columnNext;
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelListener.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelListener.java
new file mode 100644
index 0000000..2d0340f
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelListener.java
@@ -0,0 +1,14 @@
+package org.dromara.common.excel.core;
+
+import com.alibaba.excel.read.listener.ReadListener;
+
+/**
+ * Excel 瀵煎叆鐩戝惉
+ *
+ * @author Lion Li
+ */
+public interface ExcelListener<T> extends ReadListener<T> {
+
+    ExcelResult<T> getExcelResult();
+
+}
diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelResult.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelResult.java
new file mode 100644
index 0000000..0c2a418
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelResult.java
@@ -0,0 +1,26 @@
+package org.dromara.common.excel.core;
+
+import java.util.List;
+
+/**
+ * excel杩斿洖瀵硅薄
+ *
+ * @author Lion Li
+ */
+public interface ExcelResult<T> {
+
+    /**
+     * 瀵硅薄鍒楄〃
+     */
+    List<T> getList();
+
+    /**
+     * 閿欒鍒楄〃
+     */
+    List<String> getErrorList();
+
+    /**
+     * 瀵煎叆鍥炴墽
+     */
+    String getAnalysis();
+}
diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java
new file mode 100644
index 0000000..a6c14ad
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java
@@ -0,0 +1,436 @@
+package org.dromara.common.excel.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.io.resource.ClassPathResource;
+import cn.hutool.core.util.IdUtil;
+import com.alibaba.excel.EasyExcel;
+import com.alibaba.excel.ExcelWriter;
+import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
+import com.alibaba.excel.write.metadata.WriteSheet;
+import com.alibaba.excel.write.metadata.fill.FillConfig;
+import com.alibaba.excel.write.metadata.fill.FillWrapper;
+import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.file.FileUtils;
+import org.dromara.common.excel.convert.ExcelBigNumberConvert;
+import org.dromara.common.excel.core.*;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Excel鐩稿叧澶勭悊
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ExcelUtil {
+
+    /**
+     * 鍚屾瀵煎叆(閫傜敤浜庡皬鏁版嵁閲�)
+     *
+     * @param is 杈撳叆娴�
+     * @return 杞崲鍚庨泦鍚�
+     */
+    public static <T> List<T> importExcel(InputStream is, Class<T> clazz) {
+        return EasyExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
+    }
+
+
+    /**
+     * 浣跨敤鏍¢獙鐩戝惉鍣� 寮傛瀵煎叆 鍚屾杩斿洖
+     *
+     * @param is         杈撳叆娴�
+     * @param clazz      瀵硅薄绫诲瀷
+     * @param isValidate 鏄惁 Validator 妫�楠� 榛樿涓烘槸
+     * @return 杞崲鍚庨泦鍚�
+     */
+    public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, boolean isValidate) {
+        DefaultExcelListener<T> listener = new DefaultExcelListener<>(isValidate);
+        EasyExcel.read(is, clazz, listener).sheet().doRead();
+        return listener.getExcelResult();
+    }
+
+    /**
+     * 浣跨敤鑷畾涔夌洃鍚櫒 寮傛瀵煎叆 鑷畾涔夎繑鍥�
+     *
+     * @param is       杈撳叆娴�
+     * @param clazz    瀵硅薄绫诲瀷
+     * @param listener 鑷畾涔夌洃鍚櫒
+     * @return 杞崲鍚庨泦鍚�
+     */
+    public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, ExcelListener<T> listener) {
+        EasyExcel.read(is, clazz, listener).sheet().doRead();
+        return listener.getExcelResult();
+    }
+
+    /**
+     * 瀵煎嚭excel
+     *
+     * @param list      瀵煎嚭鏁版嵁闆嗗悎
+     * @param sheetName 宸ヤ綔琛ㄧ殑鍚嶇О
+     * @param clazz     瀹炰綋绫�
+     * @param response  鍝嶅簲浣�
+     */
+    public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response) {
+        try {
+            resetResponse(sheetName, response);
+            ServletOutputStream os = response.getOutputStream();
+            exportExcel(list, sheetName, clazz, false, os, null);
+        } catch (IOException e) {
+            throw new RuntimeException("瀵煎嚭Excel寮傚父");
+        }
+    }
+
+    /**
+     * 瀵煎嚭excel
+     *
+     * @param list      瀵煎嚭鏁版嵁闆嗗悎
+     * @param sheetName 宸ヤ綔琛ㄧ殑鍚嶇О
+     * @param clazz     瀹炰綋绫�
+     * @param response  鍝嶅簲浣�
+     * @param options   绾ц仈涓嬫媺閫�
+     */
+    public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response, List<DropDownOptions> options) {
+        try {
+            resetResponse(sheetName, response);
+            ServletOutputStream os = response.getOutputStream();
+            exportExcel(list, sheetName, clazz, false, os, options);
+        } catch (IOException e) {
+            throw new RuntimeException("瀵煎嚭Excel寮傚父");
+        }
+    }
+
+    /**
+     * 瀵煎嚭excel
+     *
+     * @param list      瀵煎嚭鏁版嵁闆嗗悎
+     * @param sheetName 宸ヤ綔琛ㄧ殑鍚嶇О
+     * @param clazz     瀹炰綋绫�
+     * @param merge     鏄惁鍚堝苟鍗曞厓鏍�
+     * @param response  鍝嶅簲浣�
+     */
+    public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge, HttpServletResponse response) {
+        try {
+            resetResponse(sheetName, response);
+            ServletOutputStream os = response.getOutputStream();
+            exportExcel(list, sheetName, clazz, merge, os, null);
+        } catch (IOException e) {
+            throw new RuntimeException("瀵煎嚭Excel寮傚父");
+        }
+    }
+
+    /**
+     * 瀵煎嚭excel
+     *
+     * @param list      瀵煎嚭鏁版嵁闆嗗悎
+     * @param sheetName 宸ヤ綔琛ㄧ殑鍚嶇О
+     * @param clazz     瀹炰綋绫�
+     * @param merge     鏄惁鍚堝苟鍗曞厓鏍�
+     * @param response  鍝嶅簲浣�
+     * @param options   绾ц仈涓嬫媺閫�
+     */
+    public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge, HttpServletResponse response, List<DropDownOptions> options) {
+        try {
+            resetResponse(sheetName, response);
+            ServletOutputStream os = response.getOutputStream();
+            exportExcel(list, sheetName, clazz, merge, os, options);
+        } catch (IOException e) {
+            throw new RuntimeException("瀵煎嚭Excel寮傚父");
+        }
+    }
+
+    /**
+     * 瀵煎嚭excel
+     *
+     * @param list      瀵煎嚭鏁版嵁闆嗗悎
+     * @param sheetName 宸ヤ綔琛ㄧ殑鍚嶇О
+     * @param clazz     瀹炰綋绫�
+     * @param os        杈撳嚭娴�
+     */
+    public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, OutputStream os) {
+        exportExcel(list, sheetName, clazz, false, os, null);
+    }
+
+    /**
+     * 瀵煎嚭excel
+     *
+     * @param list      瀵煎嚭鏁版嵁闆嗗悎
+     * @param sheetName 宸ヤ綔琛ㄧ殑鍚嶇О
+     * @param clazz     瀹炰綋绫�
+     * @param os        杈撳嚭娴�
+     * @param options   绾ц仈涓嬫媺閫夊唴瀹�
+     */
+    public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, OutputStream os, List<DropDownOptions> options) {
+        exportExcel(list, sheetName, clazz, false, os, options);
+    }
+
+    /**
+     * 瀵煎嚭excel
+     *
+     * @param list      瀵煎嚭鏁版嵁闆嗗悎
+     * @param sheetName 宸ヤ綔琛ㄧ殑鍚嶇О
+     * @param clazz     瀹炰綋绫�
+     * @param merge     鏄惁鍚堝苟鍗曞厓鏍�
+     * @param os        杈撳嚭娴�
+     */
+    public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge,
+                                       OutputStream os, List<DropDownOptions> options) {
+        ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz)
+            .autoCloseStream(false)
+            // 鑷姩閫傞厤
+            .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
+            // 澶ф暟鍊艰嚜鍔ㄨ浆鎹� 闃叉澶辩湡
+            .registerConverter(new ExcelBigNumberConvert())
+            .sheet(sheetName);
+        if (merge) {
+            // 鍚堝苟澶勭悊鍣�
+            builder.registerWriteHandler(new CellMergeStrategy(list, true));
+        }
+        // 娣诲姞涓嬫媺妗嗘搷浣�
+        builder.registerWriteHandler(new ExcelDownHandler(options));
+        builder.doWrite(list);
+    }
+
+    /**
+     * 鍗曡〃澶氭暟鎹ā鏉垮鍑� 妯℃澘鏍煎紡涓� {.灞炴�
+     *
+     * @param filename     鏂囦欢鍚�
+     * @param templatePath 妯℃澘璺緞 resource 鐩綍涓嬬殑璺緞鍖呮嫭妯℃澘鏂囦欢鍚�
+     *                     渚嬪: excel/temp.xlsx
+     *                     閲嶇偣: 妯℃澘鏂囦欢蹇呴』鏀剧疆鍒板惎鍔ㄧ被瀵瑰簲鐨� resource 鐩綍涓�
+     * @param data         妯℃澘闇�瑕佺殑鏁版嵁
+     * @param response     鍝嶅簲浣�
+     */
+    public static void exportTemplate(List<Object> data, String filename, String templatePath, HttpServletResponse response) {
+        try {
+            resetResponse(filename, response);
+            ServletOutputStream os = response.getOutputStream();
+            exportTemplate(data, templatePath, os);
+        } catch (IOException e) {
+            throw new RuntimeException("瀵煎嚭Excel寮傚父");
+        }
+    }
+
+    /**
+     * 鍗曡〃澶氭暟鎹ā鏉垮鍑� 妯℃澘鏍煎紡涓� {.灞炴�
+     *
+     * @param templatePath 妯℃澘璺緞 resource 鐩綍涓嬬殑璺緞鍖呮嫭妯℃澘鏂囦欢鍚�
+     *                     渚嬪: excel/temp.xlsx
+     *                     閲嶇偣: 妯℃澘鏂囦欢蹇呴』鏀剧疆鍒板惎鍔ㄧ被瀵瑰簲鐨� resource 鐩綍涓�
+     * @param data         妯℃澘闇�瑕佺殑鏁版嵁
+     * @param os           杈撳嚭娴�
+     */
+    public static void exportTemplate(List<Object> data, String templatePath, OutputStream os) {
+        ClassPathResource templateResource = new ClassPathResource(templatePath);
+        ExcelWriter excelWriter = EasyExcel.write(os)
+            .withTemplate(templateResource.getStream())
+            .autoCloseStream(false)
+            // 澶ф暟鍊艰嚜鍔ㄨ浆鎹� 闃叉澶辩湡
+            .registerConverter(new ExcelBigNumberConvert())
+            .build();
+        WriteSheet writeSheet = EasyExcel.writerSheet().build();
+        if (CollUtil.isEmpty(data)) {
+            throw new IllegalArgumentException("鏁版嵁涓虹┖");
+        }
+        // 鍗曡〃澶氭暟鎹鍑� 妯℃澘鏍煎紡涓� {.灞炴�
+        for (Object d : data) {
+            excelWriter.fill(d, writeSheet);
+        }
+        excelWriter.finish();
+    }
+
+    /**
+     * 澶氳〃澶氭暟鎹ā鏉垮鍑� 妯℃澘鏍煎紡涓� {key.灞炴�
+     *
+     * @param filename     鏂囦欢鍚�
+     * @param templatePath 妯℃澘璺緞 resource 鐩綍涓嬬殑璺緞鍖呮嫭妯℃澘鏂囦欢鍚�
+     *                     渚嬪: excel/temp.xlsx
+     *                     閲嶇偣: 妯℃澘鏂囦欢蹇呴』鏀剧疆鍒板惎鍔ㄧ被瀵瑰簲鐨� resource 鐩綍涓�
+     * @param data         妯℃澘闇�瑕佺殑鏁版嵁
+     * @param response     鍝嶅簲浣�
+     */
+    public static void exportTemplateMultiList(Map<String, Object> data, String filename, String templatePath, HttpServletResponse response) {
+        try {
+            resetResponse(filename, response);
+            ServletOutputStream os = response.getOutputStream();
+            exportTemplateMultiList(data, templatePath, os);
+        } catch (IOException e) {
+            throw new RuntimeException("瀵煎嚭Excel寮傚父");
+        }
+    }
+
+    /**
+     * 澶歴heet妯℃澘瀵煎嚭 妯℃澘鏍煎紡涓� {key.灞炴�
+     *
+     * @param filename     鏂囦欢鍚�
+     * @param templatePath 妯℃澘璺緞 resource 鐩綍涓嬬殑璺緞鍖呮嫭妯℃澘鏂囦欢鍚�
+     *                     渚嬪: excel/temp.xlsx
+     *                     閲嶇偣: 妯℃澘鏂囦欢蹇呴』鏀剧疆鍒板惎鍔ㄧ被瀵瑰簲鐨� resource 鐩綍涓�
+     * @param data         妯℃澘闇�瑕佺殑鏁版嵁
+     * @param response     鍝嶅簲浣�
+     */
+    public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String filename, String templatePath, HttpServletResponse response) {
+        try {
+            resetResponse(filename, response);
+            ServletOutputStream os = response.getOutputStream();
+            exportTemplateMultiSheet(data, templatePath, os);
+        } catch (IOException e) {
+            throw new RuntimeException("瀵煎嚭Excel寮傚父");
+        }
+    }
+
+    /**
+     * 澶氳〃澶氭暟鎹ā鏉垮鍑� 妯℃澘鏍煎紡涓� {key.灞炴�
+     *
+     * @param templatePath 妯℃澘璺緞 resource 鐩綍涓嬬殑璺緞鍖呮嫭妯℃澘鏂囦欢鍚�
+     *                     渚嬪: excel/temp.xlsx
+     *                     閲嶇偣: 妯℃澘鏂囦欢蹇呴』鏀剧疆鍒板惎鍔ㄧ被瀵瑰簲鐨� resource 鐩綍涓�
+     * @param data         妯℃澘闇�瑕佺殑鏁版嵁
+     * @param os           杈撳嚭娴�
+     */
+    public static void exportTemplateMultiList(Map<String, Object> data, String templatePath, OutputStream os) {
+        ClassPathResource templateResource = new ClassPathResource(templatePath);
+        ExcelWriter excelWriter = EasyExcel.write(os)
+            .withTemplate(templateResource.getStream())
+            .autoCloseStream(false)
+            // 澶ф暟鍊艰嚜鍔ㄨ浆鎹� 闃叉澶辩湡
+            .registerConverter(new ExcelBigNumberConvert())
+            .build();
+        WriteSheet writeSheet = EasyExcel.writerSheet().build();
+        if (CollUtil.isEmpty(data)) {
+            throw new IllegalArgumentException("鏁版嵁涓虹┖");
+        }
+        for (Map.Entry<String, Object> map : data.entrySet()) {
+            // 璁剧疆鍒楄〃鍚庣画杩樻湁鏁版嵁
+            FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
+            if (map.getValue() instanceof Collection) {
+                // 澶氳〃瀵煎嚭蹇呴』浣跨敤 FillWrapper
+                excelWriter.fill(new FillWrapper(map.getKey(), (Collection<?>) map.getValue()), fillConfig, writeSheet);
+            } else {
+                excelWriter.fill(map.getValue(), writeSheet);
+            }
+        }
+        excelWriter.finish();
+    }
+
+    /**
+     * 澶歴heet妯℃澘瀵煎嚭 妯℃澘鏍煎紡涓� {key.灞炴�
+     *
+     * @param templatePath 妯℃澘璺緞 resource 鐩綍涓嬬殑璺緞鍖呮嫭妯℃澘鏂囦欢鍚�
+     *                     渚嬪: excel/temp.xlsx
+     *                     閲嶇偣: 妯℃澘鏂囦欢蹇呴』鏀剧疆鍒板惎鍔ㄧ被瀵瑰簲鐨� resource 鐩綍涓�
+     * @param data         妯℃澘闇�瑕佺殑鏁版嵁
+     * @param os           杈撳嚭娴�
+     */
+    public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String templatePath, OutputStream os) {
+        ClassPathResource templateResource = new ClassPathResource(templatePath);
+        ExcelWriter excelWriter = EasyExcel.write(os)
+            .withTemplate(templateResource.getStream())
+            .autoCloseStream(false)
+            // 澶ф暟鍊艰嚜鍔ㄨ浆鎹� 闃叉澶辩湡
+            .registerConverter(new ExcelBigNumberConvert())
+            .build();
+        if (CollUtil.isEmpty(data)) {
+            throw new IllegalArgumentException("鏁版嵁涓虹┖");
+        }
+        for (int i = 0; i < data.size(); i++) {
+            WriteSheet writeSheet = EasyExcel.writerSheet(i).build();
+            for (Map.Entry<String, Object> map : data.get(i).entrySet()) {
+                // 璁剧疆鍒楄〃鍚庣画杩樻湁鏁版嵁
+                FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
+                if (map.getValue() instanceof Collection) {
+                    // 澶氳〃瀵煎嚭蹇呴』浣跨敤 FillWrapper
+                    excelWriter.fill(new FillWrapper(map.getKey(), (Collection<?>) map.getValue()), fillConfig, writeSheet);
+                } else {
+                    excelWriter.fill(map.getValue(), writeSheet);
+                }
+            }
+        }
+        excelWriter.finish();
+    }
+
+    /**
+     * 閲嶇疆鍝嶅簲浣�
+     */
+    private static void resetResponse(String sheetName, HttpServletResponse response) throws UnsupportedEncodingException {
+        String filename = encodingFilename(sheetName);
+        FileUtils.setAttachmentResponseHeader(response, filename);
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
+    }
+
+    /**
+     * 瑙f瀽瀵煎嚭鍊� 0=鐢�,1=濂�,2=鏈煡
+     *
+     * @param propertyValue 鍙傛暟鍊�
+     * @param converterExp  缈昏瘧娉ㄨВ
+     * @param separator     鍒嗛殧绗�
+     * @return 瑙f瀽鍚庡��
+     */
+    public static String convertByExp(String propertyValue, String converterExp, String separator) {
+        StringBuilder propertyString = new StringBuilder();
+        String[] convertSource = converterExp.split(StringUtils.SEPARATOR);
+        for (String item : convertSource) {
+            String[] itemArray = item.split("=");
+            if (StringUtils.containsAny(propertyValue, separator)) {
+                for (String value : propertyValue.split(separator)) {
+                    if (itemArray[0].equals(value)) {
+                        propertyString.append(itemArray[1] + separator);
+                        break;
+                    }
+                }
+            } else {
+                if (itemArray[0].equals(propertyValue)) {
+                    return itemArray[1];
+                }
+            }
+        }
+        return StringUtils.stripEnd(propertyString.toString(), separator);
+    }
+
+    /**
+     * 鍙嶅悜瑙f瀽鍊� 鐢�=0,濂�=1,鏈煡=2
+     *
+     * @param propertyValue 鍙傛暟鍊�
+     * @param converterExp  缈昏瘧娉ㄨВ
+     * @param separator     鍒嗛殧绗�
+     * @return 瑙f瀽鍚庡��
+     */
+    public static String reverseByExp(String propertyValue, String converterExp, String separator) {
+        StringBuilder propertyString = new StringBuilder();
+        String[] convertSource = converterExp.split(StringUtils.SEPARATOR);
+        for (String item : convertSource) {
+            String[] itemArray = item.split("=");
+            if (StringUtils.containsAny(propertyValue, separator)) {
+                for (String value : propertyValue.split(separator)) {
+                    if (itemArray[1].equals(value)) {
+                        propertyString.append(itemArray[0] + separator);
+                        break;
+                    }
+                }
+            } else {
+                if (itemArray[1].equals(propertyValue)) {
+                    return itemArray[0];
+                }
+            }
+        }
+        return StringUtils.stripEnd(propertyString.toString(), separator);
+    }
+
+    /**
+     * 缂栫爜鏂囦欢鍚�
+     */
+    public static String encodingFilename(String filename) {
+        return IdUtil.fastSimpleUUID() + "_" + filename + ".xlsx";
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-idempotent/pom.xml b/ruoyi-common/ruoyi-common-idempotent/pom.xml
new file mode 100644
index 0000000..64418b4
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-idempotent/pom.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-idempotent</artifactId>
+
+    <description>
+        ruoyi-common-idempotent 骞傜瓑鍔熻兘
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-json</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-redis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-crypto</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-core</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>
diff --git a/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/annotation/RepeatSubmit.java b/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/annotation/RepeatSubmit.java
new file mode 100644
index 0000000..42ae802
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/annotation/RepeatSubmit.java
@@ -0,0 +1,29 @@
+package org.dromara.common.idempotent.annotation;
+
+import java.lang.annotation.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 鑷畾涔夋敞瑙i槻姝㈣〃鍗曢噸澶嶆彁浜�
+ *
+ * @author Lion Li
+ */
+@Inherited
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RepeatSubmit {
+
+    /**
+     * 闂撮殧鏃堕棿(ms)锛屽皬浜庢鏃堕棿瑙嗕负閲嶅鎻愪氦
+     */
+    int interval() default 5000;
+
+    TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
+
+    /**
+     * 鎻愮ず娑堟伅 鏀寔鍥介檯鍖� 鏍煎紡涓� {code}
+     */
+    String message() default "{repeat.submit.message}";
+
+}
diff --git a/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/aspectj/RepeatSubmitAspect.java b/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/aspectj/RepeatSubmitAspect.java
new file mode 100644
index 0000000..fc53b1b
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/aspectj/RepeatSubmitAspect.java
@@ -0,0 +1,146 @@
+package org.dromara.common.idempotent.aspectj;
+
+import cn.dev33.satoken.SaManager;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.crypto.SecureUtil;
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.MessageUtils;
+import org.dromara.common.core.utils.ServletUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.redis.utils.RedisUtils;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.AfterReturning;
+import org.aspectj.lang.annotation.AfterThrowing;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.time.Duration;
+import java.util.Collection;
+import java.util.Map;
+import java.util.StringJoiner;
+
+/**
+ * 闃叉閲嶅鎻愪氦(鍙傝�冪編鍥TIS闃查噸绯荤粺)
+ *
+ * @author Lion Li
+ */
+@Aspect
+public class RepeatSubmitAspect {
+
+    private static final ThreadLocal<String> KEY_CACHE = new ThreadLocal<>();
+
+    @Before("@annotation(repeatSubmit)")
+    public void doBefore(JoinPoint point, RepeatSubmit repeatSubmit) throws Throwable {
+        // 濡傛灉娉ㄨВ涓嶄负0 鍒欎娇鐢ㄦ敞瑙f暟鍊�
+        long interval = repeatSubmit.timeUnit().toMillis(repeatSubmit.interval());
+
+        if (interval < 1000) {
+            throw new ServiceException("閲嶅鎻愪氦闂撮殧鏃堕棿涓嶈兘灏忎簬'1'绉�");
+        }
+        HttpServletRequest request = ServletUtils.getRequest();
+        String nowParams = argsArrayToString(point.getArgs());
+
+        // 璇锋眰鍦板潃锛堜綔涓哄瓨鏀綾ache鐨刱ey鍊硷級
+        String url = request.getRequestURI();
+
+        // 鍞竴鍊硷紙娌℃湁娑堟伅澶村垯浣跨敤璇锋眰鍦板潃锛�
+        String submitKey = StringUtils.trimToEmpty(request.getHeader(SaManager.getConfig().getTokenName()));
+
+        submitKey = SecureUtil.md5(submitKey + ":" + nowParams);
+        // 鍞竴鏍囪瘑锛堟寚瀹歬ey + url + 娑堟伅澶达級
+        String cacheRepeatKey = GlobalConstants.REPEAT_SUBMIT_KEY + url + submitKey;
+        if (RedisUtils.setObjectIfAbsent(cacheRepeatKey, "", Duration.ofMillis(interval))) {
+            KEY_CACHE.set(cacheRepeatKey);
+        } else {
+            String message = repeatSubmit.message();
+            if (StringUtils.startsWith(message, "{") && StringUtils.endsWith(message, "}")) {
+                message = MessageUtils.message(StringUtils.substring(message, 1, message.length() - 1));
+            }
+            throw new ServiceException(message);
+        }
+    }
+
+    /**
+     * 澶勭悊瀹岃姹傚悗鎵ц
+     *
+     * @param joinPoint 鍒囩偣
+     */
+    @AfterReturning(pointcut = "@annotation(repeatSubmit)", returning = "jsonResult")
+    public void doAfterReturning(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Object jsonResult) {
+        if (jsonResult instanceof R<?> r) {
+            try {
+                // 鎴愬姛鍒欎笉鍒犻櫎redis鏁版嵁 淇濊瘉鍦ㄦ湁鏁堟椂闂村唴鏃犳硶閲嶅鎻愪氦
+                if (r.getCode() == R.SUCCESS) {
+                    return;
+                }
+                RedisUtils.deleteObject(KEY_CACHE.get());
+            } finally {
+                KEY_CACHE.remove();
+            }
+        }
+    }
+
+    /**
+     * 鎷︽埅寮傚父鎿嶄綔
+     *
+     * @param joinPoint 鍒囩偣
+     * @param e         寮傚父
+     */
+    @AfterThrowing(value = "@annotation(repeatSubmit)", throwing = "e")
+    public void doAfterThrowing(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Exception e) {
+        RedisUtils.deleteObject(KEY_CACHE.get());
+        KEY_CACHE.remove();
+    }
+
+    /**
+     * 鍙傛暟鎷艰
+     */
+    private String argsArrayToString(Object[] paramsArray) {
+        StringJoiner params = new StringJoiner(" ");
+        if (ArrayUtil.isEmpty(paramsArray)) {
+            return params.toString();
+        }
+        for (Object o : paramsArray) {
+            if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
+                params.add(JsonUtils.toJsonString(o));
+            }
+        }
+        return params.toString();
+    }
+
+    /**
+     * 鍒ゆ柇鏄惁闇�瑕佽繃婊ょ殑瀵硅薄銆�
+     *
+     * @param o 瀵硅薄淇℃伅銆�
+     * @return 濡傛灉鏄渶瑕佽繃婊ょ殑瀵硅薄锛屽垯杩斿洖true锛涘惁鍒欒繑鍥瀎alse銆�
+     */
+    @SuppressWarnings("rawtypes")
+    public boolean isFilterObject(final Object o) {
+        Class<?> clazz = o.getClass();
+        if (clazz.isArray()) {
+            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
+        } else if (Collection.class.isAssignableFrom(clazz)) {
+            Collection collection = (Collection) o;
+            for (Object value : collection) {
+                return value instanceof MultipartFile;
+            }
+        } else if (Map.class.isAssignableFrom(clazz)) {
+            Map map = (Map) o;
+            for (Object value : map.values()) {
+                return value instanceof MultipartFile;
+            }
+        }
+        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
+            || o instanceof BindingResult;
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/config/IdempotentAutoConfiguration.java b/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/config/IdempotentAutoConfiguration.java
new file mode 100644
index 0000000..e8b785c
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-idempotent/src/main/java/org/dromara/common/idempotent/config/IdempotentAutoConfiguration.java
@@ -0,0 +1,21 @@
+package org.dromara.common.idempotent.config;
+
+import org.dromara.common.idempotent.aspectj.RepeatSubmitAspect;
+import org.dromara.common.redis.config.RedisConfiguration;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * 骞傜瓑鍔熻兘閰嶇疆
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration(after = RedisConfiguration.class)
+public class IdempotentAutoConfiguration {
+
+	@Bean
+	public RepeatSubmitAspect repeatSubmitAspect() {
+		return new RepeatSubmitAspect();
+	}
+
+}
diff --git a/ruoyi-common/ruoyi-common-idempotent/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-idempotent/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..fc57da7
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-idempotent/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.idempotent.config.IdempotentAutoConfiguration
diff --git a/ruoyi-common/ruoyi-common-job/pom.xml b/ruoyi-common/ruoyi-common-job/pom.xml
new file mode 100644
index 0000000..0df7fee
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-job/pom.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-job</artifactId>
+
+    <description>
+        ruoyi-common-job 瀹氭椂浠诲姟
+    </description>
+
+	<dependencies>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-autoconfigure</artifactId>
+        </dependency>
+
+        <!-- 鏈嶅姟鍙戠幇缁勪欢 -->
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-commons</artifactId>
+        </dependency>
+
+        <!--PowerJob-->
+        <dependency>
+            <groupId>tech.powerjob</groupId>
+            <artifactId>powerjob-worker</artifactId>
+            <exclusions>
+                <exclusion>
+                    <artifactId>powerjob-remote-impl-akka</artifactId>
+                    <groupId>tech.powerjob</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>tech.powerjob</groupId>
+            <artifactId>powerjob-official-processors</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-core</artifactId>
+        </dependency>
+
+    </dependencies>
+</project>
diff --git a/ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/PowerJobConfig.java b/ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/PowerJobConfig.java
new file mode 100644
index 0000000..35612f3
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/PowerJobConfig.java
@@ -0,0 +1,107 @@
+package org.dromara.common.job.config;
+
+import cn.hutool.core.collection.CollUtil;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.job.config.properties.PowerJobProperties;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.client.discovery.DiscoveryClient;
+import org.springframework.context.annotation.Bean;
+import tech.powerjob.common.utils.CommonUtils;
+import tech.powerjob.common.utils.NetUtils;
+import tech.powerjob.worker.PowerJobSpringWorker;
+import tech.powerjob.worker.common.PowerJobWorkerConfig;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Autoconfiguration class for PowerJob-worker.
+ *
+ * @author songyinyin
+ * @since 2020/7/26 16:37
+ */
+@AutoConfiguration
+@EnableConfigurationProperties(PowerJobProperties.class)
+@ConditionalOnProperty(prefix = "powerjob.worker", name = "enabled", havingValue = "true", matchIfMissing = true)
+public class PowerJobConfig{
+
+    @Bean
+    public PowerJobSpringWorker initPowerJob(PowerJobProperties properties, DiscoveryClient discoveryClient) {
+
+        PowerJobProperties.Worker worker = properties.getWorker();
+
+        /*
+         * Address of PowerJob-server node(s). Do not mistake for ActorSystem port. Do not add
+         * any prefix, i.e. http://.
+         */
+        List<String> serverAddress;
+        if (StringUtils.isNotBlank(worker.getServerName())) {
+            List<ServiceInstance> instances = discoveryClient.getInstances(worker.getServerName());
+            if (CollUtil.isEmpty(instances)) {
+                throw new RuntimeException("璋冨害涓績涓嶅瓨鍦�!");
+            }
+            serverAddress = StreamUtils.toList(instances, instance ->
+                String.format("%s:%s", instance.getHost(), instance.getPort()));
+        } else {
+            CommonUtils.requireNonNull(worker.getServerAddress(), "serverAddress can't be empty! " +
+                "if you don't want to enable powerjob, please config program arguments: powerjob.worker.enabled=false");
+            serverAddress = Arrays.asList(worker.getServerAddress().split(","));
+        }
+        /*
+         * Create OhMyConfig object for setting properties.
+         */
+        PowerJobWorkerConfig config = new PowerJobWorkerConfig();
+        /*
+         * Configuration of worker port. Random port is enabled when port is set with non-positive number.
+         */
+        if (worker.getPort() != null) {
+            config.setPort(worker.getPort());
+        } else {
+            int port = worker.getAkkaPort();
+            if (port <= 0) {
+                port = NetUtils.getRandomPort();
+            }
+            config.setPort(port);
+        }
+        /*
+         * appName, name of the application. Applications should be registered in advance to prevent
+         * error. This property should be the same with what you entered for appName when getting
+         * registered.
+         */
+        config.setAppName(worker.getAppName());
+        config.setServerAddress(serverAddress);
+        config.setProtocol(worker.getProtocol());
+        /*
+         * For non-Map/MapReduce tasks, {@code memory} is recommended for speeding up calculation.
+         * Map/MapReduce tasks may produce batches of subtasks, which could lead to OutOfMemory
+         * exception or error, {@code disk} should be applied.
+         */
+        config.setStoreStrategy(worker.getStoreStrategy());
+        /*
+         * When enabledTestMode is set as true, PowerJob-worker no longer connects to PowerJob-server
+         * or validate appName.
+         */
+        config.setAllowLazyConnectServer(worker.isAllowLazyConnectServer());
+        /*
+         * Max length of appended workflow context . Appended workflow context value that is longer than the value will be ignored.
+         */
+        config.setMaxAppendedWfContextLength(worker.getMaxAppendedWfContextLength());
+
+        config.setTag(worker.getTag());
+
+        config.setMaxHeavyweightTaskNum(worker.getMaxHeavyweightTaskNum());
+
+        config.setMaxLightweightTaskNum(worker.getMaxLightweightTaskNum());
+
+        config.setHealthReportInterval(worker.getHealthReportInterval());
+        /*
+         * Create PowerJobSpringWorker object and set properties.
+         */
+        return new PowerJobSpringWorker(config);
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/properties/PowerJobProperties.java b/ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/properties/PowerJobProperties.java
new file mode 100644
index 0000000..df5f285
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-job/src/main/java/org/dromara/common/job/config/properties/PowerJobProperties.java
@@ -0,0 +1,109 @@
+package org.dromara.common.job.config.properties;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import tech.powerjob.common.RemoteConstant;
+import tech.powerjob.common.enums.Protocol;
+import tech.powerjob.worker.common.constants.StoreStrategy;
+import tech.powerjob.worker.core.processor.ProcessResult;
+import tech.powerjob.worker.core.processor.WorkflowContext;
+
+/**
+ * PowerJob properties configuration class.
+ *
+ * @author songyinyin
+ * @since 2020/7/26 16:37
+ */
+@ConfigurationProperties(prefix = "powerjob")
+public class PowerJobProperties {
+
+    private final Worker worker = new Worker();
+
+    public Worker getWorker() {
+        return worker;
+    }
+
+    /**
+     * Powerjob worker configuration properties.
+     */
+    @Setter
+    @Getter
+    public static class Worker {
+
+        /**
+         * Whether to enable PowerJob Worker
+         */
+        private boolean enabled = true;
+
+        /**
+         * Name of application, String type. Total length of this property should be no more than 255
+         * characters. This is one of the required properties when registering a new application. This
+         * property should be assigned with the same value as what you entered for the appName.
+         */
+        private String appName;
+        /**
+         * Akka port of Powerjob-worker, optional value. Default value of this property is 27777.
+         * If multiple PowerJob-worker nodes were deployed, different, unique ports should be assigned.
+         * Deprecated, please use 'port'
+         */
+        @Deprecated
+        private int akkaPort = RemoteConstant.DEFAULT_WORKER_PORT;
+        /**
+         * port
+         */
+        private Integer port;
+        /**
+         * Address(es) of Powerjob-server node(s). Ip:port or domain.
+         * Example of single Powerjob-server node:
+         * <p>
+         * 127.0.0.1:7700
+         * </p>
+         * Example of Powerjob-server cluster:
+         * <p>
+         * 192.168.0.10:7700,192.168.0.11:7700,192.168.0.12:7700
+         * </p>
+         */
+        private String serverAddress;
+
+        private String serverName;
+        /**
+         * Protocol for communication between WORKER and server
+         */
+        private Protocol protocol = Protocol.AKKA;
+        /**
+         * Local store strategy for H2 database. {@code disk} or {@code memory}.
+         */
+        private StoreStrategy storeStrategy = StoreStrategy.DISK;
+        /**
+         * Max length of response result. Result that is longer than the value will be truncated.
+         * {@link ProcessResult} max length for #msg
+         */
+        private int maxResultLength = 8192;
+        /**
+         * If allowLazyConnectServer is set as true, PowerJob worker allows launching without a direct connection to the server.
+         * allowLazyConnectServer is used for conditions that your have no powerjob-server in your develop env so you can't startup the application
+         */
+        private boolean allowLazyConnectServer = false;
+        /**
+         * Max length of appended workflow context value length. Appended workflow context value that is longer than the value will be ignored.
+         * {@link WorkflowContext} max length for #appendedContextData
+         */
+        private int maxAppendedWfContextLength = 8192;
+
+        private String tag;
+        /**
+         * Max numbers of LightTaskTacker
+         */
+        private Integer maxLightweightTaskNum = 1024;
+        /**
+         * Max numbers of HeavyTaskTacker
+         */
+        private Integer maxHeavyweightTaskNum = 64;
+        /**
+         * Interval(s) of worker health report
+         */
+        private Integer healthReportInterval = 10;
+
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-job/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-job/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..222bcc1
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-job/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.job.config.PowerJobConfig
diff --git a/ruoyi-common/ruoyi-common-json/pom.xml b/ruoyi-common/ruoyi-common-json/pom.xml
new file mode 100644
index 0000000..870df5c
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-json/pom.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-json</artifactId>
+
+    <description>
+        ruoyi-common-json 搴忓垪鍖栨ā鍧�
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-core</artifactId>
+        </dependency>
+
+        <!-- JSON宸ュ叿绫� -->
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-jsr310</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>
diff --git a/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/config/JacksonConfig.java b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/config/JacksonConfig.java
new file mode 100644
index 0000000..8f5a45d
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/config/JacksonConfig.java
@@ -0,0 +1,47 @@
+package org.dromara.common.json.config;
+
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
+import org.dromara.common.json.handler.BigNumberSerializer;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
+import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.TimeZone;
+
+/**
+ * jackson 閰嶇疆
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@AutoConfiguration(before = JacksonAutoConfiguration.class)
+public class JacksonConfig {
+
+    @Bean
+    public Jackson2ObjectMapperBuilderCustomizer customizer() {
+        return builder -> {
+            // 鍏ㄥ眬閰嶇疆搴忓垪鍖栬繑鍥� JSON 澶勭悊
+            JavaTimeModule javaTimeModule = new JavaTimeModule();
+            javaTimeModule.addSerializer(Long.class, BigNumberSerializer.INSTANCE);
+            javaTimeModule.addSerializer(Long.TYPE, BigNumberSerializer.INSTANCE);
+            javaTimeModule.addSerializer(BigInteger.class, BigNumberSerializer.INSTANCE);
+            javaTimeModule.addSerializer(BigDecimal.class, ToStringSerializer.instance);
+            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+            javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
+            javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
+            builder.modules(javaTimeModule);
+            builder.timeZone(TimeZone.getDefault());
+            log.info("鍒濆鍖� jackson 閰嶇疆");
+        };
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/handler/BigNumberSerializer.java b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/handler/BigNumberSerializer.java
new file mode 100644
index 0000000..f2a7c2d
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/handler/BigNumberSerializer.java
@@ -0,0 +1,42 @@
+package org.dromara.common.json.handler;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
+import com.fasterxml.jackson.databind.ser.std.NumberSerializer;
+
+import java.io.IOException;
+
+/**
+ * 瓒呭嚭 JS 鏈�澶ф渶灏忓�� 澶勭悊
+ *
+ * @author Lion Li
+ */
+@JacksonStdImpl
+public class BigNumberSerializer extends NumberSerializer {
+
+    /**
+     * 鏍规嵁 JS Number.MAX_SAFE_INTEGER 涓� Number.MIN_SAFE_INTEGER 寰楁潵
+     */
+    private static final long MAX_SAFE_INTEGER = 9007199254740991L;
+    private static final long MIN_SAFE_INTEGER = -9007199254740991L;
+
+    /**
+     * 鎻愪緵瀹炰緥
+     */
+    public static final BigNumberSerializer INSTANCE = new BigNumberSerializer(Number.class);
+
+    public BigNumberSerializer(Class<? extends Number> rawType) {
+        super(rawType);
+    }
+
+    @Override
+    public void serialize(Number value, JsonGenerator gen, SerializerProvider provider) throws IOException {
+        // 瓒呭嚭鑼冨洿 搴忓垪鍖栦綅瀛楃涓�
+        if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) {
+            super.serialize(value, gen, provider);
+        } else {
+            gen.writeString(value.toString());
+        }
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/utils/JsonUtils.java b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/utils/JsonUtils.java
new file mode 100644
index 0000000..42af8da
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-json/src/main/java/org/dromara/common/json/utils/JsonUtils.java
@@ -0,0 +1,113 @@
+package org.dromara.common.json.utils;
+
+import cn.hutool.core.lang.Dict;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.exc.MismatchedInputException;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * JSON 宸ュ叿绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class JsonUtils {
+
+    private static final ObjectMapper OBJECT_MAPPER = SpringUtils.getBean(ObjectMapper.class);
+
+    public static ObjectMapper getObjectMapper() {
+        return OBJECT_MAPPER;
+    }
+
+    public static String toJsonString(Object object) {
+        if (ObjectUtil.isNull(object)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.writeValueAsString(object);
+        } catch (JsonProcessingException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static <T> T parseObject(String text, Class<T> clazz) {
+        if (StringUtils.isEmpty(text)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.readValue(text, clazz);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static <T> T parseObject(byte[] bytes, Class<T> clazz) {
+        if (ArrayUtil.isEmpty(bytes)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.readValue(bytes, clazz);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static <T> T parseObject(String text, TypeReference<T> typeReference) {
+        if (StringUtils.isBlank(text)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.readValue(text, typeReference);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static Dict parseMap(String text) {
+        if (StringUtils.isBlank(text)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructType(Dict.class));
+        } catch (MismatchedInputException e) {
+            // 绫诲瀷涓嶅尮閰嶈鏄庝笉鏄痡son
+            return null;
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static List<Dict> parseArrayMap(String text) {
+        if (StringUtils.isBlank(text)) {
+            return null;
+        }
+        try {
+            return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, Dict.class));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static <T> List<T> parseArray(String text, Class<T> clazz) {
+        if (StringUtils.isEmpty(text)) {
+            return new ArrayList<>();
+        }
+        try {
+            return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-json/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-json/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..1625397
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-json/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.json.config.JacksonConfig
diff --git a/ruoyi-common/ruoyi-common-loadbalancer/pom.xml b/ruoyi-common/ruoyi-common-loadbalancer/pom.xml
new file mode 100644
index 0000000..1509a44
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-loadbalancer/pom.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-loadbalancer</artifactId>
+
+    <description>
+        ruoyi-common-loadbalancer 鑷畾涔夎礋杞藉潎琛�(澶氬洟闃熷紑鍙戜娇鐢�)
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-spring-boot-starter</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-core</artifactId>
+        </dependency>
+
+    </dependencies>
+</project>
diff --git a/ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/config/CustomEnvironmentPostProcessor.java b/ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/config/CustomEnvironmentPostProcessor.java
new file mode 100644
index 0000000..7811238
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/config/CustomEnvironmentPostProcessor.java
@@ -0,0 +1,25 @@
+package org.dromara.common.loadbalance.config;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.env.EnvironmentPostProcessor;
+import org.springframework.core.Ordered;
+import org.springframework.core.env.ConfigurableEnvironment;
+
+/**
+ * dubbo鑷畾涔夎礋杞藉潎琛¢厤缃敞鍏�
+ *
+ * @author Lion Li
+ */
+public class CustomEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
+
+	@Override
+	public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
+        System.setProperty("dubbo.consumer.loadbalance", "customDubboLoadBalancer");
+	}
+
+	@Override
+	public int getOrder() {
+		return Ordered.HIGHEST_PRECEDENCE;
+	}
+
+}
diff --git a/ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/config/CustomLoadBalanceAutoConfiguration.java b/ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/config/CustomLoadBalanceAutoConfiguration.java
new file mode 100644
index 0000000..a7d86c0
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/config/CustomLoadBalanceAutoConfiguration.java
@@ -0,0 +1,13 @@
+package org.dromara.common.loadbalance.config;
+
+import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
+
+/**
+ * 鑷畾涔夎礋杞藉潎琛¤嚜鍔ㄩ厤缃�
+ *
+ * @author Lion Li
+ */
+@LoadBalancerClients(defaultConfiguration = CustomLoadBalanceClientConfiguration.class)
+public class CustomLoadBalanceAutoConfiguration {
+
+}
diff --git a/ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/config/CustomLoadBalanceClientConfiguration.java b/ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/config/CustomLoadBalanceClientConfiguration.java
new file mode 100644
index 0000000..753a14a
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/config/CustomLoadBalanceClientConfiguration.java
@@ -0,0 +1,30 @@
+package org.dromara.common.loadbalance.config;
+
+import org.dromara.common.loadbalance.core.CustomSpringCloudLoadBalancer;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
+import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
+import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.env.Environment;
+
+/**
+ * 鑷畾涔夎礋杞藉潎琛″鎴风閰嶇疆
+ *
+ * @author LionLi
+ */
+@SuppressWarnings("all")
+@Configuration(proxyBeanMethods = false)
+public class CustomLoadBalanceClientConfiguration {
+
+    @Bean
+    @ConditionalOnBean(LoadBalancerClientFactory.class)
+    public ReactorLoadBalancer<ServiceInstance> customLoadBalancer(Environment environment,
+                                                                   LoadBalancerClientFactory loadBalancerClientFactory) {
+        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
+        return new CustomSpringCloudLoadBalancer(name,
+            loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class));
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/core/CustomDubboLoadBalancer.java b/ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/core/CustomDubboLoadBalancer.java
new file mode 100644
index 0000000..1d3337f
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/core/CustomDubboLoadBalancer.java
@@ -0,0 +1,30 @@
+package org.dromara.common.loadbalance.core;
+
+import cn.hutool.core.net.NetUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.rpc.Invocation;
+import org.apache.dubbo.rpc.Invoker;
+import org.apache.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance;
+
+import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * 鑷畾涔� Dubbo 璐熻浇鍧囪 绠楁硶
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class CustomDubboLoadBalancer extends AbstractLoadBalance {
+
+    @Override
+    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
+        for (Invoker<T> invoker : invokers) {
+            if (NetUtil.localIpv4s().contains(invoker.getUrl().getHost())) {
+                return invoker;
+            }
+        }
+        return invokers.get(ThreadLocalRandom.current().nextInt(invokers.size()));
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/core/CustomSpringCloudLoadBalancer.java b/ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/core/CustomSpringCloudLoadBalancer.java
new file mode 100644
index 0000000..4e3d16d
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-loadbalancer/src/main/java/org/dromara/common/loadbalance/core/CustomSpringCloudLoadBalancer.java
@@ -0,0 +1,64 @@
+package org.dromara.common.loadbalance.core;
+
+import cn.hutool.core.net.NetUtil;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.client.loadbalancer.DefaultResponse;
+import org.springframework.cloud.client.loadbalancer.EmptyResponse;
+import org.springframework.cloud.client.loadbalancer.Request;
+import org.springframework.cloud.client.loadbalancer.Response;
+import org.springframework.cloud.loadbalancer.core.NoopServiceInstanceListSupplier;
+import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer;
+import org.springframework.cloud.loadbalancer.core.SelectedInstanceCallback;
+import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
+import reactor.core.publisher.Mono;
+
+import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * 鑷畾涔� SpringCloud 璐熻浇鍧囪 绠楁硶
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@AllArgsConstructor
+public class CustomSpringCloudLoadBalancer implements ReactorServiceInstanceLoadBalancer {
+
+    private final String serviceId;
+
+    private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
+
+    @Override
+    public Mono<Response<ServiceInstance>> choose(Request request) {
+        ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
+        return supplier.get(request).next().map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
+    }
+
+    private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,
+                                                              List<ServiceInstance> serviceInstances) {
+        Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances);
+        if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
+            ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
+        }
+        return serviceInstanceResponse;
+    }
+
+    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
+        if (instances.isEmpty()) {
+            if (log.isWarnEnabled()) {
+                log.warn("No servers available for service: " + serviceId);
+            }
+            return new EmptyResponse();
+        }
+        for (ServiceInstance instance : instances) {
+            if (NetUtil.localIpv4s().contains(instance.getHost())) {
+                return new DefaultResponse(instance);
+            }
+        }
+        return new DefaultResponse(instances.get(ThreadLocalRandom.current().nextInt(instances.size())));
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-loadbalancer/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.cluster.LoadBalance b/ruoyi-common/ruoyi-common-loadbalancer/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.cluster.LoadBalance
new file mode 100644
index 0000000..f40caf6
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-loadbalancer/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.cluster.LoadBalance
@@ -0,0 +1 @@
+customDubboLoadBalancer=org.dromara.common.loadbalance.core.CustomDubboLoadBalancer
diff --git a/ruoyi-common/ruoyi-common-loadbalancer/src/main/resources/META-INF/spring.factories b/ruoyi-common/ruoyi-common-loadbalancer/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..18edf43
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-loadbalancer/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.boot.env.EnvironmentPostProcessor=\
+  org.dromara.common.loadbalance.config.CustomEnvironmentPostProcessor
diff --git a/ruoyi-common/ruoyi-common-loadbalancer/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-loadbalancer/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..954b296
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-loadbalancer/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.loadbalance.config.CustomLoadBalanceAutoConfiguration
diff --git a/ruoyi-common/ruoyi-common-log/pom.xml b/ruoyi-common/ruoyi-common-log/pom.xml
new file mode 100644
index 0000000..cbda66f
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-log/pom.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-log</artifactId>
+
+    <description>
+        ruoyi-common-log 鏃ュ織璁板綍
+    </description>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-satoken</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-json</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>transmittable-thread-local</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.dubbo</groupId>
+            <artifactId>dubbo-spring-boot-starter</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+    </dependencies>
+
+</project>
diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/annotation/Log.java b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/annotation/Log.java
new file mode 100644
index 0000000..2dced97
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/annotation/Log.java
@@ -0,0 +1,48 @@
+package org.dromara.common.log.annotation;
+
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.log.enums.OperatorType;
+
+import java.lang.annotation.*;
+
+/**
+ * 鑷畾涔夋搷浣滄棩蹇楄褰曟敞瑙�
+ *
+ * @author ruoyi
+ */
+@Target({ElementType.PARAMETER, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Log {
+    /**
+     * 妯″潡
+     */
+    String title() default "";
+
+    /**
+     * 鍔熻兘
+     */
+    BusinessType businessType() default BusinessType.OTHER;
+
+    /**
+     * 鎿嶄綔浜虹被鍒�
+     */
+    OperatorType operatorType() default OperatorType.MANAGE;
+
+    /**
+     * 鏄惁淇濆瓨璇锋眰鐨勫弬鏁�
+     */
+    boolean isSaveRequestData() default true;
+
+    /**
+     * 鏄惁淇濆瓨鍝嶅簲鐨勫弬鏁�
+     */
+    boolean isSaveResponseData() default true;
+
+
+    /**
+     * 鎺掗櫎鎸囧畾鐨勮姹傚弬鏁�
+     */
+    String[] excludeParamNames() default {};
+
+}
diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/aspect/LogAspect.java b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/aspect/LogAspect.java
new file mode 100644
index 0000000..06beca6
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/aspect/LogAspect.java
@@ -0,0 +1,222 @@
+package org.dromara.common.log.aspect;
+
+import cn.hutool.core.lang.Dict;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.ttl.TransmittableThreadLocal;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.time.StopWatch;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.AfterReturning;
+import org.aspectj.lang.annotation.AfterThrowing;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.dromara.common.core.utils.ServletUtils;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessStatus;
+import org.dromara.common.log.event.OperLogEvent;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.system.api.model.LoginUser;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.http.HttpMethod;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.StringJoiner;
+
+/**
+ * 鎿嶄綔鏃ュ織璁板綍澶勭悊
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@Aspect
+@AutoConfiguration
+public class LogAspect {
+
+    /**
+     * 鎺掗櫎鏁忔劅灞炴�у瓧娈�
+     */
+    public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" };
+
+
+    /**
+     * 璁$畻鎿嶄綔娑堣�楁椂闂�
+     */
+    private static final ThreadLocal<StopWatch> TIME_THREADLOCAL = new TransmittableThreadLocal<>();
+
+    /**
+     * 澶勭悊璇锋眰鍓嶆墽琛�
+     */
+    @Before(value = "@annotation(controllerLog)")
+    public void boBefore(JoinPoint joinPoint, Log controllerLog) {
+        StopWatch stopWatch = new StopWatch();
+        TIME_THREADLOCAL.set(stopWatch);
+        stopWatch.start();
+    }
+
+    /**
+     * 澶勭悊瀹岃姹傚悗鎵ц
+     *
+     * @param joinPoint 鍒囩偣
+     */
+    @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
+    public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
+        handleLog(joinPoint, controllerLog, null, jsonResult);
+    }
+
+    /**
+     * 鎷︽埅寮傚父鎿嶄綔
+     *
+     * @param joinPoint 鍒囩偣
+     * @param e         寮傚父
+     */
+    @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
+    public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) {
+        handleLog(joinPoint, controllerLog, e, null);
+    }
+
+    protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) {
+        try {
+
+            // *========鏁版嵁搴撴棩蹇�=========*//
+            OperLogEvent operLog = new OperLogEvent();
+            operLog.setTenantId(LoginHelper.getTenantId());
+            operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
+            // 璇锋眰鐨勫湴鍧�
+            String ip = ServletUtils.getClientIP();
+            operLog.setOperIp(ip);
+            operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
+            LoginUser loginUser = LoginHelper.getLoginUser();
+            operLog.setOperName(loginUser.getUsername());
+            operLog.setDeptName(loginUser.getDeptName());
+
+            if (e != null) {
+                operLog.setStatus(BusinessStatus.FAIL.ordinal());
+                operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
+            }
+            // 璁剧疆鏂规硶鍚嶇О
+            String className = joinPoint.getTarget().getClass().getName();
+            String methodName = joinPoint.getSignature().getName();
+            operLog.setMethod(className + "." + methodName + "()");
+            // 璁剧疆璇锋眰鏂瑰紡
+            operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
+            // 澶勭悊璁剧疆娉ㄨВ涓婄殑鍙傛暟
+            getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
+            // 璁剧疆娑堣�楁椂闂�
+            StopWatch stopWatch = TIME_THREADLOCAL.get();
+            stopWatch.stop();
+            operLog.setCostTime(stopWatch.getTime());
+            // 鍙戝竷浜嬩欢淇濆瓨鏁版嵁搴�
+            SpringUtils.context().publishEvent(operLog);
+        } catch (Exception exp) {
+            // 璁板綍鏈湴寮傚父鏃ュ織
+            log.error("寮傚父淇℃伅:{}", exp.getMessage());
+            exp.printStackTrace();
+        } finally {
+            TIME_THREADLOCAL.remove();
+        }
+    }
+
+    /**
+     * 鑾峰彇娉ㄨВ涓鏂规硶鐨勬弿杩颁俊鎭� 鐢ㄤ簬Controller灞傛敞瑙�
+     *
+     * @param log     鏃ュ織
+     * @param operLog 鎿嶄綔鏃ュ織
+     * @throws Exception
+     */
+    public void getControllerMethodDescription(JoinPoint joinPoint, Log log, OperLogEvent operLog, Object jsonResult) throws Exception {
+        // 璁剧疆action鍔ㄤ綔
+        operLog.setBusinessType(log.businessType().ordinal());
+        // 璁剧疆鏍囬
+        operLog.setTitle(log.title());
+        // 璁剧疆鎿嶄綔浜虹被鍒�
+        operLog.setOperatorType(log.operatorType().ordinal());
+        // 鏄惁闇�瑕佷繚瀛榬equest锛屽弬鏁板拰鍊�
+        if (log.isSaveRequestData()) {
+            // 鑾峰彇鍙傛暟鐨勪俊鎭紝浼犲叆鍒版暟鎹簱涓��
+            setRequestValue(joinPoint, operLog, log.excludeParamNames());
+        }
+        // 鏄惁闇�瑕佷繚瀛榬esponse锛屽弬鏁板拰鍊�
+        if (log.isSaveResponseData() && ObjectUtil.isNotNull(jsonResult)) {
+            operLog.setJsonResult(StringUtils.substring(JsonUtils.toJsonString(jsonResult), 0, 2000));
+        }
+    }
+
+    /**
+     * 鑾峰彇璇锋眰鐨勫弬鏁帮紝鏀惧埌log涓�
+     *
+     * @param operLog 鎿嶄綔鏃ュ織
+     * @throws Exception 寮傚父
+     */
+    private void setRequestValue(JoinPoint joinPoint, OperLogEvent operLog, String[] excludeParamNames) throws Exception {
+        Map<String, String> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
+        String requestMethod = operLog.getRequestMethod();
+        if (MapUtil.isEmpty(paramsMap)
+                && HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
+            String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
+            operLog.setOperParam(StringUtils.substring(params, 0, 2000));
+        } else {
+            MapUtil.removeAny(paramsMap, EXCLUDE_PROPERTIES);
+            MapUtil.removeAny(paramsMap, excludeParamNames);
+            operLog.setOperParam(StringUtils.substring(JsonUtils.toJsonString(paramsMap), 0, 2000));
+        }
+    }
+
+    /**
+     * 鍙傛暟鎷艰
+     */
+    private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) {
+        StringJoiner params = new StringJoiner(" ");
+        if (ArrayUtil.isEmpty(paramsArray)) {
+            return params.toString();
+        }
+        for (Object o : paramsArray) {
+            if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
+                String str = JsonUtils.toJsonString(o);
+                Dict dict = JsonUtils.parseMap(str);
+                if (MapUtil.isNotEmpty(dict)) {
+                    MapUtil.removeAny(dict, EXCLUDE_PROPERTIES);
+                    MapUtil.removeAny(dict, excludeParamNames);
+                    str = JsonUtils.toJsonString(dict);
+                }
+                params.add(str);
+            }
+        }
+        return params.toString();
+    }
+
+    /**
+     * 鍒ゆ柇鏄惁闇�瑕佽繃婊ょ殑瀵硅薄銆�
+     *
+     * @param o 瀵硅薄淇℃伅銆�
+     * @return 濡傛灉鏄渶瑕佽繃婊ょ殑瀵硅薄锛屽垯杩斿洖true锛涘惁鍒欒繑鍥瀎alse銆�
+     */
+    @SuppressWarnings("rawtypes")
+    public boolean isFilterObject(final Object o) {
+        Class<?> clazz = o.getClass();
+        if (clazz.isArray()) {
+            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
+        } else if (Collection.class.isAssignableFrom(clazz)) {
+            Collection collection = (Collection) o;
+            for (Object value : collection) {
+                return value instanceof MultipartFile;
+            }
+        } else if (Map.class.isAssignableFrom(clazz)) {
+            Map map = (Map) o;
+            for (Object value : map.values()) {
+                return value instanceof MultipartFile;
+            }
+        }
+        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
+            || o instanceof BindingResult;
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/BusinessStatus.java b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/BusinessStatus.java
new file mode 100644
index 0000000..d303dc3
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/BusinessStatus.java
@@ -0,0 +1,18 @@
+package org.dromara.common.log.enums;
+
+/**
+ * 鎿嶄綔鐘舵��
+ *
+ * @author ruoyi
+ */
+public enum BusinessStatus {
+    /**
+     * 鎴愬姛
+     */
+    SUCCESS,
+
+    /**
+     * 澶辫触
+     */
+    FAIL,
+}
diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/BusinessType.java b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/BusinessType.java
new file mode 100644
index 0000000..2d25ebb
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/BusinessType.java
@@ -0,0 +1,58 @@
+package org.dromara.common.log.enums;
+
+/**
+ * 涓氬姟鎿嶄綔绫诲瀷
+ *
+ * @author ruoyi
+ */
+public enum BusinessType {
+    /**
+     * 鍏跺畠
+     */
+    OTHER,
+
+    /**
+     * 鏂板
+     */
+    INSERT,
+
+    /**
+     * 淇敼
+     */
+    UPDATE,
+
+    /**
+     * 鍒犻櫎
+     */
+    DELETE,
+
+    /**
+     * 鎺堟潈
+     */
+    GRANT,
+
+    /**
+     * 瀵煎嚭
+     */
+    EXPORT,
+
+    /**
+     * 瀵煎叆
+     */
+    IMPORT,
+
+    /**
+     * 寮洪��
+     */
+    FORCE,
+
+    /**
+     * 鐢熸垚浠g爜
+     */
+    GENCODE,
+
+    /**
+     * 娓呯┖鏁版嵁
+     */
+    CLEAN,
+}
diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/OperatorType.java b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/OperatorType.java
new file mode 100644
index 0000000..de9328b
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/enums/OperatorType.java
@@ -0,0 +1,23 @@
+package org.dromara.common.log.enums;
+
+/**
+ * 鎿嶄綔浜虹被鍒�
+ *
+ * @author ruoyi
+ */
+public enum OperatorType {
+    /**
+     * 鍏跺畠
+     */
+    OTHER,
+
+    /**
+     * 鍚庡彴鐢ㄦ埛
+     */
+    MANAGE,
+
+    /**
+     * 鎵嬫満绔敤鎴�
+     */
+    MOBILE
+}
diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/LogEventListener.java b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/LogEventListener.java
new file mode 100644
index 0000000..3697e58
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/LogEventListener.java
@@ -0,0 +1,103 @@
+package org.dromara.common.log.event;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.http.useragent.UserAgent;
+import cn.hutool.http.useragent.UserAgentUtil;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.common.core.constant.Constants;
+import org.dromara.common.core.utils.ServletUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.ip.AddressUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.system.api.RemoteClientService;
+import org.dromara.system.api.RemoteLogService;
+import org.dromara.system.api.domain.bo.RemoteLogininforBo;
+import org.dromara.system.api.domain.bo.RemoteOperLogBo;
+import org.dromara.system.api.domain.vo.RemoteClientVo;
+import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Component;
+
+/**
+ * 寮傛璋冪敤鏃ュ織鏈嶅姟
+ *
+ * @author ruoyi
+ */
+@Component
+@Slf4j
+public class LogEventListener {
+
+    @DubboReference
+    private RemoteLogService remoteLogService;
+    @DubboReference
+    private RemoteClientService remoteClientService;
+
+    /**
+     * 淇濆瓨绯荤粺鏃ュ織璁板綍
+     */
+    @Async
+    @EventListener
+    public void saveLog(OperLogEvent operLogEvent) {
+        RemoteOperLogBo sysOperLog = BeanUtil.toBean(operLogEvent, RemoteOperLogBo.class);
+        remoteLogService.saveLog(sysOperLog);
+    }
+
+    @Async
+    @EventListener
+    public void saveLogininfor(LogininforEvent logininforEvent) {
+        HttpServletRequest request = logininforEvent.getRequest();
+        final UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));
+        final String ip = ServletUtils.getClientIP(request);
+        // 瀹㈡埛绔俊鎭�
+        String clientid = request.getHeader(LoginHelper.CLIENT_KEY);
+        RemoteClientVo clientVo = null;
+        if (StringUtils.isNotBlank(clientid)) {
+            clientVo = remoteClientService.queryByClientId(clientid);
+        }
+
+        String address = AddressUtils.getRealAddressByIP(ip);
+        StringBuilder s = new StringBuilder();
+        s.append(getBlock(ip));
+        s.append(address);
+        s.append(getBlock(logininforEvent.getUsername()));
+        s.append(getBlock(logininforEvent.getStatus()));
+        s.append(getBlock(logininforEvent.getMessage()));
+        // 鎵撳嵃淇℃伅鍒版棩蹇�
+        log.info(s.toString(), logininforEvent.getArgs());
+        // 鑾峰彇瀹㈡埛绔搷浣滅郴缁�
+        String os = userAgent.getOs().getName();
+        // 鑾峰彇瀹㈡埛绔祻瑙堝櫒
+        String browser = userAgent.getBrowser().getName();
+        // 灏佽瀵硅薄
+        RemoteLogininforBo logininfor = new RemoteLogininforBo();
+        logininfor.setTenantId(logininforEvent.getTenantId());
+        logininfor.setUserName(logininforEvent.getUsername());
+        if (ObjectUtil.isNotNull(clientVo)) {
+            logininfor.setClientKey(clientVo.getClientKey());
+            logininfor.setDeviceType(clientVo.getDeviceType());
+        }
+        logininfor.setIpaddr(ip);
+        logininfor.setLoginLocation(address);
+        logininfor.setBrowser(browser);
+        logininfor.setOs(os);
+        logininfor.setMsg(logininforEvent.getMessage());
+        // 鏃ュ織鐘舵��
+        if (StringUtils.equalsAny(logininforEvent.getStatus(), Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) {
+            logininfor.setStatus(Constants.SUCCESS);
+        } else if (Constants.LOGIN_FAIL.equals(logininforEvent.getStatus())) {
+            logininfor.setStatus(Constants.FAIL);
+        }
+        remoteLogService.saveLogininfor(logininfor);
+    }
+
+    private String getBlock(Object msg) {
+        if (msg == null) {
+            msg = "";
+        }
+        return "[" + msg + "]";
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/LogininforEvent.java b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/LogininforEvent.java
new file mode 100644
index 0000000..938eaad
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/LogininforEvent.java
@@ -0,0 +1,52 @@
+package org.dromara.common.log.event;
+
+import lombok.Data;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 鐧诲綍浜嬩欢
+ *
+ * @author Lion Li
+ */
+
+@Data
+public class LogininforEvent implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 绉熸埛ID
+     */
+    private String tenantId;
+
+    /**
+     * 鐢ㄦ埛璐﹀彿
+     */
+    private String username;
+
+    /**
+     * 鐧诲綍鐘舵�� 0鎴愬姛 1澶辫触
+     */
+    private String status;
+
+    /**
+     * 鎻愮ず娑堟伅
+     */
+    private String message;
+
+    /**
+     * 璇锋眰浣�
+     */
+    private HttpServletRequest request;
+
+    /**
+     * 鍏朵粬鍙傛暟
+     */
+    private Object[] args;
+
+}
diff --git a/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/OperLogEvent.java b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/OperLogEvent.java
new file mode 100644
index 0000000..0386192
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-log/src/main/java/org/dromara/common/log/event/OperLogEvent.java
@@ -0,0 +1,115 @@
+package org.dromara.common.log.event;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 鎿嶄綔鏃ュ織浜嬩欢
+ *
+ * @author Lion Li
+ */
+
+@Data
+public class OperLogEvent implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 鏃ュ織涓婚敭
+     */
+    private Long operId;
+
+    /**
+     * 绉熸埛ID
+     */
+    private String tenantId;
+
+    /**
+     * 鎿嶄綔妯″潡
+     */
+    private String title;
+
+    /**
+     * 涓氬姟绫诲瀷锛�0鍏跺畠 1鏂板 2淇敼 3鍒犻櫎锛�
+     */
+    private Integer businessType;
+
+    /**
+     * 涓氬姟绫诲瀷鏁扮粍
+     */
+    private Integer[] businessTypes;
+
+    /**
+     * 璇锋眰鏂规硶
+     */
+    private String method;
+
+    /**
+     * 璇锋眰鏂瑰紡
+     */
+    private String requestMethod;
+
+    /**
+     * 鎿嶄綔绫诲埆锛�0鍏跺畠 1鍚庡彴鐢ㄦ埛 2鎵嬫満绔敤鎴凤級
+     */
+    private Integer operatorType;
+
+    /**
+     * 鎿嶄綔浜哄憳
+     */
+    private String operName;
+
+    /**
+     * 閮ㄩ棬鍚嶇О
+     */
+    private String deptName;
+
+    /**
+     * 璇锋眰url
+     */
+    private String operUrl;
+
+    /**
+     * 鎿嶄綔鍦板潃
+     */
+    private String operIp;
+
+    /**
+     * 鎿嶄綔鍦扮偣
+     */
+    private String operLocation;
+
+    /**
+     * 璇锋眰鍙傛暟
+     */
+    private String operParam;
+
+    /**
+     * 杩斿洖鍙傛暟
+     */
+    private String jsonResult;
+
+    /**
+     * 鎿嶄綔鐘舵�侊紙0姝e父 1寮傚父锛�
+     */
+    private Integer status;
+
+    /**
+     * 閿欒娑堟伅
+     */
+    private String errorMsg;
+
+    /**
+     * 鎿嶄綔鏃堕棿
+     */
+    private Date operTime;
+
+    /**
+     * 娑堣�楁椂闂�
+     */
+    private Long costTime;
+}
diff --git a/ruoyi-common/ruoyi-common-log/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-log/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..42b8812
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-log/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1,2 @@
+org.dromara.common.log.event.LogEventListener
+org.dromara.common.log.aspect.LogAspect
diff --git a/ruoyi-common/ruoyi-common-logstash/pom.xml b/ruoyi-common/ruoyi-common-logstash/pom.xml
new file mode 100644
index 0000000..36ad275
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-logstash/pom.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-logstash</artifactId>
+
+    <description>
+        ruoyi-common-logstash logstash鏃ュ織鎺ㄩ�佹ā鍧�
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>net.logstash.logback</groupId>
+            <artifactId>logstash-logback-encoder</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/ruoyi-common/ruoyi-common-logstash/src/main/resources/logback-logstash.xml b/ruoyi-common/ruoyi-common-logstash/src/main/resources/logback-logstash.xml
new file mode 100644
index 0000000..d25f752
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-logstash/src/main/resources/logback-logstash.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<included>
+
+    <springProperty scope="context" name="appName" source="spring.application.name"/>
+
+    <!--杈撳嚭鍒發ogstash鐨刟ppender-->
+    <appender name="logstash" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
+        <!--鍙互璁块棶鐨刲ogstash鏃ュ織鏀堕泦绔彛-->
+        <destination>${logstash.address}</destination>
+        <encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder">
+            <customFields>{"spring.application.name":"${appName}"}</customFields>
+        </encoder>
+    </appender>
+
+    <root level="info">
+        <appender-ref ref="logstash"/>
+    </root>
+</included>
diff --git a/ruoyi-common/ruoyi-common-mail/pom.xml b/ruoyi-common/ruoyi-common-mail/pom.xml
new file mode 100644
index 0000000..c0e1b2e
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mail/pom.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-mail</artifactId>
+
+    <description>
+        ruoyi-common-mail 閭欢妯″潡
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>jakarta.mail</groupId>
+            <artifactId>jakarta.mail-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.angus</groupId>
+            <artifactId>jakarta.mail</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/MailConfig.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/MailConfig.java
new file mode 100644
index 0000000..1b51c27
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/MailConfig.java
@@ -0,0 +1,37 @@
+package org.dromara.common.mail.config;
+
+import org.dromara.common.mail.config.properties.MailProperties;
+import org.dromara.common.mail.utils.MailAccount;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * JavaMail 閰嶇疆
+ *
+ * @author Michelle.Chung
+ */
+@AutoConfiguration
+@EnableConfigurationProperties(MailProperties.class)
+public class MailConfig {
+
+    @Bean
+    @ConditionalOnProperty(value = "mail.enabled", havingValue = "true")
+    public MailAccount mailAccount(MailProperties mailProperties) {
+        MailAccount account = new MailAccount();
+        account.setHost(mailProperties.getHost());
+        account.setPort(mailProperties.getPort());
+        account.setAuth(mailProperties.getAuth());
+        account.setFrom(mailProperties.getFrom());
+        account.setUser(mailProperties.getUser());
+        account.setPass(mailProperties.getPass());
+        account.setSocketFactoryPort(mailProperties.getPort());
+        account.setStarttlsEnable(mailProperties.getStarttlsEnable());
+        account.setSslEnable(mailProperties.getSslEnable());
+        account.setTimeout(mailProperties.getTimeout());
+        account.setConnectionTimeout(mailProperties.getConnectionTimeout());
+        return account;
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/MailConfiguration.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/MailConfiguration.java
new file mode 100644
index 0000000..6410122
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/MailConfiguration.java
@@ -0,0 +1,37 @@
+package org.dromara.common.mail.config;
+
+import org.dromara.common.mail.utils.MailAccount;
+import org.dromara.common.mail.config.properties.MailProperties;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * JavaMail 閰嶇疆
+ *
+ * @author Michelle.Chung
+ */
+@AutoConfiguration
+@EnableConfigurationProperties(MailProperties.class)
+public class MailConfiguration {
+
+    @Bean
+    @ConditionalOnProperty(value = "mail.enabled", havingValue = "true")
+    public MailAccount mailAccount(MailProperties mailProperties) {
+        MailAccount account = new MailAccount();
+        account.setHost(mailProperties.getHost());
+        account.setPort(mailProperties.getPort());
+        account.setAuth(mailProperties.getAuth());
+        account.setFrom(mailProperties.getFrom());
+        account.setUser(mailProperties.getUser());
+        account.setPass(mailProperties.getPass());
+        account.setSocketFactoryPort(mailProperties.getPort());
+        account.setStarttlsEnable(mailProperties.getStarttlsEnable());
+        account.setSslEnable(mailProperties.getSslEnable());
+        account.setTimeout(mailProperties.getTimeout());
+        account.setConnectionTimeout(mailProperties.getConnectionTimeout());
+        return account;
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/properties/MailProperties.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/properties/MailProperties.java
new file mode 100644
index 0000000..d0e78a2
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/config/properties/MailProperties.java
@@ -0,0 +1,69 @@
+package org.dromara.common.mail.config.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * JavaMail 閰嶇疆灞炴��
+ *
+ * @author Michelle.Chung
+ */
+@Data
+@ConfigurationProperties(prefix = "mail")
+public class MailProperties {
+
+    /**
+     * 杩囨护寮�鍏�
+     */
+    private Boolean enabled;
+
+    /**
+     * SMTP鏈嶅姟鍣ㄥ煙鍚�
+     */
+    private String host;
+
+    /**
+     * SMTP鏈嶅姟绔彛
+     */
+    private Integer port;
+
+    /**
+     * 鏄惁闇�瑕佺敤鎴峰悕瀵嗙爜楠岃瘉
+     */
+    private Boolean auth;
+
+    /**
+     * 鐢ㄦ埛鍚�
+     */
+    private String user;
+
+    /**
+     * 瀵嗙爜
+     */
+    private String pass;
+
+    /**
+     * 鍙戦�佹柟锛岄伒寰猂FC-822鏍囧噯
+     */
+    private String from;
+
+    /**
+     * 浣跨敤 STARTTLS瀹夊叏杩炴帴锛孲TARTTLS鏄绾枃鏈�氫俊鍗忚鐨勬墿灞曘�傚畠灏嗙函鏂囨湰杩炴帴鍗囩骇涓哄姞瀵嗚繛鎺ワ紙TLS鎴朣SL锛夛紝 鑰屼笉鏄娇鐢ㄤ竴涓崟鐙殑鍔犲瘑閫氫俊绔彛銆�
+     */
+    private Boolean starttlsEnable;
+
+    /**
+     * 浣跨敤 SSL瀹夊叏杩炴帴
+     */
+    private Boolean sslEnable;
+
+    /**
+     * SMTP瓒呮椂鏃堕暱锛屽崟浣嶆绉掞紝缂虹渷鍊间笉瓒呮椂
+     */
+    private Long timeout;
+
+    /**
+     * Socket杩炴帴瓒呮椂鍊硷紝鍗曚綅姣锛岀己鐪佸�间笉瓒呮椂
+     */
+    private Long connectionTimeout;
+}
diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/GlobalMailAccount.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/GlobalMailAccount.java
new file mode 100644
index 0000000..fdae869
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/GlobalMailAccount.java
@@ -0,0 +1,46 @@
+package org.dromara.common.mail.utils;
+
+import cn.hutool.core.io.IORuntimeException;
+
+/**
+ * 鍏ㄥ眬閭欢甯愭埛锛屼緷璧栦簬閭欢閰嶇疆鏂囦欢{@link MailAccount#MAIL_SETTING_PATHS}
+ *
+ * @author looly
+ */
+public enum GlobalMailAccount {
+    INSTANCE;
+
+    private final MailAccount mailAccount;
+
+    /**
+     * 鏋勯��
+     */
+    GlobalMailAccount() {
+        mailAccount = createDefaultAccount();
+    }
+
+    /**
+     * 鑾峰緱閭欢甯愭埛
+     *
+     * @return 閭欢甯愭埛
+     */
+    public MailAccount getAccount() {
+        return this.mailAccount;
+    }
+
+    /**
+     * 鍒涘缓榛樿甯愭埛
+     *
+     * @return MailAccount
+     */
+    private MailAccount createDefaultAccount() {
+        for (String mailSettingPath : MailAccount.MAIL_SETTING_PATHS) {
+            try {
+                return new MailAccount(mailSettingPath);
+            } catch (IORuntimeException ignore) {
+                //ignore
+            }
+        }
+        return null;
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/InternalMailUtil.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/InternalMailUtil.java
new file mode 100644
index 0000000..b755e73
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/InternalMailUtil.java
@@ -0,0 +1,108 @@
+package org.dromara.common.mail.utils;
+
+import cn.hutool.core.util.ArrayUtil;
+import jakarta.mail.internet.AddressException;
+import jakarta.mail.internet.InternetAddress;
+import jakarta.mail.internet.MimeUtility;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 閭欢鍐呴儴宸ュ叿绫�
+ *
+ * @author looly
+ * @since 3.2.3
+ */
+public class InternalMailUtil {
+
+    /**
+     * 灏嗗涓瓧绗︿覆閭欢鍦板潃杞负{@link InternetAddress}鍒楄〃<br>
+     * 鍗曚釜瀛楃涓插湴鍧�鍙互鏄涓湴鍧�鍚堝苟鐨勫瓧绗︿覆
+     *
+     * @param addrStrs 鍦板潃鏁扮粍
+     * @param charset  缂栫爜锛堜富瑕佺敤浜庝腑鏂囩敤鎴峰悕鐨勭紪鐮侊級
+     * @return 鍦板潃鏁扮粍
+     * @since 4.0.3
+     */
+    public static InternetAddress[] parseAddressFromStrs(String[] addrStrs, Charset charset) {
+        final List<InternetAddress> resultList = new ArrayList<>(addrStrs.length);
+        InternetAddress[] addrs;
+        for (String addrStr : addrStrs) {
+            addrs = parseAddress(addrStr, charset);
+            if (ArrayUtil.isNotEmpty(addrs)) {
+                Collections.addAll(resultList, addrs);
+            }
+        }
+        return resultList.toArray(new InternetAddress[0]);
+    }
+
+    /**
+     * 瑙f瀽绗竴涓湴鍧�
+     *
+     * @param address 鍦板潃瀛楃涓�
+     * @param charset 缂栫爜锛寋@code null}琛ㄧず浣跨敤绯荤粺灞炴�у畾涔夌殑缂栫爜鎴栫郴缁熺紪鐮�
+     * @return 鍦板潃鍒楄〃
+     */
+    public static InternetAddress parseFirstAddress(String address, Charset charset) {
+        final InternetAddress[] internetAddresses = parseAddress(address, charset);
+        if (ArrayUtil.isEmpty(internetAddresses)) {
+            try {
+                return new InternetAddress(address);
+            } catch (AddressException e) {
+                throw new MailException(e);
+            }
+        }
+        return internetAddresses[0];
+    }
+
+    /**
+     * 灏嗕竴涓湴鍧�瀛楃涓茶В鏋愪负澶氫釜鍦板潃<br>
+     * 鍦板潃闂翠娇鐢�" "銆�","銆�";"鍒嗛殧
+     *
+     * @param address 鍦板潃瀛楃涓�
+     * @param charset 缂栫爜锛寋@code null}琛ㄧず浣跨敤绯荤粺灞炴�у畾涔夌殑缂栫爜鎴栫郴缁熺紪鐮�
+     * @return 鍦板潃鍒楄〃
+     */
+    public static InternetAddress[] parseAddress(String address, Charset charset) {
+        InternetAddress[] addresses;
+        try {
+            addresses = InternetAddress.parse(address);
+        } catch (AddressException e) {
+            throw new MailException(e);
+        }
+        //缂栫爜鐢ㄦ埛鍚�
+        if (ArrayUtil.isNotEmpty(addresses)) {
+            final String charsetStr = null == charset ? null : charset.name();
+            for (InternetAddress internetAddress : addresses) {
+                try {
+                    internetAddress.setPersonal(internetAddress.getPersonal(), charsetStr);
+                } catch (UnsupportedEncodingException e) {
+                    throw new MailException(e);
+                }
+            }
+        }
+
+        return addresses;
+    }
+
+    /**
+     * 缂栫爜涓枃瀛楃<br>
+     * 缂栫爜澶辫触杩斿洖鍘熷瓧绗︿覆
+     *
+     * @param text    琚紪鐮佺殑鏂囨湰
+     * @param charset 缂栫爜
+     * @return 缂栫爜鍚庣殑缁撴灉
+     */
+    public static String encodeText(String text, Charset charset) {
+        try {
+            return MimeUtility.encodeText(text, charset.name(), null);
+        } catch (UnsupportedEncodingException e) {
+            // ignore
+        }
+        return text;
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/Mail.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/Mail.java
new file mode 100644
index 0000000..6ca4b69
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/Mail.java
@@ -0,0 +1,483 @@
+package org.dromara.common.mail.utils;
+
+import cn.hutool.core.builder.Builder;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.IORuntimeException;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import jakarta.activation.DataHandler;
+import jakarta.activation.DataSource;
+import jakarta.activation.FileDataSource;
+import jakarta.activation.FileTypeMap;
+import jakarta.mail.*;
+import jakarta.mail.internet.MimeBodyPart;
+import jakarta.mail.internet.MimeMessage;
+import jakarta.mail.internet.MimeMultipart;
+import jakarta.mail.internet.MimeUtility;
+import jakarta.mail.util.ByteArrayDataSource;
+
+import java.io.*;
+import java.nio.charset.Charset;
+import java.util.Date;
+
+/**
+ * 閭欢鍙戦�佸鎴风
+ *
+ * @author looly
+ * @since 3.2.0
+ */
+public class Mail implements Builder<MimeMessage> {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 閭甯愭埛淇℃伅浠ュ強涓�浜涘鎴风閰嶇疆淇℃伅
+     */
+    private final MailAccount mailAccount;
+    /**
+     * 鏀朵欢浜哄垪琛�
+     */
+    private String[] tos;
+    /**
+     * 鎶勯�佷汉鍒楄〃锛坈arbon copy锛�
+     */
+    private String[] ccs;
+    /**
+     * 瀵嗛�佷汉鍒楄〃锛坆lind carbon copy锛�
+     */
+    private String[] bccs;
+    /**
+     * 鍥炲鍦板潃(reply-to)
+     */
+    private String[] reply;
+    /**
+     * 鏍囬
+     */
+    private String title;
+    /**
+     * 鍐呭
+     */
+    private String content;
+    /**
+     * 鏄惁涓篐TML
+     */
+    private boolean isHtml;
+    /**
+     * 姝f枃銆侀檮浠跺拰鍥剧墖鐨勬贩鍚堥儴鍒�
+     */
+    private final Multipart multipart = new MimeMultipart();
+    /**
+     * 鏄惁浣跨敤鍏ㄥ眬浼氳瘽锛岄粯璁や负false
+     */
+    private boolean useGlobalSession = false;
+
+    /**
+     * debug杈撳嚭浣嶇疆锛屽彲浠ヨ嚜瀹氫箟debug鏃ュ織
+     */
+    private PrintStream debugOutput;
+
+    /**
+     * 鍒涘缓閭欢瀹㈡埛绔�
+     *
+     * @param mailAccount 閭欢甯愬彿
+     * @return Mail
+     */
+    public static Mail create(MailAccount mailAccount) {
+        return new Mail(mailAccount);
+    }
+
+    /**
+     * 鍒涘缓閭欢瀹㈡埛绔紝浣跨敤鍏ㄥ眬閭欢甯愭埛
+     *
+     * @return Mail
+     */
+    public static Mail create() {
+        return new Mail();
+    }
+
+    // --------------------------------------------------------------- Constructor start
+
+    /**
+     * 鏋勯�狅紝浣跨敤鍏ㄥ眬閭欢甯愭埛
+     */
+    public Mail() {
+        this(GlobalMailAccount.INSTANCE.getAccount());
+    }
+
+    /**
+     * 鏋勯��
+     *
+     * @param mailAccount 閭欢甯愭埛锛屽鏋滀负null浣跨敤榛樿閰嶇疆鏂囦欢鐨勫叏灞�閭欢閰嶇疆
+     */
+    public Mail(MailAccount mailAccount) {
+        mailAccount = (null != mailAccount) ? mailAccount : GlobalMailAccount.INSTANCE.getAccount();
+        this.mailAccount = mailAccount.defaultIfEmpty();
+    }
+    // --------------------------------------------------------------- Constructor end
+
+    // --------------------------------------------------------------- Getters and Setters start
+
+    /**
+     * 璁剧疆鏀朵欢浜�
+     *
+     * @param tos 鏀朵欢浜哄垪琛�
+     * @return this
+     * @see #setTos(String...)
+     */
+    public Mail to(String... tos) {
+        return setTos(tos);
+    }
+
+    /**
+     * 璁剧疆澶氫釜鏀朵欢浜�
+     *
+     * @param tos 鏀朵欢浜哄垪琛�
+     * @return this
+     */
+    public Mail setTos(String... tos) {
+        this.tos = tos;
+        return this;
+    }
+
+    /**
+     * 璁剧疆澶氫釜鎶勯�佷汉锛坈arbon copy锛�
+     *
+     * @param ccs 鎶勯�佷汉鍒楄〃
+     * @return this
+     * @since 4.0.3
+     */
+    public Mail setCcs(String... ccs) {
+        this.ccs = ccs;
+        return this;
+    }
+
+    /**
+     * 璁剧疆澶氫釜瀵嗛�佷汉锛坆lind carbon copy锛�
+     *
+     * @param bccs 瀵嗛�佷汉鍒楄〃
+     * @return this
+     * @since 4.0.3
+     */
+    public Mail setBccs(String... bccs) {
+        this.bccs = bccs;
+        return this;
+    }
+
+    /**
+     * 璁剧疆澶氫釜鍥炲鍦板潃(reply-to)
+     *
+     * @param reply 鍥炲鍦板潃(reply-to)鍒楄〃
+     * @return this
+     * @since 4.6.0
+     */
+    public Mail setReply(String... reply) {
+        this.reply = reply;
+        return this;
+    }
+
+    /**
+     * 璁剧疆鏍囬
+     *
+     * @param title 鏍囬
+     * @return this
+     */
+    public Mail setTitle(String title) {
+        this.title = title;
+        return this;
+    }
+
+    /**
+     * 璁剧疆姝f枃<br>
+     * 姝f枃鍙互鏄櫘閫氭枃鏈篃鍙互鏄疕TML锛堥粯璁ゆ櫘閫氭枃鏈級锛屽彲浠ラ�氳繃璋冪敤{@link #setHtml(boolean)} 璁剧疆鏄惁涓篐TML
+     *
+     * @param content 姝f枃
+     * @return this
+     */
+    public Mail setContent(String content) {
+        this.content = content;
+        return this;
+    }
+
+    /**
+     * 璁剧疆鏄惁鏄疕TML
+     *
+     * @param isHtml 鏄惁涓篐TML
+     * @return this
+     */
+    public Mail setHtml(boolean isHtml) {
+        this.isHtml = isHtml;
+        return this;
+    }
+
+    /**
+     * 璁剧疆姝f枃
+     *
+     * @param content 姝f枃鍐呭
+     * @param isHtml  鏄惁涓篐TML
+     * @return this
+     */
+    public Mail setContent(String content, boolean isHtml) {
+        setContent(content);
+        return setHtml(isHtml);
+    }
+
+    /**
+     * 璁剧疆鏂囦欢绫诲瀷闄勪欢锛屾枃浠跺彲浠ユ槸鍥剧墖鏂囦欢锛屾鏃惰嚜鍔ㄨ缃甤id锛堟鏂囦腑寮曠敤鍥剧墖锛夛紝榛樿cid涓烘枃浠跺悕
+     *
+     * @param files 闄勪欢鏂囦欢鍒楄〃
+     * @return this
+     */
+    public Mail setFiles(File... files) {
+        if (ArrayUtil.isEmpty(files)) {
+            return this;
+        }
+
+        final DataSource[] attachments = new DataSource[files.length];
+        for (int i = 0; i < files.length; i++) {
+            attachments[i] = new FileDataSource(files[i]);
+        }
+        return setAttachments(attachments);
+    }
+
+    /**
+     * 澧炲姞闄勪欢鎴栧浘鐗囷紝闄勪欢浣跨敤{@link DataSource} 褰㈠紡琛ㄧず锛屽彲浠ヤ娇鐢▄@link FileDataSource}鍖呰鏂囦欢琛ㄧず鏂囦欢闄勪欢
+     *
+     * @param attachments 闄勪欢鍒楄〃
+     * @return this
+     * @since 4.0.9
+     */
+    public Mail setAttachments(DataSource... attachments) {
+        if (ArrayUtil.isNotEmpty(attachments)) {
+            final Charset charset = this.mailAccount.getCharset();
+            MimeBodyPart bodyPart;
+            String nameEncoded;
+            try {
+                for (DataSource attachment : attachments) {
+                    bodyPart = new MimeBodyPart();
+                    bodyPart.setDataHandler(new DataHandler(attachment));
+                    nameEncoded = attachment.getName();
+                    if (this.mailAccount.isEncodefilename()) {
+                        nameEncoded = InternalMailUtil.encodeText(nameEncoded, charset);
+                    }
+                    // 鏅�氶檮浠舵枃浠跺悕
+                    bodyPart.setFileName(nameEncoded);
+                    if (StrUtil.startWith(attachment.getContentType(), "image/")) {
+                        // 鍥剧墖闄勪欢锛岀敤浜庢鏂囦腑寮曠敤鍥剧墖
+                        bodyPart.setContentID(nameEncoded);
+                    }
+                    this.multipart.addBodyPart(bodyPart);
+                }
+            } catch (MessagingException e) {
+                throw new MailException(e);
+            }
+        }
+        return this;
+    }
+
+    /**
+     * 澧炲姞鍥剧墖锛屽浘鐗囩殑閿搴斿埌閭欢妯℃澘涓殑鍗犱綅瀛楃涓诧紝鍥剧墖绫诲瀷榛樿涓�"image/jpeg"
+     *
+     * @param cid         鍥剧墖涓庡崰浣嶇锛屽崰浣嶇鏍煎紡涓篶id:${cid}
+     * @param imageStream 鍥剧墖鏂囦欢
+     * @return this
+     * @since 4.6.3
+     */
+    public Mail addImage(String cid, InputStream imageStream) {
+        return addImage(cid, imageStream, null);
+    }
+
+    /**
+     * 澧炲姞鍥剧墖锛屽浘鐗囩殑閿搴斿埌閭欢妯℃澘涓殑鍗犱綅瀛楃涓�
+     *
+     * @param cid         鍥剧墖涓庡崰浣嶇锛屽崰浣嶇鏍煎紡涓篶id:${cid}
+     * @param imageStream 鍥剧墖娴侊紝涓嶅叧闂�
+     * @param contentType 鍥剧墖绫诲瀷锛宯ull璧嬪�奸粯璁ょ殑"image/jpeg"
+     * @return this
+     * @since 4.6.3
+     */
+    public Mail addImage(String cid, InputStream imageStream, String contentType) {
+        ByteArrayDataSource imgSource;
+        try {
+            imgSource = new ByteArrayDataSource(imageStream, ObjectUtil.defaultIfNull(contentType, "image/jpeg"));
+        } catch (IOException e) {
+            throw new IORuntimeException(e);
+        }
+        imgSource.setName(cid);
+        return setAttachments(imgSource);
+    }
+
+    /**
+     * 澧炲姞鍥剧墖锛屽浘鐗囩殑閿搴斿埌閭欢妯℃澘涓殑鍗犱綅瀛楃涓�
+     *
+     * @param cid       鍥剧墖涓庡崰浣嶇锛屽崰浣嶇鏍煎紡涓篶id:${cid}
+     * @param imageFile 鍥剧墖鏂囦欢
+     * @return this
+     * @since 4.6.3
+     */
+    public Mail addImage(String cid, File imageFile) {
+        InputStream in = null;
+        try {
+            in = FileUtil.getInputStream(imageFile);
+            return addImage(cid, in, FileTypeMap.getDefaultFileTypeMap().getContentType(imageFile));
+        } finally {
+            IoUtil.close(in);
+        }
+    }
+
+    /**
+     * 璁剧疆瀛楃闆嗙紪鐮�
+     *
+     * @param charset 瀛楃闆嗙紪鐮�
+     * @return this
+     * @see MailAccount#setCharset(Charset)
+     */
+    public Mail setCharset(Charset charset) {
+        this.mailAccount.setCharset(charset);
+        return this;
+    }
+
+    /**
+     * 璁剧疆鏄惁浣跨敤鍏ㄥ眬浼氳瘽锛岄粯璁や负true
+     *
+     * @param isUseGlobalSession 鏄惁浣跨敤鍏ㄥ眬浼氳瘽锛岄粯璁や负true
+     * @return this
+     * @since 4.0.2
+     */
+    public Mail setUseGlobalSession(boolean isUseGlobalSession) {
+        this.useGlobalSession = isUseGlobalSession;
+        return this;
+    }
+
+    /**
+     * 璁剧疆debug杈撳嚭浣嶇疆锛屽彲浠ヨ嚜瀹氫箟debug鏃ュ織
+     *
+     * @param debugOutput debug杈撳嚭浣嶇疆
+     * @return this
+     * @since 5.5.6
+     */
+    public Mail setDebugOutput(PrintStream debugOutput) {
+        this.debugOutput = debugOutput;
+        return this;
+    }
+    // --------------------------------------------------------------- Getters and Setters end
+
+    @Override
+    public MimeMessage build() {
+        try {
+            return buildMsg();
+        } catch (MessagingException e) {
+            throw new MailException(e);
+        }
+    }
+
+    /**
+     * 鍙戦��
+     *
+     * @return message-id
+     * @throws MailException 閭欢鍙戦�佸紓甯�
+     */
+    public String send() throws MailException {
+        try {
+            return doSend();
+        } catch (MessagingException e) {
+            if (e instanceof SendFailedException) {
+                // 褰撳湴鍧�鏃犳晥鏃讹紝鏄剧ず鏇村姞璇︾粏鐨勬棤鏁堝湴鍧�淇℃伅
+                final Address[] invalidAddresses = ((SendFailedException) e).getInvalidAddresses();
+                final String msg = StrUtil.format("Invalid Addresses: {}", ArrayUtil.toString(invalidAddresses));
+                throw new MailException(msg, e);
+            }
+            throw new MailException(e);
+        }
+    }
+
+    // --------------------------------------------------------------- Private method start
+
+    /**
+     * 鎵ц鍙戦��
+     *
+     * @return message-id
+     * @throws MessagingException 鍙戦�佸紓甯�
+     */
+    private String doSend() throws MessagingException {
+        final MimeMessage mimeMessage = buildMsg();
+        Transport.send(mimeMessage);
+        return mimeMessage.getMessageID();
+    }
+
+    /**
+     * 鏋勫缓娑堟伅
+     *
+     * @return {@link MimeMessage}娑堟伅
+     * @throws MessagingException 娑堟伅寮傚父
+     */
+    private MimeMessage buildMsg() throws MessagingException {
+        final Charset charset = this.mailAccount.getCharset();
+        final MimeMessage msg = new MimeMessage(getSession());
+        // 鍙戜欢浜�
+        final String from = this.mailAccount.getFrom();
+        if (StrUtil.isEmpty(from)) {
+            // 鐢ㄦ埛鏈彁渚涘彂閫佹柟锛屽垯浠嶴ession涓嚜鍔ㄨ幏鍙�
+            msg.setFrom();
+        } else {
+            msg.setFrom(InternalMailUtil.parseFirstAddress(from, charset));
+        }
+        // 鏍囬
+        msg.setSubject(this.title, (null == charset) ? null : charset.name());
+        // 鍙戦�佹椂闂�
+        msg.setSentDate(new Date());
+        // 鍐呭鍜岄檮浠�
+        msg.setContent(buildContent(charset));
+        // 鏀朵欢浜�
+        msg.setRecipients(MimeMessage.RecipientType.TO, InternalMailUtil.parseAddressFromStrs(this.tos, charset));
+        // 鎶勯�佷汉
+        if (ArrayUtil.isNotEmpty(this.ccs)) {
+            msg.setRecipients(MimeMessage.RecipientType.CC, InternalMailUtil.parseAddressFromStrs(this.ccs, charset));
+        }
+        // 瀵嗛�佷汉
+        if (ArrayUtil.isNotEmpty(this.bccs)) {
+            msg.setRecipients(MimeMessage.RecipientType.BCC, InternalMailUtil.parseAddressFromStrs(this.bccs, charset));
+        }
+        // 鍥炲鍦板潃(reply-to)
+        if (ArrayUtil.isNotEmpty(this.reply)) {
+            msg.setReplyTo(InternalMailUtil.parseAddressFromStrs(this.reply, charset));
+        }
+
+        return msg;
+    }
+
+    /**
+     * 鏋勫缓閭欢淇℃伅涓讳綋
+     *
+     * @param charset 缂栫爜锛寋@code null}鍒欎娇鐢▄@link MimeUtility#getDefaultJavaCharset()}
+     * @return 閭欢淇℃伅涓讳綋
+     * @throws MessagingException 娑堟伅寮傚父
+     */
+    private Multipart buildContent(Charset charset) throws MessagingException {
+        final String charsetStr = null != charset ? charset.name() : MimeUtility.getDefaultJavaCharset();
+        // 姝f枃
+        final MimeBodyPart body = new MimeBodyPart();
+        body.setContent(content, StrUtil.format("text/{}; charset={}", isHtml ? "html" : "plain", charsetStr));
+        this.multipart.addBodyPart(body);
+
+        return this.multipart;
+    }
+
+    /**
+     * 鑾峰彇榛樿閭欢浼氳瘽<br>
+     * 濡傛灉涓哄叏灞�鍗曚緥鐨勪細璇濓紝鍒欏叏灞�鍙厑璁镐竴涓偖浠跺笎鍙凤紝鍚﹀垯姣忔鍙戦�侀偖浠朵細鏂板缓涓�涓柊鐨勪細璇�
+     *
+     * @return 閭欢浼氳瘽 {@link Session}
+     */
+    private Session getSession() {
+        final Session session = MailUtils.getSession(this.mailAccount, this.useGlobalSession);
+
+        if (null != this.debugOutput) {
+            session.setDebugOut(debugOutput);
+        }
+
+        return session;
+    }
+    // --------------------------------------------------------------- Private method end
+}
diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailAccount.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailAccount.java
new file mode 100644
index 0000000..2a732a1
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailAccount.java
@@ -0,0 +1,659 @@
+package org.dromara.common.mail.utils;
+
+import cn.hutool.core.util.CharsetUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.setting.Setting;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * 閭欢璐︽埛瀵硅薄
+ *
+ * @author Luxiaolei
+ */
+public class MailAccount implements Serializable {
+    @Serial
+    private static final long serialVersionUID = -6937313421815719204L;
+
+    private static final String MAIL_PROTOCOL = "mail.transport.protocol";
+    private static final String SMTP_HOST = "mail.smtp.host";
+    private static final String SMTP_PORT = "mail.smtp.port";
+    private static final String SMTP_AUTH = "mail.smtp.auth";
+    private static final String SMTP_TIMEOUT = "mail.smtp.timeout";
+    private static final String SMTP_CONNECTION_TIMEOUT = "mail.smtp.connectiontimeout";
+    private static final String SMTP_WRITE_TIMEOUT = "mail.smtp.writetimeout";
+
+    // SSL
+    private static final String STARTTLS_ENABLE = "mail.smtp.starttls.enable";
+    private static final String SSL_ENABLE = "mail.smtp.ssl.enable";
+    private static final String SSL_PROTOCOLS = "mail.smtp.ssl.protocols";
+    private static final String SOCKET_FACTORY = "mail.smtp.socketFactory.class";
+    private static final String SOCKET_FACTORY_FALLBACK = "mail.smtp.socketFactory.fallback";
+    private static final String SOCKET_FACTORY_PORT = "smtp.socketFactory.port";
+
+    // System Properties
+    private static final String SPLIT_LONG_PARAMS = "mail.mime.splitlongparameters";
+    //private static final String ENCODE_FILE_NAME = "mail.mime.encodefilename";
+    //private static final String CHARSET = "mail.mime.charset";
+
+    // 鍏朵粬
+    private static final String MAIL_DEBUG = "mail.debug";
+
+    public static final String[] MAIL_SETTING_PATHS = new String[]{"config/mail.setting", "config/mailAccount.setting", "mail.setting"};
+
+    /**
+     * SMTP鏈嶅姟鍣ㄥ煙鍚�
+     */
+    private String host;
+    /**
+     * SMTP鏈嶅姟绔彛
+     */
+    private Integer port;
+    /**
+     * 鏄惁闇�瑕佺敤鎴峰悕瀵嗙爜楠岃瘉
+     */
+    private Boolean auth;
+    /**
+     * 鐢ㄦ埛鍚�
+     */
+    private String user;
+    /**
+     * 瀵嗙爜
+     */
+    private String pass;
+    /**
+     * 鍙戦�佹柟锛岄伒寰猂FC-822鏍囧噯
+     */
+    private String from;
+
+    /**
+     * 鏄惁鎵撳紑璋冭瘯妯″紡锛岃皟璇曟ā寮忎細鏄剧ず涓庨偖浠舵湇鍔″櫒閫氫俊杩囩▼锛岄粯璁や笉寮�鍚�
+     */
+    private boolean debug;
+    /**
+     * 缂栫爜鐢ㄤ簬缂栫爜閭欢姝f枃鍜屽彂閫佷汉銆佹敹浠朵汉绛変腑鏂�
+     */
+    private Charset charset = CharsetUtil.CHARSET_UTF_8;
+    /**
+     * 瀵逛簬瓒呴暱鍙傛暟鏄惁鍒囧垎涓哄浠斤紝榛樿涓篺alse锛堝浗鍐呴偖绠遍檮浠朵笉鏀寔鍒囧垎鐨勯檮浠跺悕锛�
+     */
+    private boolean splitlongparameters = false;
+    /**
+     * 瀵逛簬鏂囦欢鍚嶆槸鍚︿娇鐢▄@link #charset}缂栫爜锛岄粯璁や负 {@code true}
+     */
+    private boolean encodefilename = true;
+
+    /**
+     * 浣跨敤 STARTTLS瀹夊叏杩炴帴锛孲TARTTLS鏄绾枃鏈�氫俊鍗忚鐨勬墿灞曘�傚畠灏嗙函鏂囨湰杩炴帴鍗囩骇涓哄姞瀵嗚繛鎺ワ紙TLS鎴朣SL锛夛紝 鑰屼笉鏄娇鐢ㄤ竴涓崟鐙殑鍔犲瘑閫氫俊绔彛銆�
+     */
+    private boolean starttlsEnable = false;
+    /**
+     * 浣跨敤 SSL瀹夊叏杩炴帴
+     */
+    private Boolean sslEnable;
+
+    /**
+     * SSL鍗忚锛屽涓崗璁敤绌烘牸鍒嗛殧
+     */
+    private String sslProtocols;
+
+    /**
+     * 鎸囧畾瀹炵幇javax.net.SocketFactory鎺ュ彛鐨勭被鐨勫悕绉�,杩欎釜绫诲皢琚敤浜庡垱寤篠MTP鐨勫鎺ュ瓧
+     */
+    private String socketFactoryClass = "javax.net.ssl.SSLSocketFactory";
+    /**
+     * 濡傛灉璁剧疆涓簍rue,鏈兘鍒涘缓涓�涓鎺ュ瓧浣跨敤鎸囧畾鐨勫鎺ュ瓧宸ュ巶绫诲皢瀵艰嚧浣跨敤java.net.Socket鍒涘缓鐨勫鎺ュ瓧绫�, 榛樿鍊间负true
+     */
+    private boolean socketFactoryFallback;
+    /**
+     * 鎸囧畾鐨勭鍙h繛鎺ュ埌鍦ㄤ娇鐢ㄦ寚瀹氱殑濂楁帴瀛楀伐鍘傘�傚鏋滄病鏈夎缃�,灏嗕娇鐢ㄩ粯璁ょ鍙�
+     */
+    private int socketFactoryPort = 465;
+
+    /**
+     * SMTP瓒呮椂鏃堕暱锛屽崟浣嶆绉掞紝缂虹渷鍊间笉瓒呮椂
+     */
+    private long timeout;
+    /**
+     * Socket杩炴帴瓒呮椂鍊硷紝鍗曚綅姣锛岀己鐪佸�间笉瓒呮椂
+     */
+    private long connectionTimeout;
+    /**
+     * Socket鍐欏嚭瓒呮椂鍊硷紝鍗曚綅姣锛岀己鐪佸�间笉瓒呮椂
+     */
+    private long writeTimeout;
+
+    /**
+     * 鑷畾涔夌殑鍏朵粬灞炴�э紝姝よ嚜瀹氫箟灞炴�т細瑕嗙洊榛樿灞炴��
+     */
+    private final Map<String, Object> customProperty = new HashMap<>();
+
+    // -------------------------------------------------------------- Constructor start
+
+    /**
+     * 鏋勯��,鎵�鏈夊弬鏁伴渶鑷瀹氫箟鎴栦繚鎸侀粯璁ゅ��
+     */
+    public MailAccount() {
+    }
+
+    /**
+     * 鏋勯��
+     *
+     * @param settingPath 閰嶇疆鏂囦欢璺緞
+     */
+    public MailAccount(String settingPath) {
+        this(new Setting(settingPath));
+    }
+
+    /**
+     * 鏋勯��
+     *
+     * @param setting 閰嶇疆鏂囦欢
+     */
+    public MailAccount(Setting setting) {
+        setting.toBean(this);
+    }
+
+    // -------------------------------------------------------------- Constructor end
+
+    /**
+     * 鑾峰緱SMTP鏈嶅姟鍣ㄥ煙鍚�
+     *
+     * @return SMTP鏈嶅姟鍣ㄥ煙鍚�
+     */
+    public String getHost() {
+        return host;
+    }
+
+    /**
+     * 璁剧疆SMTP鏈嶅姟鍣ㄥ煙鍚�
+     *
+     * @param host SMTP鏈嶅姟鍣ㄥ煙鍚�
+     * @return this
+     */
+    public MailAccount setHost(String host) {
+        this.host = host;
+        return this;
+    }
+
+    /**
+     * 鑾峰緱SMTP鏈嶅姟绔彛
+     *
+     * @return SMTP鏈嶅姟绔彛
+     */
+    public Integer getPort() {
+        return port;
+    }
+
+    /**
+     * 璁剧疆SMTP鏈嶅姟绔彛
+     *
+     * @param port SMTP鏈嶅姟绔彛
+     * @return this
+     */
+    public MailAccount setPort(Integer port) {
+        this.port = port;
+        return this;
+    }
+
+    /**
+     * 鏄惁闇�瑕佺敤鎴峰悕瀵嗙爜楠岃瘉
+     *
+     * @return 鏄惁闇�瑕佺敤鎴峰悕瀵嗙爜楠岃瘉
+     */
+    public Boolean isAuth() {
+        return auth;
+    }
+
+    /**
+     * 璁剧疆鏄惁闇�瑕佺敤鎴峰悕瀵嗙爜楠岃瘉
+     *
+     * @param isAuth 鏄惁闇�瑕佺敤鎴峰悕瀵嗙爜楠岃瘉
+     * @return this
+     */
+    public MailAccount setAuth(boolean isAuth) {
+        this.auth = isAuth;
+        return this;
+    }
+
+    /**
+     * 鑾峰彇鐢ㄦ埛鍚�
+     *
+     * @return 鐢ㄦ埛鍚�
+     */
+    public String getUser() {
+        return user;
+    }
+
+    /**
+     * 璁剧疆鐢ㄦ埛鍚�
+     *
+     * @param user 鐢ㄦ埛鍚�
+     * @return this
+     */
+    public MailAccount setUser(String user) {
+        this.user = user;
+        return this;
+    }
+
+    /**
+     * 鑾峰彇瀵嗙爜
+     *
+     * @return 瀵嗙爜
+     */
+    public String getPass() {
+        return pass;
+    }
+
+    /**
+     * 璁剧疆瀵嗙爜
+     *
+     * @param pass 瀵嗙爜
+     * @return this
+     */
+    public MailAccount setPass(String pass) {
+        this.pass = pass;
+        return this;
+    }
+
+    /**
+     * 鑾峰彇鍙戦�佹柟锛岄伒寰猂FC-822鏍囧噯
+     *
+     * @return 鍙戦�佹柟锛岄伒寰猂FC-822鏍囧噯
+     */
+    public String getFrom() {
+        return from;
+    }
+
+    /**
+     * 璁剧疆鍙戦�佹柟锛岄伒寰猂FC-822鏍囧噯<br>
+     * 鍙戜欢浜哄彲浠ユ槸浠ヤ笅褰㈠紡锛�
+     *
+     * <pre>
+     * 1. user@xxx.xx
+     * 2.  name &lt;user@xxx.xx&gt;
+     * </pre>
+     *
+     * @param from 鍙戦�佹柟锛岄伒寰猂FC-822鏍囧噯
+     * @return this
+     */
+    public MailAccount setFrom(String from) {
+        this.from = from;
+        return this;
+    }
+
+    /**
+     * 鏄惁鎵撳紑璋冭瘯妯″紡锛岃皟璇曟ā寮忎細鏄剧ず涓庨偖浠舵湇鍔″櫒閫氫俊杩囩▼锛岄粯璁や笉寮�鍚�
+     *
+     * @return 鏄惁鎵撳紑璋冭瘯妯″紡锛岃皟璇曟ā寮忎細鏄剧ず涓庨偖浠舵湇鍔″櫒閫氫俊杩囩▼锛岄粯璁や笉寮�鍚�
+     * @since 4.0.2
+     */
+    public boolean isDebug() {
+        return debug;
+    }
+
+    /**
+     * 璁剧疆鏄惁鎵撳紑璋冭瘯妯″紡锛岃皟璇曟ā寮忎細鏄剧ず涓庨偖浠舵湇鍔″櫒閫氫俊杩囩▼锛岄粯璁や笉寮�鍚�
+     *
+     * @param debug 鏄惁鎵撳紑璋冭瘯妯″紡锛岃皟璇曟ā寮忎細鏄剧ず涓庨偖浠舵湇鍔″櫒閫氫俊杩囩▼锛岄粯璁や笉寮�鍚�
+     * @return this
+     * @since 4.0.2
+     */
+    public MailAccount setDebug(boolean debug) {
+        this.debug = debug;
+        return this;
+    }
+
+    /**
+     * 鑾峰彇瀛楃闆嗙紪鐮�
+     *
+     * @return 缂栫爜锛屽彲鑳戒负{@code null}
+     */
+    public Charset getCharset() {
+        return charset;
+    }
+
+    /**
+     * 璁剧疆瀛楃闆嗙紪鐮侊紝姝ら�夐」涓嶄細淇敼鍏ㄥ眬閰嶇疆锛岃嫢淇敼鍏ㄥ眬閰嶇疆锛岃璁剧疆姝ら」涓簕@code null}骞惰缃細
+     * <pre>
+     * 	System.setProperty("mail.mime.charset", charset);
+     * </pre>
+     *
+     * @param charset 瀛楃闆嗙紪鐮侊紝{@code null} 鍒欒〃绀轰娇鐢ㄥ叏灞�璁剧疆鐨勯粯璁ょ紪鐮侊紝鍏ㄥ眬缂栫爜涓簃ail.mime.charset绯荤粺灞炴��
+     * @return this
+     */
+    public MailAccount setCharset(Charset charset) {
+        this.charset = charset;
+        return this;
+    }
+
+    /**
+     * 瀵逛簬瓒呴暱鍙傛暟鏄惁鍒囧垎涓哄浠斤紝榛樿涓篺alse锛堝浗鍐呴偖绠遍檮浠朵笉鏀寔鍒囧垎鐨勯檮浠跺悕锛�
+     *
+     * @return 瀵逛簬瓒呴暱鍙傛暟鏄惁鍒囧垎涓哄浠�
+     */
+    public boolean isSplitlongparameters() {
+        return splitlongparameters;
+    }
+
+    /**
+     * 璁剧疆瀵逛簬瓒呴暱鍙傛暟鏄惁鍒囧垎涓哄浠斤紝榛樿涓篺alse锛堝浗鍐呴偖绠遍檮浠朵笉鏀寔鍒囧垎鐨勯檮浠跺悕锛�<br>
+     * 娉ㄦ剰姝ら」涓哄叏灞�璁剧疆锛屾椤逛細璋冪敤
+     * <pre>
+     * System.setProperty("mail.mime.splitlongparameters", true)
+     * </pre>
+     *
+     * @param splitlongparameters 瀵逛簬瓒呴暱鍙傛暟鏄惁鍒囧垎涓哄浠�
+     */
+    public void setSplitlongparameters(boolean splitlongparameters) {
+        this.splitlongparameters = splitlongparameters;
+    }
+
+    /**
+     * 瀵逛簬鏂囦欢鍚嶆槸鍚︿娇鐢▄@link #charset}缂栫爜锛岄粯璁や负 {@code true}
+     *
+     * @return 瀵逛簬鏂囦欢鍚嶆槸鍚︿娇鐢▄@link #charset}缂栫爜锛岄粯璁や负 {@code true}
+     * @since 5.7.16
+     */
+    public boolean isEncodefilename() {
+
+        return encodefilename;
+    }
+
+    /**
+     * 璁剧疆瀵逛簬鏂囦欢鍚嶆槸鍚︿娇鐢▄@link #charset}缂栫爜锛屾閫夐」涓嶄細淇敼鍏ㄥ眬閰嶇疆<br>
+     * 濡傛灉姝ら�夐」璁剧疆涓簕@code false}锛屽垯鏄惁缂栫爜鍙栧喅浜庝袱涓郴缁熷睘鎬э細
+     * <ul>
+     *     <li>mail.mime.encodefilename  鏄惁缂栫爜闄勪欢鏂囦欢鍚�</li>
+     *     <li>mail.mime.charset         缂栫爜鏂囦欢鍚嶇殑缂栫爜</li>
+     * </ul>
+     *
+     * @param encodefilename 瀵逛簬鏂囦欢鍚嶆槸鍚︿娇鐢▄@link #charset}缂栫爜
+     * @since 5.7.16
+     */
+    public void setEncodefilename(boolean encodefilename) {
+        this.encodefilename = encodefilename;
+    }
+
+    /**
+     * 鏄惁浣跨敤 STARTTLS瀹夊叏杩炴帴锛孲TARTTLS鏄绾枃鏈�氫俊鍗忚鐨勬墿灞曘�傚畠灏嗙函鏂囨湰杩炴帴鍗囩骇涓哄姞瀵嗚繛鎺ワ紙TLS鎴朣SL锛夛紝 鑰屼笉鏄娇鐢ㄤ竴涓崟鐙殑鍔犲瘑閫氫俊绔彛銆�
+     *
+     * @return 鏄惁浣跨敤 STARTTLS瀹夊叏杩炴帴
+     */
+    public boolean isStarttlsEnable() {
+        return this.starttlsEnable;
+    }
+
+    /**
+     * 璁剧疆鏄惁浣跨敤STARTTLS瀹夊叏杩炴帴锛孲TARTTLS鏄绾枃鏈�氫俊鍗忚鐨勬墿灞曘�傚畠灏嗙函鏂囨湰杩炴帴鍗囩骇涓哄姞瀵嗚繛鎺ワ紙TLS鎴朣SL锛夛紝 鑰屼笉鏄娇鐢ㄤ竴涓崟鐙殑鍔犲瘑閫氫俊绔彛銆�
+     *
+     * @param startttlsEnable 鏄惁浣跨敤STARTTLS瀹夊叏杩炴帴
+     * @return this
+     */
+    public MailAccount setStarttlsEnable(boolean startttlsEnable) {
+        this.starttlsEnable = startttlsEnable;
+        return this;
+    }
+
+    /**
+     * 鏄惁浣跨敤 SSL瀹夊叏杩炴帴
+     *
+     * @return 鏄惁浣跨敤 SSL瀹夊叏杩炴帴
+     */
+    public Boolean isSslEnable() {
+        return this.sslEnable;
+    }
+
+    /**
+     * 璁剧疆鏄惁浣跨敤SSL瀹夊叏杩炴帴
+     *
+     * @param sslEnable 鏄惁浣跨敤SSL瀹夊叏杩炴帴
+     * @return this
+     */
+    public MailAccount setSslEnable(Boolean sslEnable) {
+        this.sslEnable = sslEnable;
+        return this;
+    }
+
+    /**
+     * 鑾峰彇SSL鍗忚锛屽涓崗璁敤绌烘牸鍒嗛殧
+     *
+     * @return SSL鍗忚锛屽涓崗璁敤绌烘牸鍒嗛殧
+     * @since 5.5.7
+     */
+    public String getSslProtocols() {
+        return sslProtocols;
+    }
+
+    /**
+     * 璁剧疆SSL鍗忚锛屽涓崗璁敤绌烘牸鍒嗛殧
+     *
+     * @param sslProtocols SSL鍗忚锛屽涓崗璁敤绌烘牸鍒嗛殧
+     * @since 5.5.7
+     */
+    public void setSslProtocols(String sslProtocols) {
+        this.sslProtocols = sslProtocols;
+    }
+
+    /**
+     * 鑾峰彇鎸囧畾瀹炵幇javax.net.SocketFactory鎺ュ彛鐨勭被鐨勫悕绉�,杩欎釜绫诲皢琚敤浜庡垱寤篠MTP鐨勫鎺ュ瓧
+     *
+     * @return 鎸囧畾瀹炵幇javax.net.SocketFactory鎺ュ彛鐨勭被鐨勫悕绉�, 杩欎釜绫诲皢琚敤浜庡垱寤篠MTP鐨勫鎺ュ瓧
+     */
+    public String getSocketFactoryClass() {
+        return socketFactoryClass;
+    }
+
+    /**
+     * 璁剧疆鎸囧畾瀹炵幇javax.net.SocketFactory鎺ュ彛鐨勭被鐨勫悕绉�,杩欎釜绫诲皢琚敤浜庡垱寤篠MTP鐨勫鎺ュ瓧
+     *
+     * @param socketFactoryClass 鎸囧畾瀹炵幇javax.net.SocketFactory鎺ュ彛鐨勭被鐨勫悕绉�,杩欎釜绫诲皢琚敤浜庡垱寤篠MTP鐨勫鎺ュ瓧
+     * @return this
+     */
+    public MailAccount setSocketFactoryClass(String socketFactoryClass) {
+        this.socketFactoryClass = socketFactoryClass;
+        return this;
+    }
+
+    /**
+     * 濡傛灉璁剧疆涓簍rue,鏈兘鍒涘缓涓�涓鎺ュ瓧浣跨敤鎸囧畾鐨勫鎺ュ瓧宸ュ巶绫诲皢瀵艰嚧浣跨敤java.net.Socket鍒涘缓鐨勫鎺ュ瓧绫�, 榛樿鍊间负true
+     *
+     * @return 濡傛灉璁剧疆涓簍rue, 鏈兘鍒涘缓涓�涓鎺ュ瓧浣跨敤鎸囧畾鐨勫鎺ュ瓧宸ュ巶绫诲皢瀵艰嚧浣跨敤java.net.Socket鍒涘缓鐨勫鎺ュ瓧绫�, 榛樿鍊间负true
+     */
+    public boolean isSocketFactoryFallback() {
+        return socketFactoryFallback;
+    }
+
+    /**
+     * 濡傛灉璁剧疆涓簍rue,鏈兘鍒涘缓涓�涓鎺ュ瓧浣跨敤鎸囧畾鐨勫鎺ュ瓧宸ュ巶绫诲皢瀵艰嚧浣跨敤java.net.Socket鍒涘缓鐨勫鎺ュ瓧绫�, 榛樿鍊间负true
+     *
+     * @param socketFactoryFallback 濡傛灉璁剧疆涓簍rue,鏈兘鍒涘缓涓�涓鎺ュ瓧浣跨敤鎸囧畾鐨勫鎺ュ瓧宸ュ巶绫诲皢瀵艰嚧浣跨敤java.net.Socket鍒涘缓鐨勫鎺ュ瓧绫�, 榛樿鍊间负true
+     * @return this
+     */
+    public MailAccount setSocketFactoryFallback(boolean socketFactoryFallback) {
+        this.socketFactoryFallback = socketFactoryFallback;
+        return this;
+    }
+
+    /**
+     * 鑾峰彇鎸囧畾鐨勭鍙h繛鎺ュ埌鍦ㄤ娇鐢ㄦ寚瀹氱殑濂楁帴瀛楀伐鍘傘�傚鏋滄病鏈夎缃�,灏嗕娇鐢ㄩ粯璁ょ鍙�
+     *
+     * @return 鎸囧畾鐨勭鍙h繛鎺ュ埌鍦ㄤ娇鐢ㄦ寚瀹氱殑濂楁帴瀛楀伐鍘傘�傚鏋滄病鏈夎缃�,灏嗕娇鐢ㄩ粯璁ょ鍙�
+     */
+    public int getSocketFactoryPort() {
+        return socketFactoryPort;
+    }
+
+    /**
+     * 鎸囧畾鐨勭鍙h繛鎺ュ埌鍦ㄤ娇鐢ㄦ寚瀹氱殑濂楁帴瀛楀伐鍘傘�傚鏋滄病鏈夎缃�,灏嗕娇鐢ㄩ粯璁ょ鍙�
+     *
+     * @param socketFactoryPort 鎸囧畾鐨勭鍙h繛鎺ュ埌鍦ㄤ娇鐢ㄦ寚瀹氱殑濂楁帴瀛楀伐鍘傘�傚鏋滄病鏈夎缃�,灏嗕娇鐢ㄩ粯璁ょ鍙�
+     * @return this
+     */
+    public MailAccount setSocketFactoryPort(int socketFactoryPort) {
+        this.socketFactoryPort = socketFactoryPort;
+        return this;
+    }
+
+    /**
+     * 璁剧疆SMTP瓒呮椂鏃堕暱锛屽崟浣嶆绉掞紝缂虹渷鍊间笉瓒呮椂
+     *
+     * @param timeout SMTP瓒呮椂鏃堕暱锛屽崟浣嶆绉掞紝缂虹渷鍊间笉瓒呮椂
+     * @return this
+     * @since 4.1.17
+     */
+    public MailAccount setTimeout(long timeout) {
+        this.timeout = timeout;
+        return this;
+    }
+
+    /**
+     * 璁剧疆Socket杩炴帴瓒呮椂鍊硷紝鍗曚綅姣锛岀己鐪佸�间笉瓒呮椂
+     *
+     * @param connectionTimeout Socket杩炴帴瓒呮椂鍊硷紝鍗曚綅姣锛岀己鐪佸�间笉瓒呮椂
+     * @return this
+     * @since 4.1.17
+     */
+    public MailAccount setConnectionTimeout(long connectionTimeout) {
+        this.connectionTimeout = connectionTimeout;
+        return this;
+    }
+
+    /**
+     * 璁剧疆Socket鍐欏嚭瓒呮椂鍊硷紝鍗曚綅姣锛岀己鐪佸�间笉瓒呮椂
+     *
+     * @param writeTimeout Socket鍐欏嚭瓒呮椂鍊硷紝鍗曚綅姣锛岀己鐪佸�间笉瓒呮椂
+     * @return this
+     * @since 5.8.3
+     */
+    public MailAccount setWriteTimeout(long writeTimeout) {
+        this.writeTimeout = writeTimeout;
+        return this;
+    }
+
+    /**
+     * 鑾峰彇鑷畾涔夊睘鎬у垪琛�
+     *
+     * @return 鑷畾涔夊弬鏁板垪琛�
+     * @since 5.6.4
+     */
+    public Map<String, Object> getCustomProperty() {
+        return customProperty;
+    }
+
+    /**
+     * 璁剧疆鑷畾涔夊睘鎬э紝濡俶ail.smtp.ssl.socketFactory
+     *
+     * @param key   灞炴�у悕锛岀┖鐧借蹇界暐
+     * @param value 灞炴�у�硷紝 null琚拷鐣�
+     * @return this
+     * @since 5.6.4
+     */
+    public MailAccount setCustomProperty(String key, Object value) {
+        if (StrUtil.isNotBlank(key) && ObjectUtil.isNotNull(value)) {
+            this.customProperty.put(key, value);
+        }
+        return this;
+    }
+
+    /**
+     * 鑾峰緱SMTP鐩稿叧淇℃伅
+     *
+     * @return {@link Properties}
+     */
+    public Properties getSmtpProps() {
+        //鍏ㄥ眬绯荤粺鍙傛暟
+        System.setProperty(SPLIT_LONG_PARAMS, String.valueOf(this.splitlongparameters));
+
+        final Properties p = new Properties();
+        p.put(MAIL_PROTOCOL, "smtp");
+        p.put(SMTP_HOST, this.host);
+        p.put(SMTP_PORT, String.valueOf(this.port));
+        p.put(SMTP_AUTH, String.valueOf(this.auth));
+        if (this.timeout > 0) {
+            p.put(SMTP_TIMEOUT, String.valueOf(this.timeout));
+        }
+        if (this.connectionTimeout > 0) {
+            p.put(SMTP_CONNECTION_TIMEOUT, String.valueOf(this.connectionTimeout));
+        }
+        // issue#2355
+        if (this.writeTimeout > 0) {
+            p.put(SMTP_WRITE_TIMEOUT, String.valueOf(this.writeTimeout));
+        }
+
+        p.put(MAIL_DEBUG, String.valueOf(this.debug));
+
+        if (this.starttlsEnable) {
+            //STARTTLS鏄绾枃鏈�氫俊鍗忚鐨勬墿灞曘�傚畠灏嗙函鏂囨湰杩炴帴鍗囩骇涓哄姞瀵嗚繛鎺ワ紙TLS鎴朣SL锛夛紝 鑰屼笉鏄娇鐢ㄤ竴涓崟鐙殑鍔犲瘑閫氫俊绔彛銆�
+            p.put(STARTTLS_ENABLE, "true");
+
+            if (null == this.sslEnable) {
+                //涓轰簡鍏煎鏃х増鏈紝褰撶敤鎴锋病鏈夋椤归厤缃椂锛屾寜鐓tarttlsEnable寮�鍚姸鎬佹椂瀵瑰緟
+                this.sslEnable = true;
+            }
+        }
+
+        // SSL
+        if (null != this.sslEnable && this.sslEnable) {
+            p.put(SSL_ENABLE, "true");
+            p.put(SOCKET_FACTORY, socketFactoryClass);
+            p.put(SOCKET_FACTORY_FALLBACK, String.valueOf(this.socketFactoryFallback));
+            p.put(SOCKET_FACTORY_PORT, String.valueOf(this.socketFactoryPort));
+            // issue#IZN95@Gitee锛屽湪Linux涓嬮渶鑷畾涔塖SL鍗忚鐗堟湰
+            if (StrUtil.isNotBlank(this.sslProtocols)) {
+                p.put(SSL_PROTOCOLS, this.sslProtocols);
+            }
+        }
+
+        // 琛ュ厖鑷畾涔夊睘鎬э紝鍏佽鑷畾灞炴�ц鐩栧凡缁忚缃殑鍊�
+        p.putAll(this.customProperty);
+
+        return p;
+    }
+
+    /**
+     * 濡傛灉鏌愪簺鍊间负null锛屼娇鐢ㄩ粯璁ゅ��
+     *
+     * @return this
+     */
+    public MailAccount defaultIfEmpty() {
+        // 鍘绘帀鍙戜欢浜虹殑濮撳悕閮ㄥ垎
+        final String fromAddress = InternalMailUtil.parseFirstAddress(this.from, this.charset).getAddress();
+
+        if (StrUtil.isBlank(this.host)) {
+            // 濡傛灉SMTP鍦板潃涓虹┖锛岄粯璁や娇鐢╯mtp.<鍙戜欢浜洪偖绠卞悗缂�>
+            this.host = StrUtil.format("smtp.{}", StrUtil.subSuf(fromAddress, fromAddress.indexOf('@') + 1));
+        }
+        if (StrUtil.isBlank(user)) {
+            // 濡傛灉鐢ㄦ埛鍚嶄负绌猴紝榛樿涓哄彂浠朵汉锛坕ssue#I4FYVY@Gitee锛�
+            //this.user = StrUtil.subPre(fromAddress, fromAddress.indexOf('@'));
+            this.user = fromAddress;
+        }
+        if (null == this.auth) {
+            // 濡傛灉瀵嗙爜闈炵┖鐧斤紝鍒欎娇鐢ㄨ璇佹ā寮�
+            this.auth = (false == StrUtil.isBlank(this.pass));
+        }
+        if (null == this.port) {
+            // 绔彛鍦⊿SL鐘舵�佷笅榛樿涓巗ocketFactoryPort涓�鑷达紝闈濻SL鐘舵�佷笅榛樿涓�25
+            this.port = (null != this.sslEnable && this.sslEnable) ? this.socketFactoryPort : 25;
+        }
+        if (null == this.charset) {
+            // 榛樿UTF-8缂栫爜
+            this.charset = CharsetUtil.CHARSET_UTF_8;
+        }
+
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return "MailAccount [host=" + host + ", port=" + port + ", auth=" + auth + ", user=" + user + ", pass=" + (StrUtil.isEmpty(this.pass) ? "" : "******") + ", from=" + from + ", startttlsEnable="
+            + starttlsEnable + ", socketFactoryClass=" + socketFactoryClass + ", socketFactoryFallback=" + socketFactoryFallback + ", socketFactoryPort=" + socketFactoryPort + "]";
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailException.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailException.java
new file mode 100644
index 0000000..cc199d4
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailException.java
@@ -0,0 +1,40 @@
+package org.dromara.common.mail.utils;
+
+import cn.hutool.core.exceptions.ExceptionUtil;
+import cn.hutool.core.util.StrUtil;
+
+import java.io.Serial;
+
+/**
+ * 閭欢寮傚父
+ *
+ * @author xiaoleilu
+ */
+public class MailException extends RuntimeException {
+    @Serial
+    private static final long serialVersionUID = 8247610319171014183L;
+
+    public MailException(Throwable e) {
+        super(ExceptionUtil.getMessage(e), e);
+    }
+
+    public MailException(String message) {
+        super(message);
+    }
+
+    public MailException(String messageTemplate, Object... params) {
+        super(StrUtil.format(messageTemplate, params));
+    }
+
+    public MailException(String message, Throwable throwable) {
+        super(message, throwable);
+    }
+
+    public MailException(String message, Throwable throwable, boolean enableSuppression, boolean writableStackTrace) {
+        super(message, throwable, enableSuppression, writableStackTrace);
+    }
+
+    public MailException(Throwable throwable, String messageTemplate, Object... params) {
+        super(StrUtil.format(messageTemplate, params), throwable);
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailUtils.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailUtils.java
new file mode 100644
index 0000000..040cc57
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/MailUtils.java
@@ -0,0 +1,467 @@
+package org.dromara.common.mail.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.CharUtil;
+import cn.hutool.core.util.StrUtil;
+import jakarta.mail.Authenticator;
+import jakarta.mail.Session;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * 閭欢宸ュ叿绫�
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class MailUtils {
+
+    private static final MailAccount ACCOUNT = SpringUtils.getBean(MailAccount.class);
+
+    /**
+     * 鑾峰彇閭欢鍙戦�佸疄渚�
+     */
+    public static MailAccount getMailAccount() {
+        return ACCOUNT;
+    }
+
+    /**
+     * 鑾峰彇閭欢鍙戦�佸疄渚� (鑷畾涔夊彂閫佷汉浠ュ強鎺堟潈鐮�)
+     *
+     * @param user 鍙戦�佷汉
+     * @param pass 鎺堟潈鐮�
+     */
+    public static MailAccount getMailAccount(String from, String user, String pass) {
+        ACCOUNT.setFrom(StringUtils.blankToDefault(from, ACCOUNT.getFrom()));
+        ACCOUNT.setUser(StringUtils.blankToDefault(user, ACCOUNT.getUser()));
+        ACCOUNT.setPass(StringUtils.blankToDefault(pass, ACCOUNT.getPass()));
+        return ACCOUNT;
+    }
+
+    /**
+     * 浣跨敤閰嶇疆鏂囦欢涓缃殑璐︽埛鍙戦�佹枃鏈偖浠讹紝鍙戦�佺粰鍗曚釜鎴栧涓敹浠朵汉<br>
+     * 澶氫釜鏀朵欢浜哄彲浠ヤ娇鐢ㄩ�楀彿鈥�,鈥濆垎闅旓紝涔熷彲浠ラ�氳繃鍒嗗彿鈥�;鈥濆垎闅�
+     *
+     * @param to      鏀朵欢浜�
+     * @param subject 鏍囬
+     * @param content 姝f枃
+     * @param files   闄勪欢鍒楄〃
+     * @return message-id
+     * @since 3.2.0
+     */
+    public static String sendText(String to, String subject, String content, File... files) {
+        return send(to, subject, content, false, files);
+    }
+
+    /**
+     * 浣跨敤閰嶇疆鏂囦欢涓缃殑璐︽埛鍙戦�丠TML閭欢锛屽彂閫佺粰鍗曚釜鎴栧涓敹浠朵汉<br>
+     * 澶氫釜鏀朵欢浜哄彲浠ヤ娇鐢ㄩ�楀彿鈥�,鈥濆垎闅旓紝涔熷彲浠ラ�氳繃鍒嗗彿鈥�;鈥濆垎闅�
+     *
+     * @param to      鏀朵欢浜�
+     * @param subject 鏍囬
+     * @param content 姝f枃
+     * @param files   闄勪欢鍒楄〃
+     * @return message-id
+     * @since 3.2.0
+     */
+    public static String sendHtml(String to, String subject, String content, File... files) {
+        return send(to, subject, content, true, files);
+    }
+
+    /**
+     * 浣跨敤閰嶇疆鏂囦欢涓缃殑璐︽埛鍙戦�侀偖浠讹紝鍙戦�佸崟涓垨澶氫釜鏀朵欢浜�<br>
+     * 澶氫釜鏀朵欢浜哄彲浠ヤ娇鐢ㄩ�楀彿鈥�,鈥濆垎闅旓紝涔熷彲浠ラ�氳繃鍒嗗彿鈥�;鈥濆垎闅�
+     *
+     * @param to      鏀朵欢浜�
+     * @param subject 鏍囬
+     * @param content 姝f枃
+     * @param isHtml  鏄惁涓篐TML
+     * @param files   闄勪欢鍒楄〃
+     * @return message-id
+     */
+    public static String send(String to, String subject, String content, boolean isHtml, File... files) {
+        return send(splitAddress(to), subject, content, isHtml, files);
+    }
+
+    /**
+     * 浣跨敤閰嶇疆鏂囦欢涓缃殑璐︽埛鍙戦�侀偖浠讹紝鍙戦�佸崟涓垨澶氫釜鏀朵欢浜�<br>
+     * 澶氫釜鏀朵欢浜恒�佹妱閫佷汉銆佸瘑閫佷汉鍙互浣跨敤閫楀彿鈥�,鈥濆垎闅旓紝涔熷彲浠ラ�氳繃鍒嗗彿鈥�;鈥濆垎闅�
+     *
+     * @param to      鏀朵欢浜猴紝鍙互浣跨敤閫楀彿鈥�,鈥濆垎闅旓紝涔熷彲浠ラ�氳繃鍒嗗彿鈥�;鈥濆垎闅�
+     * @param cc      鎶勯�佷汉锛屽彲浠ヤ娇鐢ㄩ�楀彿鈥�,鈥濆垎闅旓紝涔熷彲浠ラ�氳繃鍒嗗彿鈥�;鈥濆垎闅�
+     * @param bcc     瀵嗛�佷汉锛屽彲浠ヤ娇鐢ㄩ�楀彿鈥�,鈥濆垎闅旓紝涔熷彲浠ラ�氳繃鍒嗗彿鈥�;鈥濆垎闅�
+     * @param subject 鏍囬
+     * @param content 姝f枃
+     * @param isHtml  鏄惁涓篐TML
+     * @param files   闄勪欢鍒楄〃
+     * @return message-id
+     * @since 4.0.3
+     */
+    public static String send(String to, String cc, String bcc, String subject, String content, boolean isHtml, File... files) {
+        return send(splitAddress(to), splitAddress(cc), splitAddress(bcc), subject, content, isHtml, files);
+    }
+
+    /**
+     * 浣跨敤閰嶇疆鏂囦欢涓缃殑璐︽埛鍙戦�佹枃鏈偖浠讹紝鍙戦�佺粰澶氫汉
+     *
+     * @param tos     鏀朵欢浜哄垪琛�
+     * @param subject 鏍囬
+     * @param content 姝f枃
+     * @param files   闄勪欢鍒楄〃
+     * @return message-id
+     */
+    public static String sendText(Collection<String> tos, String subject, String content, File... files) {
+        return send(tos, subject, content, false, files);
+    }
+
+    /**
+     * 浣跨敤閰嶇疆鏂囦欢涓缃殑璐︽埛鍙戦�丠TML閭欢锛屽彂閫佺粰澶氫汉
+     *
+     * @param tos     鏀朵欢浜哄垪琛�
+     * @param subject 鏍囬
+     * @param content 姝f枃
+     * @param files   闄勪欢鍒楄〃
+     * @return message-id
+     * @since 3.2.0
+     */
+    public static String sendHtml(Collection<String> tos, String subject, String content, File... files) {
+        return send(tos, subject, content, true, files);
+    }
+
+    /**
+     * 浣跨敤閰嶇疆鏂囦欢涓缃殑璐︽埛鍙戦�侀偖浠讹紝鍙戦�佺粰澶氫汉
+     *
+     * @param tos     鏀朵欢浜哄垪琛�
+     * @param subject 鏍囬
+     * @param content 姝f枃
+     * @param isHtml  鏄惁涓篐TML
+     * @param files   闄勪欢鍒楄〃
+     * @return message-id
+     */
+    public static String send(Collection<String> tos, String subject, String content, boolean isHtml, File... files) {
+        return send(tos, null, null, subject, content, isHtml, files);
+    }
+
+    /**
+     * 浣跨敤閰嶇疆鏂囦欢涓缃殑璐︽埛鍙戦�侀偖浠讹紝鍙戦�佺粰澶氫汉
+     *
+     * @param tos     鏀朵欢浜哄垪琛�
+     * @param ccs     鎶勯�佷汉鍒楄〃锛屽彲浠ヤ负null鎴栫┖
+     * @param bccs    瀵嗛�佷汉鍒楄〃锛屽彲浠ヤ负null鎴栫┖
+     * @param subject 鏍囬
+     * @param content 姝f枃
+     * @param isHtml  鏄惁涓篐TML
+     * @param files   闄勪欢鍒楄〃
+     * @return message-id
+     * @since 4.0.3
+     */
+    public static String send(Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, boolean isHtml, File... files) {
+        return send(getMailAccount(), true, tos, ccs, bccs, subject, content, null, isHtml, files);
+    }
+
+    // ------------------------------------------------------------------------------------------------------------------------------- Custom MailAccount
+
+    /**
+     * 鍙戦�侀偖浠剁粰澶氫汉
+     *
+     * @param mailAccount 閭欢璁よ瘉瀵硅薄
+     * @param to          鏀朵欢浜猴紝澶氫釜鏀朵欢浜洪�楀彿鎴栬�呭垎鍙烽殧寮�
+     * @param subject     鏍囬
+     * @param content     姝f枃
+     * @param isHtml      鏄惁涓篐TML鏍煎紡
+     * @param files       闄勪欢鍒楄〃
+     * @return message-id
+     * @since 3.2.0
+     */
+    public static String send(MailAccount mailAccount, String to, String subject, String content, boolean isHtml, File... files) {
+        return send(mailAccount, splitAddress(to), subject, content, isHtml, files);
+    }
+
+    /**
+     * 鍙戦�侀偖浠剁粰澶氫汉
+     *
+     * @param mailAccount 閭欢甯愭埛淇℃伅
+     * @param tos         鏀朵欢浜哄垪琛�
+     * @param subject     鏍囬
+     * @param content     姝f枃
+     * @param isHtml      鏄惁涓篐TML鏍煎紡
+     * @param files       闄勪欢鍒楄〃
+     * @return message-id
+     */
+    public static String send(MailAccount mailAccount, Collection<String> tos, String subject, String content, boolean isHtml, File... files) {
+        return send(mailAccount, tos, null, null, subject, content, isHtml, files);
+    }
+
+    /**
+     * 鍙戦�侀偖浠剁粰澶氫汉
+     *
+     * @param mailAccount 閭欢甯愭埛淇℃伅
+     * @param tos         鏀朵欢浜哄垪琛�
+     * @param ccs         鎶勯�佷汉鍒楄〃锛屽彲浠ヤ负null鎴栫┖
+     * @param bccs        瀵嗛�佷汉鍒楄〃锛屽彲浠ヤ负null鎴栫┖
+     * @param subject     鏍囬
+     * @param content     姝f枃
+     * @param isHtml      鏄惁涓篐TML鏍煎紡
+     * @param files       闄勪欢鍒楄〃
+     * @return message-id
+     * @since 4.0.3
+     */
+    public static String send(MailAccount mailAccount, Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, boolean isHtml, File... files) {
+        return send(mailAccount, false, tos, ccs, bccs, subject, content, null, isHtml, files);
+    }
+
+    /**
+     * 浣跨敤閰嶇疆鏂囦欢涓缃殑璐︽埛鍙戦�丠TML閭欢锛屽彂閫佺粰鍗曚釜鎴栧涓敹浠朵汉<br>
+     * 澶氫釜鏀朵欢浜哄彲浠ヤ娇鐢ㄩ�楀彿鈥�,鈥濆垎闅旓紝涔熷彲浠ラ�氳繃鍒嗗彿鈥�;鈥濆垎闅�
+     *
+     * @param to       鏀朵欢浜�
+     * @param subject  鏍囬
+     * @param content  姝f枃
+     * @param imageMap 鍥剧墖涓庡崰浣嶇锛屽崰浣嶇鏍煎紡涓篶id:$IMAGE_PLACEHOLDER
+     * @param files    闄勪欢鍒楄〃
+     * @return message-id
+     * @since 3.2.0
+     */
+    public static String sendHtml(String to, String subject, String content, Map<String, InputStream> imageMap, File... files) {
+        return send(to, subject, content, imageMap, true, files);
+    }
+
+    /**
+     * 浣跨敤閰嶇疆鏂囦欢涓缃殑璐︽埛鍙戦�侀偖浠讹紝鍙戦�佸崟涓垨澶氫釜鏀朵欢浜�<br>
+     * 澶氫釜鏀朵欢浜哄彲浠ヤ娇鐢ㄩ�楀彿鈥�,鈥濆垎闅旓紝涔熷彲浠ラ�氳繃鍒嗗彿鈥�;鈥濆垎闅�
+     *
+     * @param to       鏀朵欢浜�
+     * @param subject  鏍囬
+     * @param content  姝f枃
+     * @param imageMap 鍥剧墖涓庡崰浣嶇锛屽崰浣嶇鏍煎紡涓篶id:$IMAGE_PLACEHOLDER
+     * @param isHtml   鏄惁涓篐TML
+     * @param files    闄勪欢鍒楄〃
+     * @return message-id
+     */
+    public static String send(String to, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
+        return send(splitAddress(to), subject, content, imageMap, isHtml, files);
+    }
+
+    /**
+     * 浣跨敤閰嶇疆鏂囦欢涓缃殑璐︽埛鍙戦�侀偖浠讹紝鍙戦�佸崟涓垨澶氫釜鏀朵欢浜�<br>
+     * 澶氫釜鏀朵欢浜恒�佹妱閫佷汉銆佸瘑閫佷汉鍙互浣跨敤閫楀彿鈥�,鈥濆垎闅旓紝涔熷彲浠ラ�氳繃鍒嗗彿鈥�;鈥濆垎闅�
+     *
+     * @param to       鏀朵欢浜猴紝鍙互浣跨敤閫楀彿鈥�,鈥濆垎闅旓紝涔熷彲浠ラ�氳繃鍒嗗彿鈥�;鈥濆垎闅�
+     * @param cc       鎶勯�佷汉锛屽彲浠ヤ娇鐢ㄩ�楀彿鈥�,鈥濆垎闅旓紝涔熷彲浠ラ�氳繃鍒嗗彿鈥�;鈥濆垎闅�
+     * @param bcc      瀵嗛�佷汉锛屽彲浠ヤ娇鐢ㄩ�楀彿鈥�,鈥濆垎闅旓紝涔熷彲浠ラ�氳繃鍒嗗彿鈥�;鈥濆垎闅�
+     * @param subject  鏍囬
+     * @param content  姝f枃
+     * @param imageMap 鍥剧墖涓庡崰浣嶇锛屽崰浣嶇鏍煎紡涓篶id:$IMAGE_PLACEHOLDER
+     * @param isHtml   鏄惁涓篐TML
+     * @param files    闄勪欢鍒楄〃
+     * @return message-id
+     * @since 4.0.3
+     */
+    public static String send(String to, String cc, String bcc, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
+        return send(splitAddress(to), splitAddress(cc), splitAddress(bcc), subject, content, imageMap, isHtml, files);
+    }
+
+    /**
+     * 浣跨敤閰嶇疆鏂囦欢涓缃殑璐︽埛鍙戦�丠TML閭欢锛屽彂閫佺粰澶氫汉
+     *
+     * @param tos      鏀朵欢浜哄垪琛�
+     * @param subject  鏍囬
+     * @param content  姝f枃
+     * @param imageMap 鍥剧墖涓庡崰浣嶇锛屽崰浣嶇鏍煎紡涓篶id:$IMAGE_PLACEHOLDER
+     * @param files    闄勪欢鍒楄〃
+     * @return message-id
+     * @since 3.2.0
+     */
+    public static String sendHtml(Collection<String> tos, String subject, String content, Map<String, InputStream> imageMap, File... files) {
+        return send(tos, subject, content, imageMap, true, files);
+    }
+
+    /**
+     * 浣跨敤閰嶇疆鏂囦欢涓缃殑璐︽埛鍙戦�侀偖浠讹紝鍙戦�佺粰澶氫汉
+     *
+     * @param tos      鏀朵欢浜哄垪琛�
+     * @param subject  鏍囬
+     * @param content  姝f枃
+     * @param imageMap 鍥剧墖涓庡崰浣嶇锛屽崰浣嶇鏍煎紡涓篶id:$IMAGE_PLACEHOLDER
+     * @param isHtml   鏄惁涓篐TML
+     * @param files    闄勪欢鍒楄〃
+     * @return message-id
+     */
+    public static String send(Collection<String> tos, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
+        return send(tos, null, null, subject, content, imageMap, isHtml, files);
+    }
+
+    /**
+     * 浣跨敤閰嶇疆鏂囦欢涓缃殑璐︽埛鍙戦�侀偖浠讹紝鍙戦�佺粰澶氫汉
+     *
+     * @param tos      鏀朵欢浜哄垪琛�
+     * @param ccs      鎶勯�佷汉鍒楄〃锛屽彲浠ヤ负null鎴栫┖
+     * @param bccs     瀵嗛�佷汉鍒楄〃锛屽彲浠ヤ负null鎴栫┖
+     * @param subject  鏍囬
+     * @param content  姝f枃
+     * @param imageMap 鍥剧墖涓庡崰浣嶇锛屽崰浣嶇鏍煎紡涓篶id:$IMAGE_PLACEHOLDER
+     * @param isHtml   鏄惁涓篐TML
+     * @param files    闄勪欢鍒楄〃
+     * @return message-id
+     * @since 4.0.3
+     */
+    public static String send(Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
+        return send(getMailAccount(), true, tos, ccs, bccs, subject, content, imageMap, isHtml, files);
+    }
+
+    // ------------------------------------------------------------------------------------------------------------------------------- Custom MailAccount
+
+    /**
+     * 鍙戦�侀偖浠剁粰澶氫汉
+     *
+     * @param mailAccount 閭欢璁よ瘉瀵硅薄
+     * @param to          鏀朵欢浜猴紝澶氫釜鏀朵欢浜洪�楀彿鎴栬�呭垎鍙烽殧寮�
+     * @param subject     鏍囬
+     * @param content     姝f枃
+     * @param imageMap    鍥剧墖涓庡崰浣嶇锛屽崰浣嶇鏍煎紡涓篶id:$IMAGE_PLACEHOLDER
+     * @param isHtml      鏄惁涓篐TML鏍煎紡
+     * @param files       闄勪欢鍒楄〃
+     * @return message-id
+     * @since 3.2.0
+     */
+    public static String send(MailAccount mailAccount, String to, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
+        return send(mailAccount, splitAddress(to), subject, content, imageMap, isHtml, files);
+    }
+
+    /**
+     * 鍙戦�侀偖浠剁粰澶氫汉
+     *
+     * @param mailAccount 閭欢甯愭埛淇℃伅
+     * @param tos         鏀朵欢浜哄垪琛�
+     * @param subject     鏍囬
+     * @param content     姝f枃
+     * @param imageMap    鍥剧墖涓庡崰浣嶇锛屽崰浣嶇鏍煎紡涓篶id:$IMAGE_PLACEHOLDER
+     * @param isHtml      鏄惁涓篐TML鏍煎紡
+     * @param files       闄勪欢鍒楄〃
+     * @return message-id
+     * @since 4.6.3
+     */
+    public static String send(MailAccount mailAccount, Collection<String> tos, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
+        return send(mailAccount, tos, null, null, subject, content, imageMap, isHtml, files);
+    }
+
+    /**
+     * 鍙戦�侀偖浠剁粰澶氫汉
+     *
+     * @param mailAccount 閭欢甯愭埛淇℃伅
+     * @param tos         鏀朵欢浜哄垪琛�
+     * @param ccs         鎶勯�佷汉鍒楄〃锛屽彲浠ヤ负null鎴栫┖
+     * @param bccs        瀵嗛�佷汉鍒楄〃锛屽彲浠ヤ负null鎴栫┖
+     * @param subject     鏍囬
+     * @param content     姝f枃
+     * @param imageMap    鍥剧墖涓庡崰浣嶇锛屽崰浣嶇鏍煎紡涓篶id:$IMAGE_PLACEHOLDER
+     * @param isHtml      鏄惁涓篐TML鏍煎紡
+     * @param files       闄勪欢鍒楄〃
+     * @return message-id
+     * @since 4.6.3
+     */
+    public static String send(MailAccount mailAccount, Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, Map<String, InputStream> imageMap,
+                              boolean isHtml, File... files) {
+        return send(mailAccount, false, tos, ccs, bccs, subject, content, imageMap, isHtml, files);
+    }
+
+    /**
+     * 鏍规嵁閰嶇疆鏂囦欢锛岃幏鍙栭偖浠跺鎴风浼氳瘽
+     *
+     * @param mailAccount 閭欢璐︽埛閰嶇疆
+     * @param isSingleton 鏄惁鍗曚緥锛堝叏灞�鍏变韩浼氳瘽锛�
+     * @return {@link Session}
+     * @since 5.5.7
+     */
+    public static Session getSession(MailAccount mailAccount, boolean isSingleton) {
+        Authenticator authenticator = null;
+        if (mailAccount.isAuth()) {
+            authenticator = new UserPassAuthenticator(mailAccount.getUser(), mailAccount.getPass());
+        }
+
+        return isSingleton ? Session.getDefaultInstance(mailAccount.getSmtpProps(), authenticator) //
+            : Session.getInstance(mailAccount.getSmtpProps(), authenticator);
+    }
+
+    // ------------------------------------------------------------------------------------------------------------------------ Private method start
+
+    /**
+     * 鍙戦�侀偖浠剁粰澶氫汉
+     *
+     * @param mailAccount      閭欢甯愭埛淇℃伅
+     * @param useGlobalSession 鏄惁鍏ㄥ眬鍏变韩Session
+     * @param tos              鏀朵欢浜哄垪琛�
+     * @param ccs              鎶勯�佷汉鍒楄〃锛屽彲浠ヤ负null鎴栫┖
+     * @param bccs             瀵嗛�佷汉鍒楄〃锛屽彲浠ヤ负null鎴栫┖
+     * @param subject          鏍囬
+     * @param content          姝f枃
+     * @param imageMap         鍥剧墖涓庡崰浣嶇锛屽崰浣嶇鏍煎紡涓篶id:${cid}
+     * @param isHtml           鏄惁涓篐TML鏍煎紡
+     * @param files            闄勪欢鍒楄〃
+     * @return message-id
+     * @since 4.6.3
+     */
+    private static String send(MailAccount mailAccount, boolean useGlobalSession, Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content,
+                               Map<String, InputStream> imageMap, boolean isHtml, File... files) {
+        final Mail mail = Mail.create(mailAccount).setUseGlobalSession(useGlobalSession);
+
+        // 鍙�夋妱閫佷汉
+        if (CollUtil.isNotEmpty(ccs)) {
+            mail.setCcs(ccs.toArray(new String[0]));
+        }
+        // 鍙�夊瘑閫佷汉
+        if (CollUtil.isNotEmpty(bccs)) {
+            mail.setBccs(bccs.toArray(new String[0]));
+        }
+
+        mail.setTos(tos.toArray(new String[0]));
+        mail.setTitle(subject);
+        mail.setContent(content);
+        mail.setHtml(isHtml);
+        mail.setFiles(files);
+
+        // 鍥剧墖
+        if (MapUtil.isNotEmpty(imageMap)) {
+            for (Map.Entry<String, InputStream> entry : imageMap.entrySet()) {
+                mail.addImage(entry.getKey(), entry.getValue());
+                // 鍏抽棴娴�
+                IoUtil.close(entry.getValue());
+            }
+        }
+
+        return mail.send();
+    }
+
+    /**
+     * 灏嗗涓仈绯讳汉杞负鍒楄〃锛屽垎闅旂涓洪�楀彿鎴栬�呭垎鍙�
+     *
+     * @param addresses 澶氫釜鑱旂郴浜猴紝濡傛灉涓虹┖杩斿洖null
+     * @return 鑱旂郴浜哄垪琛�
+     */
+    private static List<String> splitAddress(String addresses) {
+        if (StrUtil.isBlank(addresses)) {
+            return null;
+        }
+
+        List<String> result;
+        if (StrUtil.contains(addresses, CharUtil.COMMA)) {
+            result = StrUtil.splitTrim(addresses, CharUtil.COMMA);
+        } else if (StrUtil.contains(addresses, ';')) {
+            result = StrUtil.splitTrim(addresses, ';');
+        } else {
+            result = CollUtil.newArrayList(addresses);
+        }
+        return result;
+    }
+    // ------------------------------------------------------------------------------------------------------------------------ Private method end
+
+}
diff --git a/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/UserPassAuthenticator.java b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/UserPassAuthenticator.java
new file mode 100644
index 0000000..fbbe5e3
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mail/src/main/java/org/dromara/common/mail/utils/UserPassAuthenticator.java
@@ -0,0 +1,33 @@
+package org.dromara.common.mail.utils;
+
+import jakarta.mail.Authenticator;
+import jakarta.mail.PasswordAuthentication;
+
+/**
+ * 鐢ㄦ埛鍚嶅瘑鐮侀獙璇佸櫒
+ *
+ * @author looly
+ * @since 3.1.2
+ */
+public class UserPassAuthenticator extends Authenticator {
+
+    private final String user;
+    private final String pass;
+
+    /**
+     * 鏋勯��
+     *
+     * @param user 鐢ㄦ埛鍚�
+     * @param pass 瀵嗙爜
+     */
+    public UserPassAuthenticator(String user, String pass) {
+        this.user = user;
+        this.pass = pass;
+    }
+
+    @Override
+    protected PasswordAuthentication getPasswordAuthentication() {
+        return new PasswordAuthentication(this.user, this.pass);
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-mail/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-mail/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..aa1474e
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mail/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.mail.config.MailConfiguration
diff --git a/ruoyi-common/ruoyi-common-mybatis/pom.xml b/ruoyi-common/ruoyi-common-mybatis/pom.xml
new file mode 100644
index 0000000..64876fb
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mybatis/pom.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-mybatis</artifactId>
+
+    <description>
+        ruoyi-common-mybatis 鏁版嵁搴撴湇鍔�
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-satoken</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-dubbo</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.mybatis.spring.boot</groupId>
+            <artifactId>mybatis-spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.mybatis</groupId>
+                    <artifactId>mybatis-spring</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!-- sql鎬ц兘鍒嗘瀽鎻掍欢 -->
+        <dependency>
+            <groupId>p6spy</groupId>
+            <artifactId>p6spy</artifactId>
+        </dependency>
+
+        <!-- Dynamic DataSource -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
+            <version>${dynamic-ds.version}</version>
+        </dependency>
+
+        <!-- Mysql Connector -->
+        <dependency>
+            <groupId>com.mysql</groupId>
+            <artifactId>mysql-connector-j</artifactId>
+        </dependency>
+        <!-- Oracle -->
+        <dependency>
+            <groupId>com.oracle.database.jdbc</groupId>
+            <artifactId>ojdbc8</artifactId>
+        </dependency>
+        <!-- PostgreSql -->
+        <dependency>
+            <groupId>org.postgresql</groupId>
+            <artifactId>postgresql</artifactId>
+        </dependency>
+        <!-- SqlServer -->
+        <dependency>
+            <groupId>com.microsoft.sqlserver</groupId>
+            <artifactId>mssql-jdbc</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>
diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataColumn.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataColumn.java
new file mode 100644
index 0000000..aca470f
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataColumn.java
@@ -0,0 +1,28 @@
+package org.dromara.common.mybatis.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 鏁版嵁鏉冮檺
+ *
+ * 涓�涓敞瑙e彧鑳藉搴斾竴涓ā鏉�
+ *
+ * @author Lion Li
+ * @version 3.5.0
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface DataColumn {
+
+    /**
+     * 鍗犱綅绗﹀叧閿瓧
+     */
+    String[] key() default "deptName";
+
+    /**
+     * 鍗犱綅绗︽浛鎹㈠��
+     */
+    String[] value() default "dept_id";
+
+}
diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataPermission.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataPermission.java
new file mode 100644
index 0000000..f4351e3
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataPermission.java
@@ -0,0 +1,18 @@
+package org.dromara.common.mybatis.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 鏁版嵁鏉冮檺缁�
+ *
+ * @author Lion Li
+ * @version 3.5.0
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface DataPermission {
+
+    DataColumn[] value();
+
+}
diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfiguration.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfiguration.java
new file mode 100644
index 0000000..e0ec34b
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfiguration.java
@@ -0,0 +1,116 @@
+package org.dromara.common.mybatis.config;
+
+import cn.hutool.core.net.NetUtil;
+import com.baomidou.mybatisplus.autoconfigure.DdlApplicationRunner;
+import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
+import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
+import com.baomidou.mybatisplus.extension.ddl.IDdl;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import org.dromara.common.core.factory.YmlPropertySourceFactory;
+import org.dromara.common.mybatis.handler.InjectionMetaObjectHandler;
+import org.dromara.common.mybatis.interceptor.PlusDataPermissionInterceptor;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+import java.util.List;
+
+/**
+ * mybatis-plus閰嶇疆绫�(涓嬫柟娉ㄩ噴鏈夋彃浠朵粙缁�)
+ *
+ * @author Lion Li
+ */
+@EnableTransactionManagement(proxyTargetClass = true)
+@AutoConfiguration(before = MybatisPlusAutoConfiguration.class)
+@MapperScan("${mybatis-plus.mapperPackage}")
+@PropertySource(value = "classpath:common-mybatis.yml", factory = YmlPropertySourceFactory.class)
+public class MybatisPlusConfiguration {
+
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        // 鏁版嵁鏉冮檺澶勭悊
+        interceptor.addInnerInterceptor(dataPermissionInterceptor());
+        // 鍒嗛〉鎻掍欢
+        interceptor.addInnerInterceptor(paginationInnerInterceptor());
+        // 涔愯閿佹彃浠�
+        interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor());
+        return interceptor;
+    }
+
+    /**
+     * 鏁版嵁鏉冮檺鎷︽埅鍣�
+     */
+    public PlusDataPermissionInterceptor dataPermissionInterceptor() {
+        return new PlusDataPermissionInterceptor();
+    }
+
+    /**
+     * 鍒嗛〉鎻掍欢锛岃嚜鍔ㄨ瘑鍒暟鎹簱绫诲瀷
+     */
+    public PaginationInnerInterceptor paginationInnerInterceptor() {
+        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
+        // 璁剧疆鏈�澶у崟椤甸檺鍒舵暟閲忥紝榛樿 500 鏉★紝-1 涓嶅彈闄愬埗
+        paginationInnerInterceptor.setMaxLimit(-1L);
+        // 鍒嗛〉鍚堢悊鍖�
+        paginationInnerInterceptor.setOverflow(true);
+        return paginationInnerInterceptor;
+    }
+
+    /**
+     * 涔愯閿佹彃浠�
+     */
+    public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
+        return new OptimisticLockerInnerInterceptor();
+    }
+
+    /**
+     * 鍏冨璞″瓧娈靛~鍏呮帶鍒跺櫒
+     */
+    @Bean
+    public MetaObjectHandler metaObjectHandler() {
+        return new InjectionMetaObjectHandler();
+    }
+
+    /**
+     * 浣跨敤缃戝崱淇℃伅缁戝畾闆姳鐢熸垚鍣�
+     * 闃叉闆嗙兢闆姳ID閲嶅
+     */
+    @Bean
+    public IdentifierGenerator idGenerator() {
+        return new DefaultIdentifierGenerator(NetUtil.getLocalhost());
+    }
+
+    /**
+     * PaginationInnerInterceptor 鍒嗛〉鎻掍欢锛岃嚜鍔ㄨ瘑鍒暟鎹簱绫诲瀷
+     * https://baomidou.com/pages/97710a/
+     * OptimisticLockerInnerInterceptor 涔愯閿佹彃浠�
+     * https://baomidou.com/pages/0d93c0/
+     * MetaObjectHandler 鍏冨璞″瓧娈靛~鍏呮帶鍒跺櫒
+     * https://baomidou.com/pages/4c6bcf/
+     * ISqlInjector sql娉ㄥ叆鍣�
+     * https://baomidou.com/pages/42ea4a/
+     * BlockAttackInnerInterceptor 濡傛灉鏄鍏ㄨ〃鐨勫垹闄ゆ垨鏇存柊鎿嶄綔锛屽氨浼氱粓姝㈣鎿嶄綔
+     * https://baomidou.com/pages/f9a237/
+     * IllegalSQLInnerInterceptor sql鎬ц兘瑙勮寖鎻掍欢(鍨冨溇SQL鎷︽埅)
+     * IdentifierGenerator 鑷畾涔変富閿瓥鐣�
+     * https://baomidou.com/pages/568eb2/
+     * TenantLineInnerInterceptor 澶氱鎴锋彃浠�
+     * https://baomidou.com/pages/aef2f2/
+     * DynamicTableNameInnerInterceptor 鍔ㄦ�佽〃鍚嶆彃浠�
+     * https://baomidou.com/pages/2a45ff/
+     */
+
+    @Bean
+    public DdlApplicationRunner ddlApplicationRunner(@Autowired(required = false) List<IDdl> ddlList) {
+        return new DdlApplicationRunner(ddlList);
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/domain/BaseEntity.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/domain/BaseEntity.java
new file mode 100644
index 0000000..820b49a
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/domain/BaseEntity.java
@@ -0,0 +1,71 @@
+package org.dromara.common.mybatis.core.domain;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Entity鍩虹被
+ *
+ * @author Lion Li
+ */
+
+@Data
+public class BaseEntity implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 鎼滅储鍊�
+     */
+    @JsonIgnore
+    @TableField(exist = false)
+    private String searchValue;
+
+    /**
+     * 鍒涘缓閮ㄩ棬
+     */
+    @TableField(fill = FieldFill.INSERT)
+    private Long createDept;
+
+    /**
+     * 鍒涘缓鑰�
+     */
+    @TableField(fill = FieldFill.INSERT)
+    private Long createBy;
+
+    /**
+     * 鍒涘缓鏃堕棿
+     */
+    @TableField(fill = FieldFill.INSERT)
+    private Date createTime;
+
+    /**
+     * 鏇存柊鑰�
+     */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Long updateBy;
+
+    /**
+     * 鏇存柊鏃堕棿
+     */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Date updateTime;
+
+    /**
+     * 璇锋眰鍙傛暟
+     */
+    @JsonInclude(JsonInclude.Include.NON_EMPTY)
+    @TableField(exist = false)
+    private Map<String, Object> params = new HashMap<>();
+
+}
diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java
new file mode 100644
index 0000000..b14e2d2
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java
@@ -0,0 +1,198 @@
+package org.dromara.common.mybatis.core.mapper;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.toolkit.Db;
+import org.apache.ibatis.logging.Log;
+import org.apache.ibatis.logging.LogFactory;
+import org.dromara.common.core.utils.MapstructUtils;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * 鑷畾涔� Mapper 鎺ュ彛, 瀹炵幇 鑷畾涔夋墿灞�
+ *
+ * @param <T> table 娉涘瀷
+ * @param <V> vo 娉涘瀷
+ * @author Lion Li
+ * @since 2021-05-13
+ */
+@SuppressWarnings("unchecked")
+public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
+
+    Log log = LogFactory.getLog(BaseMapperPlus.class);
+
+    default Class<V> currentVoClass() {
+        return (Class<V>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 1);
+    }
+
+    default Class<T> currentModelClass() {
+        return (Class<T>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 0);
+    }
+
+    default List<T> selectList() {
+        return this.selectList(new QueryWrapper<>());
+    }
+
+    /**
+     * 鎵归噺鎻掑叆
+     */
+    default boolean insertBatch(Collection<T> entityList) {
+        return Db.saveBatch(entityList);
+    }
+
+    /**
+     * 鎵归噺鏇存柊
+     */
+    default boolean updateBatchById(Collection<T> entityList) {
+        return Db.updateBatchById(entityList);
+    }
+
+    /**
+     * 鎵归噺鎻掑叆鎴栨洿鏂�
+     */
+    default boolean insertOrUpdateBatch(Collection<T> entityList) {
+        return Db.saveOrUpdateBatch(entityList);
+    }
+
+    /**
+     * 鎵归噺鎻掑叆(鍖呭惈闄愬埗鏉℃暟)
+     */
+    default boolean insertBatch(Collection<T> entityList, int batchSize) {
+        return Db.saveBatch(entityList, batchSize);
+    }
+
+    /**
+     * 鎵归噺鏇存柊(鍖呭惈闄愬埗鏉℃暟)
+     */
+    default boolean updateBatchById(Collection<T> entityList, int batchSize) {
+        return Db.updateBatchById(entityList, batchSize);
+    }
+
+    /**
+     * 鎵归噺鎻掑叆鎴栨洿鏂�(鍖呭惈闄愬埗鏉℃暟)
+     */
+    default boolean insertOrUpdateBatch(Collection<T> entityList, int batchSize) {
+        return Db.saveOrUpdateBatch(entityList, batchSize);
+    }
+
+    /**
+     * 鎻掑叆鎴栨洿鏂�(鍖呭惈闄愬埗鏉℃暟)
+     */
+    default boolean insertOrUpdate(T entity) {
+        return Db.saveOrUpdate(entity);
+    }
+
+    default V selectVoById(Serializable id) {
+        return selectVoById(id, this.currentVoClass());
+    }
+
+    /**
+     * 鏍规嵁 ID 鏌ヨ
+     */
+    default <C> C selectVoById(Serializable id, Class<C> voClass) {
+        T obj = this.selectById(id);
+        if (ObjectUtil.isNull(obj)) {
+            return null;
+        }
+        return MapstructUtils.convert(obj, voClass);
+    }
+
+    default List<V> selectVoBatchIds(Collection<? extends Serializable> idList) {
+        return selectVoBatchIds(idList, this.currentVoClass());
+    }
+
+    /**
+     * 鏌ヨ锛堟牴鎹甀D 鎵归噺鏌ヨ锛�
+     */
+    default <C> List<C> selectVoBatchIds(Collection<? extends Serializable> idList, Class<C> voClass) {
+        List<T> list = this.selectBatchIds(idList);
+        if (CollUtil.isEmpty(list)) {
+            return CollUtil.newArrayList();
+        }
+        return MapstructUtils.convert(list, voClass);
+    }
+
+    default List<V> selectVoByMap(Map<String, Object> map) {
+        return selectVoByMap(map, this.currentVoClass());
+    }
+
+    /**
+     * 鏌ヨ锛堟牴鎹� columnMap 鏉′欢锛�
+     */
+    default <C> List<C> selectVoByMap(Map<String, Object> map, Class<C> voClass) {
+        List<T> list = this.selectByMap(map);
+        if (CollUtil.isEmpty(list)) {
+            return CollUtil.newArrayList();
+        }
+        return MapstructUtils.convert(list, voClass);
+    }
+
+    default V selectVoOne(Wrapper<T> wrapper) {
+        return selectVoOne(wrapper, this.currentVoClass());
+    }
+
+    /**
+     * 鏍规嵁 entity 鏉′欢锛屾煡璇竴鏉¤褰�
+     */
+    default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass) {
+        T obj = this.selectOne(wrapper);
+        if (ObjectUtil.isNull(obj)) {
+            return null;
+        }
+        return MapstructUtils.convert(obj, voClass);
+    }
+
+    default List<V> selectVoList() {
+        return selectVoList(new QueryWrapper<>(), this.currentVoClass());
+    }
+
+    default List<V> selectVoList(Wrapper<T> wrapper) {
+        return selectVoList(wrapper, this.currentVoClass());
+    }
+
+    /**
+     * 鏍规嵁 entity 鏉′欢锛屾煡璇㈠叏閮ㄨ褰�
+     */
+    default <C> List<C> selectVoList(Wrapper<T> wrapper, Class<C> voClass) {
+        List<T> list = this.selectList(wrapper);
+        if (CollUtil.isEmpty(list)) {
+            return CollUtil.newArrayList();
+        }
+        return MapstructUtils.convert(list, voClass);
+    }
+
+    default <P extends IPage<V>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper) {
+        return selectVoPage(page, wrapper, this.currentVoClass());
+    }
+
+    /**
+     * 鍒嗛〉鏌ヨVO
+     */
+    default <C, P extends IPage<C>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper, Class<C> voClass) {
+        List<T> list = this.selectList(page, wrapper);
+        IPage<C> voPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
+        if (CollUtil.isEmpty(list)) {
+            return (P) voPage;
+        }
+        voPage.setRecords(MapstructUtils.convert(list, voClass));
+        return (P) voPage;
+    }
+
+    default <C> List<C> selectObjs(Wrapper<T> wrapper, Function<? super Object, C> mapper) {
+        return this.selectObjs(wrapper).stream().filter(Objects::nonNull).map(mapper).collect(Collectors.toList());
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/PageQuery.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/PageQuery.java
new file mode 100644
index 0000000..8ef4a57
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/PageQuery.java
@@ -0,0 +1,114 @@
+package org.dromara.common.mybatis.core.page;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.metadata.OrderItem;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.sql.SqlUtil;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 鍒嗛〉鏌ヨ瀹炰綋绫�
+ *
+ * @author Lion Li
+ */
+
+@Data
+public class PageQuery implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 鍒嗛〉澶у皬
+     */
+    private Integer pageSize;
+
+    /**
+     * 褰撳墠椤垫暟
+     */
+    private Integer pageNum;
+
+    /**
+     * 鎺掑簭鍒�
+     */
+    private String orderByColumn;
+
+    /**
+     * 鎺掑簭鐨勬柟鍚慸esc鎴栬�卆sc
+     */
+    private String isAsc;
+
+    /**
+     * 褰撳墠璁板綍璧峰绱㈠紩 榛樿鍊�
+     */
+    public static final int DEFAULT_PAGE_NUM = 1;
+
+    /**
+     * 姣忛〉鏄剧ず璁板綍鏁� 榛樿鍊� 榛樿鏌ュ叏閮�
+     */
+    public static final int DEFAULT_PAGE_SIZE = Integer.MAX_VALUE;
+
+    public <T> Page<T> build() {
+        Integer pageNum = ObjectUtil.defaultIfNull(getPageNum(), DEFAULT_PAGE_NUM);
+        Integer pageSize = ObjectUtil.defaultIfNull(getPageSize(), DEFAULT_PAGE_SIZE);
+        if (pageNum <= 0) {
+            pageNum = DEFAULT_PAGE_NUM;
+        }
+        Page<T> page = new Page<>(pageNum, pageSize);
+        List<OrderItem> orderItems = buildOrderItem();
+        if (CollUtil.isNotEmpty(orderItems)) {
+            page.addOrder(orderItems);
+        }
+        return page;
+    }
+
+    /**
+     * 鏋勫缓鎺掑簭
+     *
+     * 鏀寔鐨勭敤娉曞涓�:
+     * {isAsc:"asc",orderByColumn:"id"} order by id asc
+     * {isAsc:"asc",orderByColumn:"id,createTime"} order by id asc,create_time asc
+     * {isAsc:"desc",orderByColumn:"id,createTime"} order by id desc,create_time desc
+     * {isAsc:"asc,desc",orderByColumn:"id,createTime"} order by id asc,create_time desc
+     */
+    private List<OrderItem> buildOrderItem() {
+        if (StringUtils.isBlank(orderByColumn) || StringUtils.isBlank(isAsc)) {
+            return null;
+        }
+        String orderBy = SqlUtil.escapeOrderBySql(orderByColumn);
+        orderBy = StringUtils.toUnderScoreCase(orderBy);
+
+        // 鍏煎鍓嶇鎺掑簭绫诲瀷
+        isAsc = StringUtils.replaceEach(isAsc, new String[]{"ascending", "descending"}, new String[]{"asc", "desc"});
+
+        String[] orderByArr = orderBy.split(StringUtils.SEPARATOR);
+        String[] isAscArr = isAsc.split(StringUtils.SEPARATOR);
+        if (isAscArr.length != 1 && isAscArr.length != orderByArr.length) {
+            throw new ServiceException("鎺掑簭鍙傛暟鏈夎");
+        }
+
+        List<OrderItem> list = new ArrayList<>();
+        // 姣忎釜瀛楁鍚勮嚜鎺掑簭
+        for (int i = 0; i < orderByArr.length; i++) {
+            String orderByStr = orderByArr[i];
+            String isAscStr = isAscArr.length == 1 ? isAscArr[0] : isAscArr[i];
+            if ("asc".equals(isAscStr)) {
+                list.add(OrderItem.asc(orderByStr));
+            } else if ("desc".equals(isAscStr)) {
+                list.add(OrderItem.desc(orderByStr));
+            } else {
+                throw new ServiceException("鎺掑簭鍙傛暟鏈夎");
+            }
+        }
+        return list;
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/TableDataInfo.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/TableDataInfo.java
new file mode 100644
index 0000000..a4b6799
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/TableDataInfo.java
@@ -0,0 +1,81 @@
+package org.dromara.common.mybatis.core.page;
+
+import cn.hutool.http.HttpStatus;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 琛ㄦ牸鍒嗛〉鏁版嵁瀵硅薄
+ *
+ * @author Lion Li
+ */
+
+@Data
+@NoArgsConstructor
+public class TableDataInfo<T> implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 鎬昏褰曟暟
+     */
+    private long total;
+
+    /**
+     * 鍒楄〃鏁版嵁
+     */
+    private List<T> rows;
+
+    /**
+     * 娑堟伅鐘舵�佺爜
+     */
+    private int code;
+
+    /**
+     * 娑堟伅鍐呭
+     */
+    private String msg;
+
+    /**
+     * 鍒嗛〉
+     *
+     * @param list  鍒楄〃鏁版嵁
+     * @param total 鎬昏褰曟暟
+     */
+    public TableDataInfo(List<T> list, long total) {
+        this.rows = list;
+        this.total = total;
+    }
+
+    public static <T> TableDataInfo<T> build(IPage<T> page) {
+        TableDataInfo<T> rspData = new TableDataInfo<>();
+        rspData.setCode(HttpStatus.HTTP_OK);
+        rspData.setMsg("鏌ヨ鎴愬姛");
+        rspData.setRows(page.getRecords());
+        rspData.setTotal(page.getTotal());
+        return rspData;
+    }
+
+    public static <T> TableDataInfo<T> build(List<T> list) {
+        TableDataInfo<T> rspData = new TableDataInfo<>();
+        rspData.setCode(HttpStatus.HTTP_OK);
+        rspData.setMsg("鏌ヨ鎴愬姛");
+        rspData.setRows(list);
+        rspData.setTotal(list.size());
+        return rspData;
+    }
+
+    public static <T> TableDataInfo<T> build() {
+        TableDataInfo<T> rspData = new TableDataInfo<>();
+        rspData.setCode(HttpStatus.HTTP_OK);
+        rspData.setMsg("鏌ヨ鎴愬姛");
+        return rspData;
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataBaseType.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataBaseType.java
new file mode 100644
index 0000000..93487e9
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataBaseType.java
@@ -0,0 +1,49 @@
+package org.dromara.common.mybatis.enums;
+
+import org.dromara.common.core.utils.StringUtils;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 鏁版嵁搴撶被鍨�
+ *
+ * @author Lion Li
+ */
+@Getter
+@AllArgsConstructor
+public enum DataBaseType {
+
+    /**
+     * MySQL
+     */
+    MY_SQL("MySQL"),
+
+    /**
+     * Oracle
+     */
+    ORACLE("Oracle"),
+
+    /**
+     * PostgreSQL
+     */
+    POSTGRE_SQL("PostgreSQL"),
+
+    /**
+     * SQL Server
+     */
+    SQL_SERVER("Microsoft SQL Server");
+
+    private final String type;
+
+    public static DataBaseType find(String databaseProductName) {
+        if (StringUtils.isBlank(databaseProductName)) {
+            return null;
+        }
+        for (DataBaseType type : values()) {
+            if (type.getType().equals(databaseProductName)) {
+                return type;
+            }
+        }
+        return null;
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataScopeType.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataScopeType.java
new file mode 100644
index 0000000..9ea66b0
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataScopeType.java
@@ -0,0 +1,73 @@
+package org.dromara.common.mybatis.enums;
+
+import org.dromara.common.core.utils.StringUtils;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.dromara.common.mybatis.helper.DataPermissionHelper;
+
+/**
+ * 鏁版嵁鏉冮檺绫诲瀷
+ * <p>
+ * 璇硶鏀寔 spel 妯℃澘琛ㄨ揪寮�
+ * <p>
+ * 鍐呯疆鏁版嵁 user 褰撳墠鐢ㄦ埛 鍐呭鍙傝�� LoginUser
+ * 濡傞渶鎵╁睍鏁版嵁 鍙娇鐢� {@link DataPermissionHelper} 鎿嶄綔
+ * 鍐呯疆鏈嶅姟 sdss 绯荤粺鏁版嵁鏉冮檺鏈嶅姟 鍐呭鍙傝�� SysDataScopeService
+ * 濡傞渶鎵╁睍鏇村鑷畾涔夋湇鍔� 鍙互鍙傝�� sdss 鑷缂栧啓
+ *
+ * @author Lion Li
+ * @version 3.5.0
+ */
+@Getter
+@AllArgsConstructor
+public enum DataScopeType {
+
+    /**
+     * 鍏ㄩ儴鏁版嵁鏉冮檺
+     */
+    ALL("1", "", ""),
+
+    /**
+     * 鑷畾鏁版嵁鏉冮檺
+     */
+    CUSTOM("2", " #{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} ) ", " 1 = 0 "),
+
+    /**
+     * 閮ㄩ棬鏁版嵁鏉冮檺
+     */
+    DEPT("3", " #{#deptName} = #{#user.deptId} ", " 1 = 0 "),
+
+    /**
+     * 閮ㄩ棬鍙婁互涓嬫暟鎹潈闄�
+     */
+    DEPT_AND_CHILD("4", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} )", " 1 = 0 "),
+
+    /**
+     * 浠呮湰浜烘暟鎹潈闄�
+     */
+    SELF("5", " #{#userName} = #{#user.userId} ", " 1 = 0 ");
+
+    private final String code;
+
+    /**
+     * 璇硶 閲囩敤 spel 妯℃澘琛ㄨ揪寮�
+     */
+    private final String sqlTemplate;
+
+    /**
+     * 涓嶆弧瓒� sqlTemplate 鍒欏~鍏�
+     */
+    private final String elseSql;
+
+    public static DataScopeType findCode(String code) {
+        if (StringUtils.isBlank(code)) {
+            return null;
+        }
+        for (DataScopeType type : values()) {
+            if (type.getCode().equals(code)) {
+                return type;
+            }
+        }
+        return null;
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/filter/DubboDataPermissionFilter.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/filter/DubboDataPermissionFilter.java
new file mode 100644
index 0000000..61e549d
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/filter/DubboDataPermissionFilter.java
@@ -0,0 +1,28 @@
+package org.dromara.common.mybatis.filter;
+
+import org.dromara.common.mybatis.helper.DataPermissionHelper;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.common.extension.Activate;
+import org.apache.dubbo.rpc.*;
+
+import java.util.Map;
+
+/**
+ * dubbo 鏁版嵁鏉冮檺鍙傛暟浼犻��
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@Activate(group = {CommonConstants.CONSUMER})
+public class DubboDataPermissionFilter implements Filter {
+
+    @Override
+    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
+        RpcServiceContext context = RpcContext.getServiceContext();
+        Map<String, Object> dataPermissionContext = DataPermissionHelper.getContext();
+        context.setObjectAttachment(DataPermissionHelper.DATA_PERMISSION_KEY, dataPermissionContext);
+        return invoker.invoke(invocation);
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/InjectionMetaObjectHandler.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/InjectionMetaObjectHandler.java
new file mode 100644
index 0000000..c4ff3bf
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/InjectionMetaObjectHandler.java
@@ -0,0 +1,82 @@
+package org.dromara.common.mybatis.handler;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.http.HttpStatus;
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.reflection.MetaObject;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.system.api.model.LoginUser;
+
+import java.util.Date;
+
+/**
+ * MP娉ㄥ叆澶勭悊鍣�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class InjectionMetaObjectHandler implements MetaObjectHandler {
+
+    @Override
+    public void insertFill(MetaObject metaObject) {
+        try {
+            if (ObjectUtil.isNotNull(metaObject)
+                && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) {
+                Date current = ObjectUtil.isNotNull(baseEntity.getCreateTime())
+                    ? baseEntity.getCreateTime() : new Date();
+                baseEntity.setCreateTime(current);
+                baseEntity.setUpdateTime(current);
+                LoginUser loginUser = getLoginUser();
+                if (ObjectUtil.isNotNull(loginUser)) {
+                    Long userId = ObjectUtil.isNotNull(baseEntity.getCreateBy())
+                        ? baseEntity.getCreateBy() : loginUser.getUserId();
+                    // 褰撳墠宸茬櫥褰� 涓� 鍒涘缓浜轰负绌� 鍒欏~鍏�
+                    baseEntity.setCreateBy(userId);
+                    // 褰撳墠宸茬櫥褰� 涓� 鏇存柊浜轰负绌� 鍒欏~鍏�
+                    baseEntity.setUpdateBy(userId);
+                    baseEntity.setCreateDept(ObjectUtil.isNotNull(baseEntity.getCreateDept())
+                        ? baseEntity.getCreateDept() : loginUser.getDeptId());
+                }
+            }
+        } catch (Exception e) {
+            throw new ServiceException("鑷姩娉ㄥ叆寮傚父 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED);
+        }
+    }
+
+    @Override
+    public void updateFill(MetaObject metaObject) {
+        try {
+            if (ObjectUtil.isNotNull(metaObject)
+                && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) {
+                Date current = new Date();
+                // 鏇存柊鏃堕棿濉厖(涓嶇涓轰笉涓虹┖)
+                baseEntity.setUpdateTime(current);
+                LoginUser loginUser = getLoginUser();
+                // 褰撳墠宸茬櫥褰� 鏇存柊浜哄~鍏�(涓嶇涓轰笉涓虹┖)
+                if (ObjectUtil.isNotNull(loginUser)) {
+                    baseEntity.setUpdateBy(loginUser.getUserId());
+                }
+            }
+        } catch (Exception e) {
+            throw new ServiceException("鑷姩娉ㄥ叆寮傚父 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED);
+        }
+    }
+
+    /**
+     * 鑾峰彇鐧诲綍鐢ㄦ埛
+     */
+    private LoginUser getLoginUser() {
+        LoginUser loginUser;
+        try {
+            loginUser = LoginHelper.getLoginUser();
+        } catch (Exception e) {
+            log.warn("鑷姩娉ㄥ叆璀﹀憡 => 鐢ㄦ埛鏈櫥褰�");
+            return null;
+        }
+        return loginUser;
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/MybatisExceptionHandler.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/MybatisExceptionHandler.java
new file mode 100644
index 0000000..c517fa7
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/MybatisExceptionHandler.java
@@ -0,0 +1,46 @@
+package org.dromara.common.mybatis.handler;
+
+import org.dromara.common.core.domain.R;
+import lombok.extern.slf4j.Slf4j;
+import org.mybatis.spring.MyBatisSystemException;
+import org.springframework.dao.DuplicateKeyException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+/**
+ * Mybatis寮傚父澶勭悊鍣�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@RestControllerAdvice
+public class MybatisExceptionHandler {
+
+    /**
+     * 涓婚敭鎴朥NIQUE绱㈠紩锛屾暟鎹噸澶嶅紓甯�
+     */
+    @ExceptionHandler(DuplicateKeyException.class)
+    public R<Void> handleDuplicateKeyException(DuplicateKeyException e, HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        log.error("璇锋眰鍦板潃'{}',鏁版嵁搴撲腑宸插瓨鍦ㄨ褰�'{}'", requestURI, e.getMessage());
+        return R.fail("鏁版嵁搴撲腑宸插瓨鍦ㄨ璁板綍锛岃鑱旂郴绠$悊鍛樼‘璁�");
+    }
+
+    /**
+     * Mybatis绯荤粺寮傚父 閫氱敤澶勭悊
+     */
+    @ExceptionHandler(MyBatisSystemException.class)
+    public R<Void> handleCannotFindDataSourceException(MyBatisSystemException e, HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        String message = e.getMessage();
+        if ("CannotFindDataSourceException".contains(message)) {
+            log.error("璇锋眰鍦板潃'{}', 鏈壘鍒版暟鎹簮", requestURI);
+            return R.fail("鏈壘鍒版暟鎹簮锛岃鑱旂郴绠$悊鍛樼‘璁�");
+        }
+        log.error("璇锋眰鍦板潃'{}', Mybatis绯荤粺寮傚父", requestURI, e);
+        return R.fail(message);
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java
new file mode 100644
index 0000000..dc4f9cb
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java
@@ -0,0 +1,186 @@
+package org.dromara.common.mybatis.handler;
+
+import cn.hutool.core.annotation.AnnotationUtil;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ClassUtil;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.extern.slf4j.Slf4j;
+import net.sf.jsqlparser.JSQLParserException;
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.expression.Parenthesis;
+import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
+import net.sf.jsqlparser.parser.CCJSqlParserUtil;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.annotation.DataColumn;
+import org.dromara.common.mybatis.annotation.DataPermission;
+import org.dromara.common.mybatis.enums.DataScopeType;
+import org.dromara.common.mybatis.helper.DataPermissionHelper;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.system.api.model.LoginUser;
+import org.dromara.system.api.model.RoleDTO;
+import org.springframework.context.expression.BeanFactoryResolver;
+import org.springframework.expression.BeanResolver;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.ParserContext;
+import org.springframework.expression.common.TemplateParserContext;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+
+import java.lang.reflect.Method;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+
+/**
+ * 鏁版嵁鏉冮檺杩囨护
+ *
+ * @author Lion Li
+ * @version 3.5.0
+ */
+@Slf4j
+public class PlusDataPermissionHandler {
+
+    /**
+     * 鏂规硶鎴栫被(鍚嶇О) 涓� 娉ㄨВ鐨勬槧灏勫叧绯荤紦瀛�
+     */
+    private final Map<String, DataPermission> dataPermissionCacheMap = new ConcurrentHashMap<>();
+
+    /**
+     * spel 瑙f瀽鍣�
+     */
+    private final ExpressionParser parser = new SpelExpressionParser();
+    private final ParserContext parserContext = new TemplateParserContext();
+    /**
+     * bean瑙f瀽鍣� 鐢ㄤ簬澶勭悊 spel 琛ㄨ揪寮忎腑瀵� bean 鐨勮皟鐢�
+     */
+    private final BeanResolver beanResolver = new BeanFactoryResolver(SpringUtils.getBeanFactory());
+
+
+    public Expression getSqlSegment(Expression where, String mappedStatementId, boolean isSelect) {
+        DataColumn[] dataColumns = findAnnotation(mappedStatementId);
+        LoginUser currentUser = DataPermissionHelper.getVariable("user");
+        if (ObjectUtil.isNull(currentUser)) {
+            currentUser = LoginHelper.getLoginUser();
+            DataPermissionHelper.setVariable("user", currentUser);
+        }
+        // 濡傛灉鏄秴绾х鐞嗗憳鎴栫鎴风鐞嗗憳锛屽垯涓嶈繃婊ゆ暟鎹�
+        if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) {
+            return where;
+        }
+        String dataFilterSql = buildDataFilter(dataColumns, isSelect);
+        if (StringUtils.isBlank(dataFilterSql)) {
+            return where;
+        }
+        try {
+            Expression expression = CCJSqlParserUtil.parseExpression(dataFilterSql);
+            // 鏁版嵁鏉冮檺浣跨敤鍗曠嫭鐨勬嫭鍙� 闃叉涓庡叾浠栨潯浠跺啿绐�
+            Parenthesis parenthesis = new Parenthesis(expression);
+            if (ObjectUtil.isNotNull(where)) {
+                return new AndExpression(where, parenthesis);
+            } else {
+                return parenthesis;
+            }
+        } catch (JSQLParserException e) {
+            throw new ServiceException("鏁版嵁鏉冮檺瑙f瀽寮傚父 => " + e.getMessage());
+        }
+    }
+
+    /**
+     * 鏋勯�犳暟鎹繃婊ql
+     */
+    private String buildDataFilter(DataColumn[] dataColumns, boolean isSelect) {
+        // 鏇存柊鎴栧垹闄ら渶婊¤冻鎵�鏈夋潯浠�
+        String joinStr = isSelect ? " OR " : " AND ";
+        LoginUser user = DataPermissionHelper.getVariable("user");
+        StandardEvaluationContext context = new StandardEvaluationContext();
+        context.setBeanResolver(beanResolver);
+        DataPermissionHelper.getContext().forEach(context::setVariable);
+        Set<String> conditions = new HashSet<>();
+        for (RoleDTO role : user.getRoles()) {
+            user.setRoleId(role.getRoleId());
+            // 鑾峰彇瑙掕壊鏉冮檺娉涘瀷
+            DataScopeType type = DataScopeType.findCode(role.getDataScope());
+            if (ObjectUtil.isNull(type)) {
+                throw new ServiceException("瑙掕壊鏁版嵁鑼冨洿寮傚父 => " + role.getDataScope());
+            }
+            // 鍏ㄩ儴鏁版嵁鏉冮檺鐩存帴杩斿洖
+            if (type == DataScopeType.ALL) {
+                return "";
+            }
+            boolean isSuccess = false;
+            for (DataColumn dataColumn : dataColumns) {
+                if (dataColumn.key().length != dataColumn.value().length) {
+                    throw new ServiceException("瑙掕壊鏁版嵁鑼冨洿寮傚父 => key涓巚alue闀垮害涓嶅尮閰�");
+                }
+                // 涓嶅寘鍚� key 鍙橀噺 鍒欎笉澶勭悊
+                if (!StringUtils.containsAny(type.getSqlTemplate(),
+                    Arrays.stream(dataColumn.key()).map(key -> "#" + key).toArray(String[]::new)
+                )) {
+                    continue;
+                }
+                // 璁剧疆娉ㄨВ鍙橀噺 key 涓鸿〃杈惧紡鍙橀噺 value 涓哄彉閲忓��
+                for (int i = 0; i < dataColumn.key().length; i++) {
+                    context.setVariable(dataColumn.key()[i], dataColumn.value()[i]);
+                }
+
+                // 瑙f瀽sql妯℃澘骞跺~鍏�
+                String sql = parser.parseExpression(type.getSqlTemplate(), parserContext).getValue(context, String.class);
+                conditions.add(joinStr + sql);
+                isSuccess = true;
+            }
+            // 鏈鐞嗘垚鍔熷垯濉厖鍏滃簳鏂规
+            if (!isSuccess && StringUtils.isNotBlank(type.getElseSql())) {
+                conditions.add(joinStr + type.getElseSql());
+            }
+        }
+
+        if (CollUtil.isNotEmpty(conditions)) {
+            String sql = StreamUtils.join(conditions, Function.identity(), "");
+            return sql.substring(joinStr.length());
+        }
+        return "";
+    }
+
+    public DataColumn[] findAnnotation(String mappedStatementId) {
+        StringBuilder sb = new StringBuilder(mappedStatementId);
+        int index = sb.lastIndexOf(".");
+        String clazzName = sb.substring(0, index);
+        String methodName = sb.substring(index + 1, sb.length());
+        Class<?> clazz;
+        try {
+            clazz = ClassUtil.loadClass(clazzName);
+        } catch (Exception e) {
+            return null;
+        }
+        List<Method> methods = Arrays.stream(ClassUtil.getDeclaredMethods(clazz))
+            .filter(method -> method.getName().equals(methodName)).toList();
+        DataPermission dataPermission;
+        // 鑾峰彇鏂规硶娉ㄨВ
+        for (Method method : methods) {
+            dataPermission = dataPermissionCacheMap.get(mappedStatementId);
+            if (ObjectUtil.isNotNull(dataPermission)) {
+                return dataPermission.value();
+            }
+            if (AnnotationUtil.hasAnnotation(method, DataPermission.class)) {
+                dataPermission = AnnotationUtil.getAnnotation(method, DataPermission.class);
+                dataPermissionCacheMap.put(mappedStatementId, dataPermission);
+                return dataPermission.value();
+            }
+        }
+        dataPermission = dataPermissionCacheMap.get(clazz.getName());
+        if (ObjectUtil.isNotNull(dataPermission)) {
+            return dataPermission.value();
+        }
+        // 鑾峰彇绫绘敞瑙�
+        if (AnnotationUtil.hasAnnotation(clazz, DataPermission.class)) {
+            dataPermission = AnnotationUtil.getAnnotation(clazz, DataPermission.class);
+            dataPermissionCacheMap.put(clazz.getName(), dataPermission);
+            return dataPermission.value();
+        }
+        return null;
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java
new file mode 100644
index 0000000..4a29383
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java
@@ -0,0 +1,82 @@
+package org.dromara.common.mybatis.helper;
+
+import cn.hutool.core.convert.Convert;
+import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.mybatis.enums.DataBaseType;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 鏁版嵁搴撳姪鎵�
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class DataBaseHelper {
+
+    private static final DynamicRoutingDataSource DS = SpringUtils.getBean(DynamicRoutingDataSource.class);
+
+    /**
+     * 鑾峰彇褰撳墠鏁版嵁搴撶被鍨�
+     */
+    public static DataBaseType getDataBaseType() {
+        DataSource dataSource = DS.determineDataSource();
+        try (Connection conn = dataSource.getConnection()) {
+            DatabaseMetaData metaData = conn.getMetaData();
+            String databaseProductName = metaData.getDatabaseProductName();
+            return DataBaseType.find(databaseProductName);
+        } catch (SQLException e) {
+            throw new ServiceException(e.getMessage());
+        }
+    }
+
+    public static boolean isMySql() {
+        return DataBaseType.MY_SQL == getDataBaseType();
+    }
+
+    public static boolean isOracle() {
+        return DataBaseType.ORACLE == getDataBaseType();
+    }
+
+    public static boolean isPostgerSql() {
+        return DataBaseType.POSTGRE_SQL == getDataBaseType();
+    }
+
+    public static boolean isSqlServer() {
+        return DataBaseType.SQL_SERVER == getDataBaseType();
+    }
+
+    public static String findInSet(Object var1, String var2) {
+        DataBaseType dataBasyType = getDataBaseType();
+        String var = Convert.toStr(var1);
+        if (dataBasyType == DataBaseType.SQL_SERVER) {
+            // charindex(',100,' , ',0,100,101,') <> 0
+            return "charindex(',%s,' , ','+%s+',') <> 0".formatted(var, var2);
+        } else if (dataBasyType == DataBaseType.POSTGRE_SQL) {
+            // (select position(',100,' in ',0,100,101,')) <> 0
+            return "(select position(',%s,' in ','||%s||',')) <> 0".formatted(var, var2);
+        } else if (dataBasyType == DataBaseType.ORACLE) {
+            // instr(',0,100,101,' , ',100,') <> 0
+            return "instr(','||%s||',' , ',%s,') <> 0".formatted(var2, var);
+        }
+        // find_in_set('100' , '0,100,101')
+        return "find_in_set('%s' , %s) <> 0".formatted(var, var2);
+    }
+
+    /**
+     * 鑾峰彇褰撳墠鍔犺浇鐨勬暟鎹簱鍚�
+     */
+    public static List<String> getDataSourceNameList() {
+        return new ArrayList<>(DS.getDataSources().keySet());
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionHelper.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionHelper.java
new file mode 100644
index 0000000..0beae4d
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionHelper.java
@@ -0,0 +1,93 @@
+package org.dromara.common.mybatis.helper;
+
+import cn.dev33.satoken.context.SaHolder;
+import cn.dev33.satoken.context.model.SaStorage;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy;
+import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Supplier;
+
+/**
+ * 鏁版嵁鏉冮檺鍔╂墜
+ *
+ * @author Lion Li
+ * @version 3.5.0
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@SuppressWarnings("unchecked cast")
+public class DataPermissionHelper {
+
+    public static final String DATA_PERMISSION_KEY = "data:permission";
+
+    public static <T> T getVariable(String key) {
+        Map<String, Object> context = getContext();
+        return (T) context.get(key);
+    }
+
+
+    public static void setVariable(String key, Object value) {
+        Map<String, Object> context = getContext();
+        context.put(key, value);
+    }
+
+    public static Map<String, Object> getContext() {
+        SaStorage saStorage = SaHolder.getStorage();
+        Object attribute = saStorage.get(DATA_PERMISSION_KEY);
+        if (ObjectUtil.isNull(attribute)) {
+            saStorage.set(DATA_PERMISSION_KEY, new HashMap<>());
+            attribute = saStorage.get(DATA_PERMISSION_KEY);
+        }
+        if (attribute instanceof Map map) {
+            return map;
+        }
+        throw new NullPointerException("data permission context type exception");
+    }
+
+    /**
+     * 寮�鍚拷鐣ユ暟鎹潈闄�(寮�鍚悗闇�鎵嬪姩璋冪敤 {@link #disableIgnore()} 鍏抽棴)
+     */
+    public static void enableIgnore() {
+        InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().dataPermission(true).build());
+    }
+
+    /**
+     * 鍏抽棴蹇界暐鏁版嵁鏉冮檺
+     */
+    public static void disableIgnore() {
+        InterceptorIgnoreHelper.clearIgnoreStrategy();
+    }
+
+    /**
+     * 鍦ㄥ拷鐣ユ暟鎹潈闄愪腑鎵ц
+     *
+     * @param handle 澶勭悊鎵ц鏂规硶
+     */
+    public static void ignore(Runnable handle) {
+        enableIgnore();
+        try {
+            handle.run();
+        } finally {
+            disableIgnore();
+        }
+    }
+
+    /**
+     * 鍦ㄥ拷鐣ユ暟鎹潈闄愪腑鎵ц
+     *
+     * @param handle 澶勭悊鎵ц鏂规硶
+     */
+    public static <T> T ignore(Supplier<T> handle) {
+        enableIgnore();
+        try {
+            return handle.get();
+        } finally {
+            disableIgnore();
+        }
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java
new file mode 100644
index 0000000..0ab0c11
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java
@@ -0,0 +1,129 @@
+package org.dromara.common.mybatis.interceptor;
+
+import cn.hutool.core.collection.ConcurrentHashSet;
+import cn.hutool.core.util.ArrayUtil;
+import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
+import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
+import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
+import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
+import org.dromara.common.mybatis.annotation.DataColumn;
+import org.dromara.common.mybatis.handler.PlusDataPermissionHandler;
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.statement.delete.Delete;
+import net.sf.jsqlparser.statement.select.PlainSelect;
+import net.sf.jsqlparser.statement.select.Select;
+import net.sf.jsqlparser.statement.select.SelectBody;
+import net.sf.jsqlparser.statement.select.SetOperationList;
+import net.sf.jsqlparser.statement.update.Update;
+import org.apache.ibatis.executor.Executor;
+import org.apache.ibatis.executor.statement.StatementHandler;
+import org.apache.ibatis.mapping.BoundSql;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.mapping.SqlCommandType;
+import org.apache.ibatis.session.ResultHandler;
+import org.apache.ibatis.session.RowBounds;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 鏁版嵁鏉冮檺鎷︽埅鍣�
+ *
+ * @author Lion Li
+ * @version 3.5.0
+ */
+public class PlusDataPermissionInterceptor extends JsqlParserSupport implements InnerInterceptor {
+
+    private final PlusDataPermissionHandler dataPermissionHandler = new PlusDataPermissionHandler();
+    /**
+     * 鏃犳晥娉ㄨВ鏂规硶缂撳瓨鐢ㄤ簬蹇�熻繑鍥�
+     */
+    private final Set<String> invalidCacheSet = new ConcurrentHashSet<>();
+
+    @Override
+    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
+        // 妫�鏌ュ拷鐣ユ敞瑙�
+        if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
+            return;
+        }
+        // 妫�鏌ユ槸鍚︽棤鏁� 鏃犳暟鎹潈闄愭敞瑙�
+        if (invalidCacheSet.contains(ms.getId())) {
+            return;
+        }
+        DataColumn[] dataColumns = dataPermissionHandler.findAnnotation(ms.getId());
+        if (ArrayUtil.isEmpty(dataColumns)) {
+            invalidCacheSet.add(ms.getId());
+            return;
+        }
+        // 瑙f瀽 sql 鍒嗛厤瀵瑰簲鏂规硶
+        PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
+        mpBs.sql(parserSingle(mpBs.sql(), ms.getId()));
+    }
+
+    @Override
+    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
+        PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
+        MappedStatement ms = mpSh.mappedStatement();
+        SqlCommandType sct = ms.getSqlCommandType();
+        if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {
+            if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
+                return;
+            }
+            // 妫�鏌ユ槸鍚︽棤鏁� 鏃犳暟鎹潈闄愭敞瑙�
+            if (invalidCacheSet.contains(ms.getId())) {
+                return;
+            }
+            DataColumn[] dataColumns = dataPermissionHandler.findAnnotation(ms.getId());
+            if (ArrayUtil.isEmpty(dataColumns)) {
+                invalidCacheSet.add(ms.getId());
+                return;
+            }
+            PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
+            mpBs.sql(parserMulti(mpBs.sql(), ms.getId()));
+        }
+    }
+
+    @Override
+    protected void processSelect(Select select, int index, String sql, Object obj) {
+        SelectBody selectBody = select.getSelectBody();
+        if (selectBody instanceof PlainSelect plainSelect) {
+            this.setWhere(plainSelect, (String) obj);
+        } else if (selectBody instanceof SetOperationList setOperationList) {
+            List<SelectBody> selectBodyList = setOperationList.getSelects();
+            selectBodyList.forEach(s -> this.setWhere((PlainSelect) s, (String) obj));
+        }
+    }
+
+    @Override
+    protected void processUpdate(Update update, int index, String sql, Object obj) {
+        Expression sqlSegment = dataPermissionHandler.getSqlSegment(update.getWhere(), (String) obj, false);
+        if (null != sqlSegment) {
+            update.setWhere(sqlSegment);
+        }
+    }
+
+    @Override
+    protected void processDelete(Delete delete, int index, String sql, Object obj) {
+        Expression sqlSegment = dataPermissionHandler.getSqlSegment(delete.getWhere(), (String) obj, false);
+        if (null != sqlSegment) {
+            delete.setWhere(sqlSegment);
+        }
+    }
+
+    /**
+     * 璁剧疆 where 鏉′欢
+     *
+     * @param plainSelect       鏌ヨ瀵硅薄
+     * @param mappedStatementId 鎵ц鏂规硶id
+     */
+    protected void setWhere(PlainSelect plainSelect, String mappedStatementId) {
+        Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), mappedStatementId, true);
+        if (null != sqlSegment) {
+            plainSelect.setWhere(sqlSegment);
+        }
+    }
+
+}
+
diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/service/SysDataScopeService.java b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/service/SysDataScopeService.java
new file mode 100644
index 0000000..0c187f6
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/service/SysDataScopeService.java
@@ -0,0 +1,28 @@
+package org.dromara.common.mybatis.service;
+
+import org.dromara.system.api.RemoteDataScopeService;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.springframework.stereotype.Service;
+
+/**
+ * 鏁版嵁鏉冮檺 瀹炵幇
+ * <p>
+ * 娉ㄦ剰: 姝ervice鍐呬笉鍏佽璋冪敤鏍囨敞`鏁版嵁鏉冮檺`娉ㄨВ鐨勬柟娉�
+ * 渚嬪: deptMapper.selectList 姝� selectList 鏂规硶鏍囨敞浜哷鏁版嵁鏉冮檺`娉ㄨВ 浼氬嚭鐜板惊鐜В鏋愮殑闂
+ *
+ * @author Lion Li
+ */
+@Service("sdss")
+public class SysDataScopeService {
+
+    @DubboReference
+    private RemoteDataScopeService remoteDataScopeService;
+
+    public String getRoleCustom(Long roleId) {
+        return remoteDataScopeService.getRoleCustom(roleId);
+    }
+
+    public String getDeptAndChild(Long deptId) {
+        return remoteDataScopeService.getDeptAndChild(deptId);
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter b/ruoyi-common/ruoyi-common-mybatis/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter
new file mode 100644
index 0000000..6d8ff88
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mybatis/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter
@@ -0,0 +1 @@
+dubboDataPermissionFilter=org.dromara.common.mybatis.filter.DubboDataPermissionFilter
diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-mybatis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..3a20a02
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mybatis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1,3 @@
+org.dromara.common.mybatis.config.MybatisPlusConfiguration
+org.dromara.common.mybatis.handler.MybatisExceptionHandler
+org.dromara.common.mybatis.service.SysDataScopeService
diff --git a/ruoyi-common/ruoyi-common-mybatis/src/main/resources/common-mybatis.yml b/ruoyi-common/ruoyi-common-mybatis/src/main/resources/common-mybatis.yml
new file mode 100644
index 0000000..f5dc637
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-mybatis/src/main/resources/common-mybatis.yml
@@ -0,0 +1,33 @@
+# 鍐呯疆閰嶇疆 涓嶅厑璁镐慨鏀� 濡傞渶淇敼璇峰湪 nacos 涓婂啓鐩稿悓閰嶇疆瑕嗙洊
+# MyBatisPlus閰嶇疆
+# https://baomidou.com/config/
+mybatis-plus:
+  # 鍚姩鏃舵槸鍚︽鏌� MyBatis XML 鏂囦欢鐨勫瓨鍦紝榛樿涓嶆鏌�
+  checkConfigLocation: false
+  configuration:
+    # 鑷姩椹煎嘲鍛藉悕瑙勫垯锛坈amel case锛夋槧灏�
+    mapUnderscoreToCamelCase: true
+    # MyBatis 鑷姩鏄犲皠绛栫暐
+    # NONE锛氫笉鍚敤 PARTIAL锛氬彧瀵归潪宓屽 resultMap 鑷姩鏄犲皠 FULL锛氬鎵�鏈� resultMap 鑷姩鏄犲皠
+    autoMappingBehavior: FULL
+    # MyBatis 鑷姩鏄犲皠鏃舵湭鐭ュ垪鎴栨湭鐭ュ睘鎬у鐞嗙瓥
+    # NONE锛氫笉鍋氬鐞� WARNING锛氭墦鍗扮浉鍏宠鍛� FAILING锛氭姏鍑哄紓甯稿拰璇︾粏淇℃伅
+    autoMappingUnknownColumnBehavior: NONE
+    # 鏇磋缁嗙殑鏃ュ織杈撳嚭 浼氭湁鎬ц兘鎹熻�� org.apache.ibatis.logging.stdout.StdOutImpl
+    # 鍏抽棴鏃ュ織璁板綍 (鍙崟绾娇鐢� p6spy 鍒嗘瀽) org.apache.ibatis.logging.nologging.NoLoggingImpl
+    # 榛樿鏃ュ織杈撳嚭 org.apache.ibatis.logging.slf4j.Slf4jImpl
+    logImpl: org.apache.ibatis.logging.nologging.NoLoggingImpl
+  global-config:
+    # 鏄惁鎵撳嵃 Logo banner
+    banner: true
+    dbConfig:
+      # 涓婚敭绫诲瀷
+      # AUTO 鑷 NONE 绌� INPUT 鐢ㄦ埛杈撳叆 ASSIGN_ID 闆姳 ASSIGN_UUID 鍞竴 UUID
+      idType: ASSIGN_ID
+      # 閫昏緫宸插垹闄ゅ��(妗嗘灦琛ㄥ潎浣跨敤姝ゅ�� 绂佹闅忔剰淇敼)
+      logicDeleteValue: 2
+      # 閫昏緫鏈垹闄ゅ��
+      logicNotDeleteValue: 0
+      insertStrategy: NOT_NULL
+      updateStrategy: NOT_NULL
+      whereStrategy: NOT_NULL
diff --git a/ruoyi-common/ruoyi-common-oss/pom.xml b/ruoyi-common/ruoyi-common-oss/pom.xml
new file mode 100644
index 0000000..8e7afff
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-oss/pom.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-oss</artifactId>
+
+    <description>
+        ruoyi-common-oss oss鏈嶅姟
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-json</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-redis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.amazonaws</groupId>
+            <artifactId>aws-java-sdk-s3</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/constant/OssConstant.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/constant/OssConstant.java
new file mode 100644
index 0000000..9d8db93
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/constant/OssConstant.java
@@ -0,0 +1,40 @@
+package org.dromara.common.oss.constant;
+
+import org.dromara.common.core.constant.GlobalConstants;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 瀵硅薄瀛樺偍甯搁噺
+ *
+ * @author Lion Li
+ */
+public interface OssConstant {
+
+    /**
+     * 榛樿閰嶇疆KEY
+     */
+    String DEFAULT_CONFIG_KEY = GlobalConstants.GLOBAL_REDIS_KEY + "sys_oss:default_config";
+
+    /**
+     * 棰勮鍒楄〃璧勬簮寮�鍏矺ey
+     */
+    String PEREVIEW_LIST_RESOURCE_KEY = "sys.oss.previewListResource";
+
+    /**
+     * 绯荤粺鏁版嵁ids
+     */
+    List<Long> SYSTEM_DATA_IDS = Arrays.asList(1L, 2L, 3L, 4L);
+
+    /**
+     * 浜戞湇鍔″晢
+     */
+    String[] CLOUD_SERVICE = new String[] {"aliyun", "qcloud", "qiniu", "obs"};
+
+    /**
+     * https 鐘舵��
+     */
+    String IS_HTTPS = "Y";
+
+}
diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java
new file mode 100644
index 0000000..53e05c9
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java
@@ -0,0 +1,262 @@
+package org.dromara.common.oss.core;
+
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.IdUtil;
+import com.amazonaws.ClientConfiguration;
+import com.amazonaws.HttpMethod;
+import com.amazonaws.Protocol;
+import com.amazonaws.auth.AWSCredentials;
+import com.amazonaws.auth.AWSCredentialsProvider;
+import com.amazonaws.auth.AWSStaticCredentialsProvider;
+import com.amazonaws.auth.BasicAWSCredentials;
+import com.amazonaws.client.builder.AwsClientBuilder;
+import com.amazonaws.services.s3.AmazonS3;
+import com.amazonaws.services.s3.AmazonS3Client;
+import com.amazonaws.services.s3.AmazonS3ClientBuilder;
+import com.amazonaws.services.s3.model.*;
+import org.dromara.common.core.utils.DateUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.oss.constant.OssConstant;
+import org.dromara.common.oss.entity.UploadResult;
+import org.dromara.common.oss.enumd.AccessPolicyType;
+import org.dromara.common.oss.enumd.PolicyType;
+import org.dromara.common.oss.exception.OssException;
+import org.dromara.common.oss.properties.OssProperties;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Date;
+
+/**
+ * S3 瀛樺偍鍗忚 鎵�鏈夊吋瀹筍3鍗忚鐨勪簯鍘傚晢鍧囨敮鎸�
+ * 闃块噷浜� 鑵捐浜� 涓冪墰浜� minio
+ *
+ * @author Lion Li
+ */
+public class OssClient {
+
+    private final String configKey;
+
+    private final OssProperties properties;
+
+    private final AmazonS3 client;
+
+    public OssClient(String configKey, OssProperties ossProperties) {
+        this.configKey = configKey;
+        this.properties = ossProperties;
+        try {
+            AwsClientBuilder.EndpointConfiguration endpointConfig =
+                new AwsClientBuilder.EndpointConfiguration(properties.getEndpoint(), properties.getRegion());
+
+            AWSCredentials credentials = new BasicAWSCredentials(properties.getAccessKey(), properties.getSecretKey());
+            AWSCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(credentials);
+            ClientConfiguration clientConfig = new ClientConfiguration();
+            if (OssConstant.IS_HTTPS.equals(properties.getIsHttps())) {
+                clientConfig.setProtocol(Protocol.HTTPS);
+            } else {
+                clientConfig.setProtocol(Protocol.HTTP);
+            }
+            AmazonS3ClientBuilder build = AmazonS3Client.builder()
+                .withEndpointConfiguration(endpointConfig)
+                .withClientConfiguration(clientConfig)
+                .withCredentials(credentialsProvider)
+                .disableChunkedEncoding();
+            if (!StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE)) {
+                // minio 浣跨敤https闄愬埗浣跨敤鍩熷悕璁块棶 闇�瑕佹閰嶇疆 绔欑偣濉煙鍚�
+                build.enablePathStyleAccess();
+            }
+            this.client = build.build();
+
+            createBucket();
+        } catch (Exception e) {
+            if (e instanceof OssException) {
+                throw e;
+            }
+            throw new OssException("閰嶇疆閿欒! 璇锋鏌ョ郴缁熼厤缃�:[" + e.getMessage() + "]");
+        }
+    }
+
+    public void createBucket() {
+        try {
+            String bucketName = properties.getBucketName();
+            if (client.doesBucketExistV2(bucketName)) {
+                return;
+            }
+            CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
+            AccessPolicyType accessPolicy = getAccessPolicy();
+            createBucketRequest.setCannedAcl(accessPolicy.getAcl());
+            client.createBucket(createBucketRequest);
+            client.setBucketPolicy(bucketName, getPolicy(bucketName, accessPolicy.getPolicyType()));
+        } catch (Exception e) {
+            throw new OssException("鍒涘缓Bucket澶辫触, 璇锋牳瀵归厤缃俊鎭�:[" + e.getMessage() + "]");
+        }
+    }
+
+    public UploadResult upload(byte[] data, String path, String contentType) {
+        return upload(new ByteArrayInputStream(data), path, contentType);
+    }
+
+    public UploadResult upload(InputStream inputStream, String path, String contentType) {
+        if (!(inputStream instanceof ByteArrayInputStream)) {
+            inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream));
+        }
+        try {
+            ObjectMetadata metadata = new ObjectMetadata();
+            metadata.setContentType(contentType);
+            metadata.setContentLength(inputStream.available());
+            PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), path, inputStream, metadata);
+            // 璁剧疆涓婁紶瀵硅薄鐨� Acl 涓哄叕鍏辫
+            putObjectRequest.setCannedAcl(getAccessPolicy().getAcl());
+            client.putObject(putObjectRequest);
+        } catch (Exception e) {
+            throw new OssException("涓婁紶鏂囦欢澶辫触锛岃妫�鏌ラ厤缃俊鎭�:[" + e.getMessage() + "]");
+        }
+        return UploadResult.builder().url(getUrl() + "/" + path).filename(path).build();
+    }
+
+    public UploadResult upload(File file, String path) {
+        try {
+            PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), path, file);
+            // 璁剧疆涓婁紶瀵硅薄鐨� Acl 涓哄叕鍏辫
+            putObjectRequest.setCannedAcl(getAccessPolicy().getAcl());
+            client.putObject(putObjectRequest);
+        } catch (Exception e) {
+            throw new OssException("涓婁紶鏂囦欢澶辫触锛岃妫�鏌ラ厤缃俊鎭�:[" + e.getMessage() + "]");
+        }
+        return UploadResult.builder().url(getUrl() + "/" + path).filename(path).build();
+    }
+
+    public void delete(String path) {
+        path = path.replace(getUrl() + "/", "");
+        try {
+            client.deleteObject(properties.getBucketName(), path);
+        } catch (Exception e) {
+            throw new OssException("鍒犻櫎鏂囦欢澶辫触锛岃妫�鏌ラ厤缃俊鎭�:[" + e.getMessage() + "]");
+        }
+    }
+
+    public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) {
+        return upload(data, getPath(properties.getPrefix(), suffix), contentType);
+    }
+
+    public UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType) {
+        return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType);
+    }
+
+    public UploadResult uploadSuffix(File file, String suffix) {
+        return upload(file, getPath(properties.getPrefix(), suffix));
+    }
+
+    /**
+     * 鑾峰彇鏂囦欢鍏冩暟鎹�
+     *
+     * @param path 瀹屾暣鏂囦欢璺緞
+     */
+    public ObjectMetadata getObjectMetadata(String path) {
+        path = path.replace(getUrl() + "/", "");
+        S3Object object = client.getObject(properties.getBucketName(), path);
+        return object.getObjectMetadata();
+    }
+
+    public InputStream getObjectContent(String path) {
+        path = path.replace(getUrl() + "/", "");
+        S3Object object = client.getObject(properties.getBucketName(), path);
+        return object.getObjectContent();
+    }
+
+    public String getUrl() {
+        String domain = properties.getDomain();
+        String endpoint = properties.getEndpoint();
+        String header = OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? "https://" : "http://";
+        // 浜戞湇鍔″晢鐩存帴杩斿洖
+        if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) {
+            if (StringUtils.isNotBlank(domain)) {
+                return header + domain;
+            }
+            return header + properties.getBucketName() + "." + endpoint;
+        }
+        // minio 鍗曠嫭澶勭悊
+        if (StringUtils.isNotBlank(domain)) {
+            return header + domain + "/" + properties.getBucketName();
+        }
+        return header + endpoint + "/" + properties.getBucketName();
+    }
+
+    public String getPath(String prefix, String suffix) {
+        // 鐢熸垚uuid
+        String uuid = IdUtil.fastSimpleUUID();
+        // 鏂囦欢璺緞
+        String path = DateUtils.datePath() + "/" + uuid;
+        if (StringUtils.isNotBlank(prefix)) {
+            path = prefix + "/" + path;
+        }
+        return path + suffix;
+    }
+
+
+    public String getConfigKey() {
+        return configKey;
+    }
+
+    /**
+     * 鑾峰彇绉佹湁URL閾炬帴
+     *
+     * @param objectKey 瀵硅薄KEY
+     * @param second    鎺堟潈鏃堕棿
+     */
+    public String getPrivateUrl(String objectKey, Integer second) {
+        GeneratePresignedUrlRequest generatePresignedUrlRequest =
+            new GeneratePresignedUrlRequest(properties.getBucketName(), objectKey)
+                .withMethod(HttpMethod.GET)
+                .withExpiration(new Date(System.currentTimeMillis() + 1000L * second));
+        URL url = client.generatePresignedUrl(generatePresignedUrlRequest);
+        return url.toString();
+    }
+
+    /**
+     * 妫�鏌ラ厤缃槸鍚︾浉鍚�
+     */
+    public boolean checkPropertiesSame(OssProperties properties) {
+        return this.properties.equals(properties);
+    }
+
+    /**
+     * 鑾峰彇褰撳墠妗舵潈闄愮被鍨�
+     *
+     * @return 褰撳墠妗舵潈闄愮被鍨媍ode
+     */
+    public AccessPolicyType getAccessPolicy() {
+        return AccessPolicyType.getByType(properties.getAccessPolicy());
+    }
+
+    private static String getPolicy(String bucketName, PolicyType policyType) {
+        StringBuilder builder = new StringBuilder();
+        builder.append("{\n\"Statement\": [\n{\n\"Action\": [\n");
+        builder.append(switch (policyType) {
+            case WRITE -> "\"s3:GetBucketLocation\",\n\"s3:ListBucketMultipartUploads\"\n";
+            case READ_WRITE -> "\"s3:GetBucketLocation\",\n\"s3:ListBucket\",\n\"s3:ListBucketMultipartUploads\"\n";
+            default -> "\"s3:GetBucketLocation\"\n";
+        });
+        builder.append("],\n\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
+        builder.append(bucketName);
+        builder.append("\"\n},\n");
+        if (policyType == PolicyType.READ) {
+            builder.append("{\n\"Action\": [\n\"s3:ListBucket\"\n],\n\"Effect\": \"Deny\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
+            builder.append(bucketName);
+            builder.append("\"\n},\n");
+        }
+        builder.append("{\n\"Action\": ");
+        builder.append(switch (policyType) {
+            case WRITE -> "[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n";
+            case READ_WRITE -> "[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:GetObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n";
+            default -> "\"s3:GetObject\",\n";
+        });
+        builder.append("\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::");
+        builder.append(bucketName);
+        builder.append("/*\"\n}\n],\n\"Version\": \"2012-10-17\"\n}\n");
+        return builder.toString();
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/UploadResult.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/UploadResult.java
new file mode 100644
index 0000000..a6f57e5
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity/UploadResult.java
@@ -0,0 +1,24 @@
+package org.dromara.common.oss.entity;
+
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * 涓婁紶杩斿洖浣�
+ *
+ * @author Lion Li
+ */
+@Data
+@Builder
+public class UploadResult {
+
+    /**
+     * 鏂囦欢璺緞
+     */
+    private String url;
+
+    /**
+     * 鏂囦欢鍚�
+     */
+    private String filename;
+}
diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enumd/AccessPolicyType.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enumd/AccessPolicyType.java
new file mode 100644
index 0000000..9074d72
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enumd/AccessPolicyType.java
@@ -0,0 +1,55 @@
+package org.dromara.common.oss.enumd;
+
+import com.amazonaws.services.s3.model.CannedAccessControlList;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 妗惰闂瓥鐣ラ厤缃�
+ *
+ * @author 闄堣碀
+ */
+@Getter
+@AllArgsConstructor
+public enum AccessPolicyType {
+
+    /**
+     * private
+     */
+    PRIVATE("0", CannedAccessControlList.Private, PolicyType.WRITE),
+
+    /**
+     * public
+     */
+    PUBLIC("1", CannedAccessControlList.PublicRead, PolicyType.READ),
+
+    /**
+     * custom
+     */
+    CUSTOM("2",CannedAccessControlList.PublicRead, PolicyType.READ);
+
+    /**
+     * 妗� 鏉冮檺绫诲瀷
+     */
+    private final String type;
+
+    /**
+     * 鏂囦欢瀵硅薄 鏉冮檺绫诲瀷
+     */
+    private final CannedAccessControlList acl;
+
+    /**
+     * 妗剁瓥鐣ョ被鍨�
+     */
+    private final PolicyType policyType;
+
+    public static AccessPolicyType getByType(String type) {
+        for (AccessPolicyType value : values()) {
+            if (value.getType().equals(type)) {
+                return value;
+            }
+        }
+        throw new RuntimeException("'type' not found By " + type);
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enumd/PolicyType.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enumd/PolicyType.java
new file mode 100644
index 0000000..fe96341
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/enumd/PolicyType.java
@@ -0,0 +1,35 @@
+package org.dromara.common.oss.enumd;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * minio绛栫暐閰嶇疆
+ *
+ * @author Lion Li
+ */
+@Getter
+@AllArgsConstructor
+public enum PolicyType {
+
+    /**
+     * 鍙
+     */
+    READ("read-only"),
+
+    /**
+     * 鍙啓
+     */
+    WRITE("write-only"),
+
+    /**
+     * 璇诲啓
+     */
+    READ_WRITE("read-write");
+
+    /**
+     * 绫诲瀷
+     */
+    private final String type;
+
+}
diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/exception/OssException.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/exception/OssException.java
new file mode 100644
index 0000000..52e9623
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/exception/OssException.java
@@ -0,0 +1,19 @@
+package org.dromara.common.oss.exception;
+
+import java.io.Serial;
+
+/**
+ * OSS寮傚父绫�
+ *
+ * @author Lion Li
+ */
+public class OssException extends RuntimeException {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    public OssException(String msg) {
+        super(msg);
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/factory/OssFactory.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/factory/OssFactory.java
new file mode 100644
index 0000000..763b090
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/factory/OssFactory.java
@@ -0,0 +1,65 @@
+package org.dromara.common.oss.factory;
+
+import org.dromara.common.core.constant.CacheNames;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.oss.constant.OssConstant;
+import org.dromara.common.oss.core.OssClient;
+import org.dromara.common.oss.exception.OssException;
+import org.dromara.common.oss.properties.OssProperties;
+import org.dromara.common.redis.utils.CacheUtils;
+import org.dromara.common.redis.utils.RedisUtils;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 鏂囦欢涓婁紶Factory
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class OssFactory {
+
+    private static final Map<String, OssClient> CLIENT_CACHE = new ConcurrentHashMap<>();
+
+    /**
+     * 鑾峰彇榛樿瀹炰緥
+     */
+    public static OssClient instance() {
+        // 鑾峰彇redis 榛樿绫诲瀷
+        String configKey = RedisUtils.getCacheObject(OssConstant.DEFAULT_CONFIG_KEY);
+        if (StringUtils.isEmpty(configKey)) {
+            throw new OssException("鏂囦欢瀛樺偍鏈嶅姟绫诲瀷鏃犳硶鎵惧埌!");
+        }
+        return instance(configKey);
+    }
+
+    /**
+     * 鏍规嵁绫诲瀷鑾峰彇瀹炰緥
+     */
+    public static synchronized OssClient instance(String configKey) {
+        String json = CacheUtils.get(CacheNames.SYS_OSS_CONFIG, configKey);
+        if (json == null) {
+            throw new OssException("绯荤粺寮傚父, '" + configKey + "'閰嶇疆淇℃伅涓嶅瓨鍦�!");
+        }
+        OssProperties properties = JsonUtils.parseObject(json, OssProperties.class);
+        // 浣跨敤绉熸埛鏍囪瘑閬垮厤澶氫釜绉熸埛鐩稿悓key瀹炰緥瑕嗙洊
+        String key = properties.getTenantId() + ":" + configKey;
+        OssClient client = CLIENT_CACHE.get(key);
+        if (client == null) {
+            CLIENT_CACHE.put(key, new OssClient(configKey, properties));
+            log.info("鍒涘缓OSS瀹炰緥 key => {}", configKey);
+            return CLIENT_CACHE.get(key);
+        }
+        // 閰嶇疆涓嶇浉鍚屽垯閲嶆柊鏋勫缓
+        if (!client.checkPropertiesSame(properties)) {
+            CLIENT_CACHE.put(key, new OssClient(configKey, properties));
+            log.info("閲嶈浇OSS瀹炰緥 key => {}", configKey);
+            return CLIENT_CACHE.get(key);
+        }
+        return client;
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/properties/OssProperties.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/properties/OssProperties.java
new file mode 100644
index 0000000..cb37206
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/properties/OssProperties.java
@@ -0,0 +1,63 @@
+package org.dromara.common.oss.properties;
+
+import lombok.Data;
+
+/**
+ * OSS瀵硅薄瀛樺偍 閰嶇疆灞炴��
+ *
+ * @author Lion Li
+ */
+@Data
+public class OssProperties {
+
+    /**
+     * 绉熸埛id
+     */
+    private String tenantId;
+
+    /**
+     * 璁块棶绔欑偣
+     */
+    private String endpoint;
+
+    /**
+     * 鑷畾涔夊煙鍚�
+     */
+    private String domain;
+
+    /**
+     * 鍓嶇紑
+     */
+    private String prefix;
+
+    /**
+     * ACCESS_KEY
+     */
+    private String accessKey;
+
+    /**
+     * SECRET_KEY
+     */
+    private String secretKey;
+
+    /**
+     * 瀛樺偍绌洪棿鍚�
+     */
+    private String bucketName;
+
+    /**
+     * 瀛樺偍鍖哄煙
+     */
+    private String region;
+
+    /**
+     * 鏄惁https锛圷=鏄�,N=鍚︼級
+     */
+    private String isHttps;
+
+    /**
+     * 妗舵潈闄愮被鍨�(0private 1public 2custom)
+     */
+    private String accessPolicy;
+
+}
diff --git a/ruoyi-common/ruoyi-common-prometheus/pom.xml b/ruoyi-common/ruoyi-common-prometheus/pom.xml
new file mode 100644
index 0000000..319bed4
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-prometheus/pom.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-prometheus</artifactId>
+
+    <description>
+        ruoyi-common-prometheus prometheus鐩戞帶
+    </description>
+
+	<dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.micrometer</groupId>
+            <artifactId>micrometer-registry-prometheus</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/ruoyi-common/ruoyi-common-prometheus/src/main/java/org/dromara/common/prometheus/config/PrometheusConfiguration.java b/ruoyi-common/ruoyi-common-prometheus/src/main/java/org/dromara/common/prometheus/config/PrometheusConfiguration.java
new file mode 100644
index 0000000..c5cfbf5
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-prometheus/src/main/java/org/dromara/common/prometheus/config/PrometheusConfiguration.java
@@ -0,0 +1,22 @@
+package org.dromara.common.prometheus.config;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * prometheus 閰嶇疆
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration
+public class PrometheusConfiguration {
+
+    @Bean
+    public MeterRegistryCustomizer<MeterRegistry> configurer(@Value("${spring.application.name}") String applicationName) {
+        return (registry) -> registry.config().commonTags("application", applicationName);
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-prometheus/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-prometheus/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..d05b042
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-prometheus/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.prometheus.config.PrometheusConfiguration
diff --git a/ruoyi-common/ruoyi-common-ratelimiter/pom.xml b/ruoyi-common/ruoyi-common-ratelimiter/pom.xml
new file mode 100644
index 0000000..bbde940
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-ratelimiter/pom.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-ratelimiter</artifactId>
+
+    <description>
+        ruoyi-common-ratelimiter 闄愭祦鍔熻兘
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-redis</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/annotation/RateLimiter.java b/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/annotation/RateLimiter.java
new file mode 100644
index 0000000..de09752
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/annotation/RateLimiter.java
@@ -0,0 +1,41 @@
+package org.dromara.common.ratelimiter.annotation;
+
+import org.dromara.common.ratelimiter.enums.LimitType;
+
+import java.lang.annotation.*;
+
+/**
+ * 闄愭祦娉ㄨВ
+ *
+ * @author Lion Li
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RateLimiter {
+    /**
+     * 闄愭祦key,鏀寔浣跨敤Spring el琛ㄨ揪寮忔潵鍔ㄦ�佽幏鍙栨柟娉曚笂鐨勫弬鏁板��
+     * 鏍煎紡绫讳技浜�  #code.id #{#code}
+     */
+    String key() default "";
+
+    /**
+     * 闄愭祦鏃堕棿,鍗曚綅绉�
+     */
+    int time() default 60;
+
+    /**
+     * 闄愭祦娆℃暟
+     */
+    int count() default 100;
+
+    /**
+     * 闄愭祦绫诲瀷
+     */
+    LimitType limitType() default LimitType.DEFAULT;
+
+    /**
+     * 鎻愮ず娑堟伅 鏀寔鍥介檯鍖� 鏍煎紡涓� {code}
+     */
+    String message() default "{rate.limiter.message}";
+}
diff --git a/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/aspectj/RateLimiterAspect.java b/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/aspectj/RateLimiterAspect.java
new file mode 100644
index 0000000..8f3a5ca
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/aspectj/RateLimiterAspect.java
@@ -0,0 +1,127 @@
+package org.dromara.common.ratelimiter.aspectj;
+
+import cn.hutool.core.util.ArrayUtil;
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.MessageUtils;
+import org.dromara.common.core.utils.ServletUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.ratelimiter.annotation.RateLimiter;
+import org.dromara.common.ratelimiter.enums.LimitType;
+import org.dromara.common.redis.utils.RedisUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.redisson.api.RateType;
+import org.springframework.core.DefaultParameterNameDiscoverer;
+import org.springframework.core.ParameterNameDiscoverer;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.Expression;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.ParserContext;
+import org.springframework.expression.common.TemplateParserContext;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+
+import java.lang.reflect.Method;
+
+/**
+ * 闄愭祦澶勭悊
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@Aspect
+public class RateLimiterAspect {
+
+    /**
+     * 瀹氫箟spel琛ㄨ揪寮忚В鏋愬櫒
+     */
+    private final ExpressionParser parser = new SpelExpressionParser();
+    /**
+     * 瀹氫箟spel瑙f瀽妯$増
+     */
+    private final ParserContext parserContext = new TemplateParserContext();
+    /**
+     * 瀹氫箟spel涓婁笅鏂囧璞¤繘琛岃В鏋�
+     */
+    private final EvaluationContext context = new StandardEvaluationContext();
+    /**
+     * 鏂规硶鍙傛暟瑙f瀽鍣�
+     */
+    private final ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
+
+    @Before("@annotation(rateLimiter)")
+    public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable {
+        int time = rateLimiter.time();
+        int count = rateLimiter.count();
+        String combineKey = getCombineKey(rateLimiter, point);
+        try {
+            RateType rateType = RateType.OVERALL;
+            if (rateLimiter.limitType() == LimitType.CLUSTER) {
+                rateType = RateType.PER_CLIENT;
+            }
+            long number = RedisUtils.rateLimiter(combineKey, rateType, count, time);
+            if (number == -1) {
+                String message = rateLimiter.message();
+                if (StringUtils.startsWith(message, "{") && StringUtils.endsWith(message, "}")) {
+                    message = MessageUtils.message(StringUtils.substring(message, 1, message.length() - 1));
+                }
+                throw new ServiceException(message);
+            }
+            log.info("闄愬埗浠ょ墝 => {}, 鍓╀綑浠ょ墝 => {}, 缂撳瓨key => '{}'", count, number, combineKey);
+        } catch (Exception e) {
+            if (e instanceof ServiceException) {
+                throw e;
+            } else {
+                throw new RuntimeException("鏈嶅姟鍣ㄩ檺娴佸紓甯革紝璇风◢鍊欏啀璇�");
+            }
+        }
+    }
+
+    public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
+        String key = rateLimiter.key();
+        // 鑾峰彇鏂规硶(閫氳繃鏂规硶绛惧悕鏉ヨ幏鍙�)
+        MethodSignature signature = (MethodSignature) point.getSignature();
+        Method method = signature.getMethod();
+        Class<?> targetClass = method.getDeclaringClass();
+        // 鍒ゆ柇鏄惁鏄痵pel鏍煎紡
+        if (StringUtils.containsAny(key, "#")) {
+            // 鑾峰彇鍙傛暟鍊�
+            Object[] args = point.getArgs();
+            // 鑾峰彇鏂规硶涓婂弬鏁扮殑鍚嶇О
+            String[] parameterNames = pnd.getParameterNames(method);
+            if (ArrayUtil.isEmpty(parameterNames)) {
+                throw new ServiceException("闄愭祦key瑙f瀽寮傚父!璇疯仈绯荤鐞嗗憳!");
+            }
+            for (int i = 0; i < parameterNames.length; i++) {
+                context.setVariable(parameterNames[i], args[i]);
+            }
+            // 瑙f瀽杩斿洖缁檏ey
+            try {
+                Expression expression;
+                if (StringUtils.startsWith(key, parserContext.getExpressionPrefix())
+                    && StringUtils.endsWith(key, parserContext.getExpressionSuffix())) {
+                    expression = parser.parseExpression(key, parserContext);
+                } else {
+                    expression = parser.parseExpression(key);
+                }
+                key = expression.getValue(context, String.class) + ":";
+            } catch (Exception e) {
+                throw new ServiceException("闄愭祦key瑙f瀽寮傚父!璇疯仈绯荤鐞嗗憳!");
+            }
+        }
+        StringBuilder stringBuffer = new StringBuilder(GlobalConstants.RATE_LIMIT_KEY);
+        stringBuffer.append(ServletUtils.getRequest().getRequestURI()).append(":");
+        if (rateLimiter.limitType() == LimitType.IP) {
+            // 鑾峰彇璇锋眰ip
+            stringBuffer.append(ServletUtils.getClientIP()).append(":");
+        } else if (rateLimiter.limitType() == LimitType.CLUSTER) {
+            // 鑾峰彇瀹㈡埛绔疄渚媔d
+            stringBuffer.append(RedisUtils.getClient().getId()).append(":");
+        }
+        return stringBuffer.append(key).toString();
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/config/RateLimiterConfig.java b/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/config/RateLimiterConfig.java
new file mode 100644
index 0000000..4b7e5b7
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/config/RateLimiterConfig.java
@@ -0,0 +1,20 @@
+package org.dromara.common.ratelimiter.config;
+
+import org.dromara.common.ratelimiter.aspectj.RateLimiterAspect;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.redis.connection.RedisConfiguration;
+
+/**
+ * @author guangxin
+ * @date 2023/1/18
+ */
+@AutoConfiguration(after = RedisConfiguration.class)
+public class RateLimiterConfig {
+
+    @Bean
+    public RateLimiterAspect rateLimiterAspect() {
+        return new RateLimiterAspect();
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/enums/LimitType.java b/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/enums/LimitType.java
new file mode 100644
index 0000000..b7f059f
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-ratelimiter/src/main/java/org/dromara/common/ratelimiter/enums/LimitType.java
@@ -0,0 +1,24 @@
+package org.dromara.common.ratelimiter.enums;
+
+/**
+ * 闄愭祦绫诲瀷
+ *
+ * @author ruoyi
+ */
+
+public enum LimitType {
+    /**
+     * 榛樿绛栫暐鍏ㄥ眬闄愭祦
+     */
+    DEFAULT,
+
+    /**
+     * 鏍规嵁璇锋眰鑰匢P杩涜闄愭祦
+     */
+    IP,
+
+    /**
+     * 瀹炰緥闄愭祦(闆嗙兢澶氬悗绔疄渚�)
+     */
+    CLUSTER
+}
diff --git a/ruoyi-common/ruoyi-common-ratelimiter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-ratelimiter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..3b95432
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-ratelimiter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.ratelimiter.config.RateLimiterConfig
diff --git a/ruoyi-common/ruoyi-common-redis/pom.xml b/ruoyi-common/ruoyi-common-redis/pom.xml
new file mode 100644
index 0000000..5e5c277
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-redis/pom.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-redis</artifactId>
+
+    <description>
+        ruoyi-common-redis 缂撳瓨鏈嶅姟
+    </description>
+
+    <dependencies>
+        <!-- RuoYi Common Core-->
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-core</artifactId>
+        </dependency>
+
+        <!--redisson-->
+        <dependency>
+            <groupId>org.redisson</groupId>
+            <artifactId>redisson-spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/RedisConfiguration.java b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/RedisConfiguration.java
new file mode 100644
index 0000000..8264423
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/RedisConfiguration.java
@@ -0,0 +1,144 @@
+package org.dromara.common.redis.config;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.redis.config.properties.RedissonProperties;
+import org.dromara.common.redis.handler.KeyPrefixHandler;
+import org.dromara.common.redis.manager.PlusSpringCacheManager;
+import org.redisson.client.codec.StringCodec;
+import org.redisson.codec.CompositeCodec;
+import org.redisson.codec.TypedJsonJacksonCodec;
+import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * redis閰嶇疆
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@AutoConfiguration
+@EnableCaching
+@EnableConfigurationProperties(RedissonProperties.class)
+public class RedisConfiguration {
+
+    @Autowired
+    private RedissonProperties redissonProperties;
+
+    @Autowired
+    private ObjectMapper objectMapper;
+
+    @Bean
+    public RedissonAutoConfigurationCustomizer redissonCustomizer() {
+        return config -> {
+            ObjectMapper om = objectMapper.copy();
+            om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
+            // 鎸囧畾搴忓垪鍖栬緭鍏ョ殑绫诲瀷锛岀被蹇呴』鏄潪final淇グ鐨勩�傚簭鍒楀寲鏃跺皢瀵硅薄鍏ㄧ被鍚嶄竴璧蜂繚瀛樹笅鏉�
+            om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
+            TypedJsonJacksonCodec jsonCodec = new TypedJsonJacksonCodec(Object.class, om);
+            // 缁勫悎搴忓垪鍖� key 浣跨敤 String 鍐呭浣跨敤閫氱敤 json 鏍煎紡
+            CompositeCodec codec = new CompositeCodec(StringCodec.INSTANCE, jsonCodec, jsonCodec);
+            config.setThreads(redissonProperties.getThreads())
+                .setNettyThreads(redissonProperties.getNettyThreads())
+                // 缂撳瓨 Lua 鑴氭湰 鍑忓皯缃戠粶浼犺緭(redisson 澶ч儴鍒嗙殑鍔熻兘閮芥槸鍩轰簬 Lua 鑴氭湰瀹炵幇)
+                .setUseScriptCache(true)
+                .setCodec(codec);
+            RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
+            if (ObjectUtil.isNotNull(singleServerConfig)) {
+                // 浣跨敤鍗曟満妯″紡
+                config.useSingleServer()
+                    //璁剧疆redis key鍓嶇紑
+                    .setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix()))
+                    .setTimeout(singleServerConfig.getTimeout())
+                    .setClientName(singleServerConfig.getClientName())
+                    .setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
+                    .setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize())
+                    .setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize())
+                    .setConnectionPoolSize(singleServerConfig.getConnectionPoolSize());
+            }
+            // 闆嗙兢閰嶇疆鏂瑰紡 鍙傝�冧笅鏂规敞閲�
+            RedissonProperties.ClusterServersConfig clusterServersConfig = redissonProperties.getClusterServersConfig();
+            if (ObjectUtil.isNotNull(clusterServersConfig)) {
+                config.useClusterServers()
+                    //璁剧疆redis key鍓嶇紑
+                    .setNameMapper(new KeyPrefixHandler(redissonProperties.getKeyPrefix()))
+                    .setTimeout(clusterServersConfig.getTimeout())
+                    .setClientName(clusterServersConfig.getClientName())
+                    .setIdleConnectionTimeout(clusterServersConfig.getIdleConnectionTimeout())
+                    .setSubscriptionConnectionPoolSize(clusterServersConfig.getSubscriptionConnectionPoolSize())
+                    .setMasterConnectionMinimumIdleSize(clusterServersConfig.getMasterConnectionMinimumIdleSize())
+                    .setMasterConnectionPoolSize(clusterServersConfig.getMasterConnectionPoolSize())
+                    .setSlaveConnectionMinimumIdleSize(clusterServersConfig.getSlaveConnectionMinimumIdleSize())
+                    .setSlaveConnectionPoolSize(clusterServersConfig.getSlaveConnectionPoolSize())
+                    .setReadMode(clusterServersConfig.getReadMode())
+                    .setSubscriptionMode(clusterServersConfig.getSubscriptionMode());
+            }
+            log.info("鍒濆鍖� redis 閰嶇疆");
+        };
+    }
+
+    /**
+     * 鑷畾涔夌紦瀛樼鐞嗗櫒 鏁村悎spring-cache
+     */
+    @Bean
+    public CacheManager cacheManager() {
+        return new PlusSpringCacheManager();
+    }
+
+    /**
+     * redis闆嗙兢閰嶇疆 yml
+     *
+     * --- # redis 闆嗙兢閰嶇疆(鍗曟満涓庨泦缇ゅ彧鑳藉紑鍚竴涓彟涓�涓渶瑕佹敞閲婃帀)
+     * spring.data:
+     *   redis:
+     *     cluster:
+     *       nodes:
+     *         - 192.168.0.100:6379
+     *         - 192.168.0.101:6379
+     *         - 192.168.0.102:6379
+     *     # 瀵嗙爜
+     *     password:
+     *     # 杩炴帴瓒呮椂鏃堕棿
+     *     timeout: 10s
+     *     # 鏄惁寮�鍚痵sl
+     *     ssl.enabled: false
+     *
+     * redisson:
+     *   # 绾跨▼姹犳暟閲�
+     *   threads: 16
+     *   # Netty绾跨▼姹犳暟閲�
+     *   nettyThreads: 32
+     *   # 闆嗙兢閰嶇疆
+     *   clusterServersConfig:
+     *     # 瀹㈡埛绔悕绉�
+     *     clientName: ${ruoyi.name}
+     *     # master鏈�灏忕┖闂茶繛鎺ユ暟
+     *     masterConnectionMinimumIdleSize: 32
+     *     # master杩炴帴姹犲ぇ灏�
+     *     masterConnectionPoolSize: 64
+     *     # slave鏈�灏忕┖闂茶繛鎺ユ暟
+     *     slaveConnectionMinimumIdleSize: 32
+     *     # slave杩炴帴姹犲ぇ灏�
+     *     slaveConnectionPoolSize: 64
+     *     # 杩炴帴绌洪棽瓒呮椂锛屽崟浣嶏細姣
+     *     idleConnectionTimeout: 10000
+     *     # 鍛戒护绛夊緟瓒呮椂锛屽崟浣嶏細姣
+     *     timeout: 3000
+     *     # 鍙戝竷鍜岃闃呰繛鎺ユ睜澶у皬
+     *     subscriptionConnectionPoolSize: 50
+     *     # 璇诲彇妯″紡
+     *     readMode: "SLAVE"
+     *     # 璁㈤槄妯″紡
+     *     subscriptionMode: "MASTER"
+     */
+
+}
diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/properties/RedissonProperties.java b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/properties/RedissonProperties.java
new file mode 100644
index 0000000..ebec786
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/config/properties/RedissonProperties.java
@@ -0,0 +1,135 @@
+package org.dromara.common.redis.config.properties;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.redisson.config.ReadMode;
+import org.redisson.config.SubscriptionMode;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * Redisson 閰嶇疆灞炴��
+ *
+ * @author Lion Li
+ */
+@Data
+@ConfigurationProperties(prefix = "redisson")
+public class RedissonProperties {
+
+    /**
+     * redis缂撳瓨key鍓嶇紑
+     */
+    private String keyPrefix;
+
+    /**
+     * 绾跨▼姹犳暟閲�,榛樿鍊� = 褰撳墠澶勭悊鏍告暟閲� * 2
+     */
+    private int threads;
+
+    /**
+     * Netty绾跨▼姹犳暟閲�,榛樿鍊� = 褰撳墠澶勭悊鏍告暟閲� * 2
+     */
+    private int nettyThreads;
+
+    /**
+     * 鍗曟満鏈嶅姟閰嶇疆
+     */
+    private SingleServerConfig singleServerConfig;
+
+    /**
+     * 闆嗙兢鏈嶅姟閰嶇疆
+     */
+    private ClusterServersConfig clusterServersConfig;
+
+    @Data
+    @NoArgsConstructor
+    public static class SingleServerConfig {
+
+        /**
+         * 瀹㈡埛绔悕绉�
+         */
+        private String clientName;
+
+        /**
+         * 鏈�灏忕┖闂茶繛鎺ユ暟
+         */
+        private int connectionMinimumIdleSize;
+
+        /**
+         * 杩炴帴姹犲ぇ灏�
+         */
+        private int connectionPoolSize;
+
+        /**
+         * 杩炴帴绌洪棽瓒呮椂锛屽崟浣嶏細姣
+         */
+        private int idleConnectionTimeout;
+
+        /**
+         * 鍛戒护绛夊緟瓒呮椂锛屽崟浣嶏細姣
+         */
+        private int timeout;
+
+        /**
+         * 鍙戝竷鍜岃闃呰繛鎺ユ睜澶у皬
+         */
+        private int subscriptionConnectionPoolSize;
+
+    }
+
+    @Data
+    @NoArgsConstructor
+    public static class ClusterServersConfig {
+
+        /**
+         * 瀹㈡埛绔悕绉�
+         */
+        private String clientName;
+
+        /**
+         * master鏈�灏忕┖闂茶繛鎺ユ暟
+         */
+        private int masterConnectionMinimumIdleSize;
+
+        /**
+         * master杩炴帴姹犲ぇ灏�
+         */
+        private int masterConnectionPoolSize;
+
+        /**
+         * slave鏈�灏忕┖闂茶繛鎺ユ暟
+         */
+        private int slaveConnectionMinimumIdleSize;
+
+        /**
+         * slave杩炴帴姹犲ぇ灏�
+         */
+        private int slaveConnectionPoolSize;
+
+        /**
+         * 杩炴帴绌洪棽瓒呮椂锛屽崟浣嶏細姣
+         */
+        private int idleConnectionTimeout;
+
+        /**
+         * 鍛戒护绛夊緟瓒呮椂锛屽崟浣嶏細姣
+         */
+        private int timeout;
+
+        /**
+         * 鍙戝竷鍜岃闃呰繛鎺ユ睜澶у皬
+         */
+        private int subscriptionConnectionPoolSize;
+
+        /**
+         * 璇诲彇妯″紡
+         */
+        private ReadMode readMode;
+
+        /**
+         * 璁㈤槄妯″紡
+         */
+        private SubscriptionMode subscriptionMode;
+
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/handler/KeyPrefixHandler.java b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/handler/KeyPrefixHandler.java
new file mode 100644
index 0000000..3bf3e34
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/handler/KeyPrefixHandler.java
@@ -0,0 +1,50 @@
+package org.dromara.common.redis.handler;
+
+import org.dromara.common.core.utils.StringUtils;
+import org.redisson.api.NameMapper;
+
+/**
+ * redis缂撳瓨key鍓嶇紑澶勭悊
+ *
+ * @author ye
+ * @date 2022/7/14 17:44
+ * @since 4.3.0
+ */
+public class KeyPrefixHandler implements NameMapper {
+
+    private final String keyPrefix;
+
+    public KeyPrefixHandler(String keyPrefix) {
+        //鍓嶇紑涓虹┖ 鍒欒繑鍥炵┖鍓嶇紑
+        this.keyPrefix = StringUtils.isBlank(keyPrefix) ? "" : keyPrefix + ":";
+    }
+
+    /**
+     * 澧炲姞鍓嶇紑
+     */
+    @Override
+    public String map(String name) {
+        if (StringUtils.isBlank(name)) {
+            return null;
+        }
+        if (StringUtils.isNotBlank(keyPrefix) && !name.startsWith(keyPrefix)) {
+            return keyPrefix + name;
+        }
+        return name;
+    }
+
+    /**
+     * 鍘婚櫎鍓嶇紑
+     */
+    @Override
+    public String unmap(String name) {
+        if (StringUtils.isBlank(name)) {
+            return null;
+        }
+        if (StringUtils.isNotBlank(keyPrefix) && name.startsWith(keyPrefix)) {
+            return name.substring(keyPrefix.length());
+        }
+        return name;
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/manager/PlusSpringCacheManager.java b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/manager/PlusSpringCacheManager.java
new file mode 100644
index 0000000..8c84889
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/manager/PlusSpringCacheManager.java
@@ -0,0 +1,192 @@
+/**
+ * Copyright (c) 2013-2021 Nikita Koksharov
+ *
+ * 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 org.dromara.common.redis.manager;
+
+import org.dromara.common.redis.utils.RedisUtils;
+import org.redisson.api.RMap;
+import org.redisson.api.RMapCache;
+import org.redisson.spring.cache.CacheConfig;
+import org.redisson.spring.cache.RedissonCache;
+import org.springframework.boot.convert.DurationStyle;
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.transaction.TransactionAwareCacheDecorator;
+import org.springframework.util.StringUtils;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * A {@link CacheManager} implementation
+ * backed by Redisson instance.
+ * <p>
+ * 淇敼 RedissonSpringCacheManager 婧愮爜
+ * 閲嶅啓 cacheName 澶勭悊鏂规硶 鏀寔澶氬弬鏁�
+ *
+ * @author Nikita Koksharov
+ *
+ */
+@SuppressWarnings("unchecked")
+public class PlusSpringCacheManager implements CacheManager {
+
+    private boolean dynamic = true;
+
+    private boolean allowNullValues = true;
+
+    private boolean transactionAware = true;
+
+    Map<String, CacheConfig> configMap = new ConcurrentHashMap<>();
+    ConcurrentMap<String, Cache> instanceMap = new ConcurrentHashMap<>();
+
+    /**
+     * Creates CacheManager supplied by Redisson instance
+     */
+    public PlusSpringCacheManager() {
+    }
+
+
+    /**
+     * Defines possibility of storing {@code null} values.
+     * <p>
+     * Default is <code>true</code>
+     *
+     * @param allowNullValues stores if <code>true</code>
+     */
+    public void setAllowNullValues(boolean allowNullValues) {
+        this.allowNullValues = allowNullValues;
+    }
+
+    /**
+     * Defines if cache aware of Spring-managed transactions.
+     * If {@code true} put/evict operations are executed only for successful transaction in after-commit phase.
+     * <p>
+     * Default is <code>false</code>
+     *
+     * @param transactionAware cache is transaction aware if <code>true</code>
+     */
+    public void setTransactionAware(boolean transactionAware) {
+        this.transactionAware = transactionAware;
+    }
+
+    /**
+     * Defines 'fixed' cache names.
+     * A new cache instance will not be created in dynamic for non-defined names.
+     * <p>
+     * `null` parameter setups dynamic mode
+     *
+     * @param names of caches
+     */
+    public void setCacheNames(Collection<String> names) {
+        if (names != null) {
+            for (String name : names) {
+                getCache(name);
+            }
+            dynamic = false;
+        } else {
+            dynamic = true;
+        }
+    }
+
+    /**
+     * Set cache config mapped by cache name
+     *
+     * @param config object
+     */
+    public void setConfig(Map<String, ? extends CacheConfig> config) {
+        this.configMap = (Map<String, CacheConfig>) config;
+    }
+
+    protected CacheConfig createDefaultConfig() {
+        return new CacheConfig();
+    }
+
+    @Override
+    public Cache getCache(String name) {
+        // 閲嶅啓 cacheName 鏀寔澶氬弬鏁�
+        String[] array = StringUtils.delimitedListToStringArray(name, "#");
+        name = array[0];
+
+        Cache cache = instanceMap.get(name);
+        if (cache != null) {
+            return cache;
+        }
+        if (!dynamic) {
+            return cache;
+        }
+
+        CacheConfig config = configMap.get(name);
+        if (config == null) {
+            config = createDefaultConfig();
+            configMap.put(name, config);
+        }
+
+        if (array.length > 1) {
+            config.setTTL(DurationStyle.detectAndParse(array[1]).toMillis());
+        }
+        if (array.length > 2) {
+            config.setMaxIdleTime(DurationStyle.detectAndParse(array[2]).toMillis());
+        }
+        if (array.length > 3) {
+            config.setMaxSize(Integer.parseInt(array[3]));
+        }
+
+        if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) {
+            return createMap(name, config);
+        }
+
+        return createMapCache(name, config);
+    }
+
+    private Cache createMap(String name, CacheConfig config) {
+        RMap<Object, Object> map = RedisUtils.getClient().getMap(name);
+
+        Cache cache = new RedissonCache(map, allowNullValues);
+        if (transactionAware) {
+            cache = new TransactionAwareCacheDecorator(cache);
+        }
+        Cache oldCache = instanceMap.putIfAbsent(name, cache);
+        if (oldCache != null) {
+            cache = oldCache;
+        }
+        return cache;
+    }
+
+    private Cache createMapCache(String name, CacheConfig config) {
+        RMapCache<Object, Object> map = RedisUtils.getClient().getMapCache(name);
+
+        Cache cache = new RedissonCache(map, config, allowNullValues);
+        if (transactionAware) {
+            cache = new TransactionAwareCacheDecorator(cache);
+        }
+        Cache oldCache = instanceMap.putIfAbsent(name, cache);
+        if (oldCache != null) {
+            cache = oldCache;
+        } else {
+            map.setMaxSize(config.getMaxSize());
+        }
+        return cache;
+    }
+
+    @Override
+    public Collection<String> getCacheNames() {
+        return Collections.unmodifiableSet(configMap.keySet());
+    }
+
+
+}
diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/CacheUtils.java b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/CacheUtils.java
new file mode 100644
index 0000000..42a88d6
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/CacheUtils.java
@@ -0,0 +1,75 @@
+package org.dromara.common.redis.utils;
+
+import org.dromara.common.core.utils.SpringUtils;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.redisson.api.RMap;
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+
+import java.util.Set;
+
+/**
+ * 缂撳瓨鎿嶄綔宸ュ叿绫� {@link }
+ *
+ * @author Michelle.Chung
+ * @date 2022/8/13
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@SuppressWarnings(value = {"unchecked"})
+public class CacheUtils {
+
+    private static final CacheManager CACHE_MANAGER = SpringUtils.getBean(CacheManager.class);
+
+    /**
+     * 鑾峰彇缂撳瓨缁勫唴鎵�鏈夌殑KEY
+     *
+     * @param cacheNames 缂撳瓨缁勫悕绉�
+     */
+    public static Set<Object> keys(String cacheNames) {
+        RMap<Object, Object> rmap = (RMap<Object, Object>) CACHE_MANAGER.getCache(cacheNames).getNativeCache();
+        return rmap.keySet();
+    }
+
+    /**
+     * 鑾峰彇缂撳瓨鍊�
+     *
+     * @param cacheNames 缂撳瓨缁勫悕绉�
+     * @param key        缂撳瓨key
+     */
+    public static <T> T get(String cacheNames, Object key) {
+        Cache.ValueWrapper wrapper = CACHE_MANAGER.getCache(cacheNames).get(key);
+        return wrapper != null ? (T) wrapper.get() : null;
+    }
+
+    /**
+     * 淇濆瓨缂撳瓨鍊�
+     *
+     * @param cacheNames 缂撳瓨缁勫悕绉�
+     * @param key        缂撳瓨key
+     * @param value      缂撳瓨鍊�
+     */
+    public static void put(String cacheNames, Object key, Object value) {
+        CACHE_MANAGER.getCache(cacheNames).put(key, value);
+    }
+
+    /**
+     * 鍒犻櫎缂撳瓨鍊�
+     *
+     * @param cacheNames 缂撳瓨缁勫悕绉�
+     * @param key        缂撳瓨key
+     */
+    public static void evict(String cacheNames, Object key) {
+        CACHE_MANAGER.getCache(cacheNames).evict(key);
+    }
+
+    /**
+     * 娓呯┖缂撳瓨鍊�
+     *
+     * @param cacheNames 缂撳瓨缁勫悕绉�
+     */
+    public static void clear(String cacheNames) {
+        CACHE_MANAGER.getCache(cacheNames).clear();
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/RedisUtils.java b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/RedisUtils.java
new file mode 100644
index 0000000..ce32055
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-redis/src/main/java/org/dromara/common/redis/utils/RedisUtils.java
@@ -0,0 +1,538 @@
+package org.dromara.common.redis.utils;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.utils.SpringUtils;
+import org.redisson.api.*;
+
+import java.time.Duration;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * redis 宸ュ叿绫�
+ *
+ * @author Lion Li
+ * @version 3.1.0 鏂板
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@SuppressWarnings(value = {"unchecked", "rawtypes"})
+public class RedisUtils {
+
+    private static final RedissonClient CLIENT = SpringUtils.getBean(RedissonClient.class);
+
+    /**
+     * 闄愭祦
+     *
+     * @param key          闄愭祦key
+     * @param rateType     闄愭祦绫诲瀷
+     * @param rate         閫熺巼
+     * @param rateInterval 閫熺巼闂撮殧
+     * @return -1 琛ㄧず澶辫触
+     */
+    public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval) {
+        RRateLimiter rateLimiter = CLIENT.getRateLimiter(key);
+        rateLimiter.trySetRate(rateType, rate, rateInterval, RateIntervalUnit.SECONDS);
+        if (rateLimiter.tryAcquire()) {
+            return rateLimiter.availablePermits();
+        } else {
+            return -1L;
+        }
+    }
+
+    /**
+     * 鑾峰彇瀹㈡埛绔疄渚�
+     */
+    public static RedissonClient getClient() {
+        return CLIENT;
+    }
+
+    /**
+     * 鍙戝竷閫氶亾娑堟伅
+     *
+     * @param channelKey 閫氶亾key
+     * @param msg        鍙戦�佹暟鎹�
+     * @param consumer   鑷畾涔夊鐞�
+     */
+    public static <T> void publish(String channelKey, T msg, Consumer<T> consumer) {
+        RTopic topic = CLIENT.getTopic(channelKey);
+        topic.publish(msg);
+        consumer.accept(msg);
+    }
+
+    public static <T> void publish(String channelKey, T msg) {
+        RTopic topic = CLIENT.getTopic(channelKey);
+        topic.publish(msg);
+    }
+
+    /**
+     * 璁㈤槄閫氶亾鎺ユ敹娑堟伅
+     *
+     * @param channelKey 閫氶亾key
+     * @param clazz      娑堟伅绫诲瀷
+     * @param consumer   鑷畾涔夊鐞�
+     */
+    public static <T> void subscribe(String channelKey, Class<T> clazz, Consumer<T> consumer) {
+        RTopic topic = CLIENT.getTopic(channelKey);
+        topic.addListener(clazz, (channel, msg) -> consumer.accept(msg));
+    }
+
+    /**
+     * 缂撳瓨鍩烘湰鐨勫璞★紝Integer銆丼tring銆佸疄浣撶被绛�
+     *
+     * @param key   缂撳瓨鐨勯敭鍊�
+     * @param value 缂撳瓨鐨勫��
+     */
+    public static <T> void setCacheObject(final String key, final T value) {
+        setCacheObject(key, value, false);
+    }
+
+    /**
+     * 缂撳瓨鍩烘湰鐨勫璞★紝淇濈暀褰撳墠瀵硅薄 TTL 鏈夋晥鏈�
+     *
+     * @param key       缂撳瓨鐨勯敭鍊�
+     * @param value     缂撳瓨鐨勫��
+     * @param isSaveTtl 鏄惁淇濈暀TTL鏈夋晥鏈�(渚嬪: set涔嬪墠ttl鍓╀綑90 set涔嬪悗杩樻槸涓�90)
+     * @since Redis 6.X 浠ヤ笂浣跨敤 setAndKeepTTL 鍏煎 5.X 鏂规
+     */
+    public static <T> void setCacheObject(final String key, final T value, final boolean isSaveTtl) {
+        RBucket<T> bucket = CLIENT.getBucket(key);
+        if (isSaveTtl) {
+            try {
+                bucket.setAndKeepTTL(value);
+            } catch (Exception e) {
+                long timeToLive = bucket.remainTimeToLive();
+                setCacheObject(key, value, Duration.ofMillis(timeToLive));
+            }
+        } else {
+            bucket.set(value);
+        }
+    }
+
+    /**
+     * 缂撳瓨鍩烘湰鐨勫璞★紝Integer銆丼tring銆佸疄浣撶被绛�
+     *
+     * @param key      缂撳瓨鐨勯敭鍊�
+     * @param value    缂撳瓨鐨勫��
+     * @param duration 鏃堕棿
+     */
+    public static <T> void setCacheObject(final String key, final T value, final Duration duration) {
+        RBatch batch = CLIENT.createBatch();
+        RBucketAsync<T> bucket = batch.getBucket(key);
+        bucket.setAsync(value);
+        bucket.expireAsync(duration);
+        batch.execute();
+    }
+
+    /**
+     * 濡傛灉涓嶅瓨鍦ㄥ垯璁剧疆 骞惰繑鍥� true 濡傛灉瀛樺湪鍒欒繑鍥� false
+     *
+     * @param key   缂撳瓨鐨勯敭鍊�
+     * @param value 缂撳瓨鐨勫��
+     * @return set鎴愬姛鎴栧け璐�
+     */
+    public static <T> boolean setObjectIfAbsent(final String key, final T value, final Duration duration) {
+        RBucket<T> bucket = CLIENT.getBucket(key);
+        return bucket.setIfAbsent(value, duration);
+    }
+
+    /**
+     * 濡傛灉瀛樺湪鍒欒缃� 骞惰繑鍥� true 濡傛灉瀛樺湪鍒欒繑鍥� false
+     *
+     * @param key   缂撳瓨鐨勯敭鍊�
+     * @param value 缂撳瓨鐨勫��
+     * @return set鎴愬姛鎴栧け璐�
+     */
+    public static <T> boolean setObjectIfExists(final String key, final T value, final Duration duration) {
+        RBucket<T> bucket = CLIENT.getBucket(key);
+        return bucket.setIfExists(value, duration);
+    }
+
+    /**
+     * 娉ㄥ唽瀵硅薄鐩戝惉鍣�
+     * <p>
+     * key 鐩戝惉鍣ㄩ渶寮�鍚� `notify-keyspace-events` 绛� redis 鐩稿叧閰嶇疆
+     *
+     * @param key      缂撳瓨鐨勯敭鍊�
+     * @param listener 鐩戝惉鍣ㄩ厤缃�
+     */
+    public static <T> void addObjectListener(final String key, final ObjectListener listener) {
+        RBucket<T> result = CLIENT.getBucket(key);
+        result.addListener(listener);
+    }
+
+    /**
+     * 璁剧疆鏈夋晥鏃堕棿
+     *
+     * @param key     Redis閿�
+     * @param timeout 瓒呮椂鏃堕棿
+     * @return true=璁剧疆鎴愬姛锛沠alse=璁剧疆澶辫触
+     */
+    public static boolean expire(final String key, final long timeout) {
+        return expire(key, Duration.ofSeconds(timeout));
+    }
+
+    /**
+     * 璁剧疆鏈夋晥鏃堕棿
+     *
+     * @param key      Redis閿�
+     * @param duration 瓒呮椂鏃堕棿
+     * @return true=璁剧疆鎴愬姛锛沠alse=璁剧疆澶辫触
+     */
+    public static boolean expire(final String key, final Duration duration) {
+        RBucket rBucket = CLIENT.getBucket(key);
+        return rBucket.expire(duration);
+    }
+
+    /**
+     * 鑾峰緱缂撳瓨鐨勫熀鏈璞°��
+     *
+     * @param key 缂撳瓨閿��
+     * @return 缂撳瓨閿�煎搴旂殑鏁版嵁
+     */
+    public static <T> T getCacheObject(final String key) {
+        RBucket<T> rBucket = CLIENT.getBucket(key);
+        return rBucket.get();
+    }
+
+    /**
+     * 鑾峰緱key鍓╀綑瀛樻椿鏃堕棿
+     *
+     * @param key 缂撳瓨閿��
+     * @return 鍓╀綑瀛樻椿鏃堕棿
+     */
+    public static <T> long getTimeToLive(final String key) {
+        RBucket<T> rBucket = CLIENT.getBucket(key);
+        return rBucket.remainTimeToLive();
+    }
+
+    /**
+     * 鍒犻櫎鍗曚釜瀵硅薄
+     *
+     * @param key 缂撳瓨鐨勯敭鍊�
+     */
+    public static boolean deleteObject(final String key) {
+        return CLIENT.getBucket(key).delete();
+    }
+
+    /**
+     * 鍒犻櫎闆嗗悎瀵硅薄
+     *
+     * @param collection 澶氫釜瀵硅薄
+     */
+    public static void deleteObject(final Collection collection) {
+        RBatch batch = CLIENT.createBatch();
+        collection.forEach(t -> {
+            batch.getBucket(t.toString()).deleteAsync();
+        });
+        batch.execute();
+    }
+
+    /**
+     * 妫�鏌ョ紦瀛樺璞℃槸鍚﹀瓨鍦�
+     *
+     * @param key 缂撳瓨鐨勯敭鍊�
+     */
+    public static boolean isExistsObject(final String key) {
+        return CLIENT.getBucket(key).isExists();
+    }
+
+    /**
+     * 缂撳瓨List鏁版嵁
+     *
+     * @param key      缂撳瓨鐨勯敭鍊�
+     * @param dataList 寰呯紦瀛樼殑List鏁版嵁
+     * @return 缂撳瓨鐨勫璞�
+     */
+    public static <T> boolean setCacheList(final String key, final List<T> dataList) {
+        RList<T> rList = CLIENT.getList(key);
+        return rList.addAll(dataList);
+    }
+
+    /**
+     * 杩藉姞缂撳瓨List鏁版嵁
+     *
+     * @param key  缂撳瓨鐨勯敭鍊�
+     * @param data 寰呯紦瀛樼殑鏁版嵁
+     * @return 缂撳瓨鐨勫璞�
+     */
+    public static <T> boolean addCacheList(final String key, final T data) {
+        RList<T> rList = CLIENT.getList(key);
+        return rList.add(data);
+    }
+
+    /**
+     * 娉ㄥ唽List鐩戝惉鍣�
+     * <p>
+     * key 鐩戝惉鍣ㄩ渶寮�鍚� `notify-keyspace-events` 绛� redis 鐩稿叧閰嶇疆
+     *
+     * @param key      缂撳瓨鐨勯敭鍊�
+     * @param listener 鐩戝惉鍣ㄩ厤缃�
+     */
+    public static <T> void addListListener(final String key, final ObjectListener listener) {
+        RList<T> rList = CLIENT.getList(key);
+        rList.addListener(listener);
+    }
+
+    /**
+     * 鑾峰緱缂撳瓨鐨刲ist瀵硅薄
+     *
+     * @param key 缂撳瓨鐨勯敭鍊�
+     * @return 缂撳瓨閿�煎搴旂殑鏁版嵁
+     */
+    public static <T> List<T> getCacheList(final String key) {
+        RList<T> rList = CLIENT.getList(key);
+        return rList.readAll();
+    }
+
+    /**
+     * 鑾峰緱缂撳瓨鐨刲ist瀵硅薄(鑼冨洿)
+     *
+     * @param key  缂撳瓨鐨勯敭鍊�
+     * @param form 璧峰涓嬫爣
+     * @param to   鎴涓嬫爣
+     * @return 缂撳瓨閿�煎搴旂殑鏁版嵁
+     */
+    public static <T> List<T> getCacheListRange(final String key, int form, int to) {
+        RList<T> rList = CLIENT.getList(key);
+        return rList.range(form, to);
+    }
+
+    /**
+     * 缂撳瓨Set
+     *
+     * @param key     缂撳瓨閿��
+     * @param dataSet 缂撳瓨鐨勬暟鎹�
+     * @return 缂撳瓨鏁版嵁鐨勫璞�
+     */
+    public static <T> boolean setCacheSet(final String key, final Set<T> dataSet) {
+        RSet<T> rSet = CLIENT.getSet(key);
+        return rSet.addAll(dataSet);
+    }
+
+    /**
+     * 杩藉姞缂撳瓨Set鏁版嵁
+     *
+     * @param key  缂撳瓨鐨勯敭鍊�
+     * @param data 寰呯紦瀛樼殑鏁版嵁
+     * @return 缂撳瓨鐨勫璞�
+     */
+    public static <T> boolean addCacheSet(final String key, final T data) {
+        RSet<T> rSet = CLIENT.getSet(key);
+        return rSet.add(data);
+    }
+
+    /**
+     * 娉ㄥ唽Set鐩戝惉鍣�
+     * <p>
+     * key 鐩戝惉鍣ㄩ渶寮�鍚� `notify-keyspace-events` 绛� redis 鐩稿叧閰嶇疆
+     *
+     * @param key      缂撳瓨鐨勯敭鍊�
+     * @param listener 鐩戝惉鍣ㄩ厤缃�
+     */
+    public static <T> void addSetListener(final String key, final ObjectListener listener) {
+        RSet<T> rSet = CLIENT.getSet(key);
+        rSet.addListener(listener);
+    }
+
+    /**
+     * 鑾峰緱缂撳瓨鐨剆et
+     *
+     * @param key 缂撳瓨鐨刱ey
+     * @return set瀵硅薄
+     */
+    public static <T> Set<T> getCacheSet(final String key) {
+        RSet<T> rSet = CLIENT.getSet(key);
+        return rSet.readAll();
+    }
+
+    /**
+     * 缂撳瓨Map
+     *
+     * @param key     缂撳瓨鐨勯敭鍊�
+     * @param dataMap 缂撳瓨鐨勬暟鎹�
+     */
+    public static <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
+        if (dataMap != null) {
+            RMap<String, T> rMap = CLIENT.getMap(key);
+            rMap.putAll(dataMap);
+        }
+    }
+
+    /**
+     * 娉ㄥ唽Map鐩戝惉鍣�
+     * <p>
+     * key 鐩戝惉鍣ㄩ渶寮�鍚� `notify-keyspace-events` 绛� redis 鐩稿叧閰嶇疆
+     *
+     * @param key      缂撳瓨鐨勯敭鍊�
+     * @param listener 鐩戝惉鍣ㄩ厤缃�
+     */
+    public static <T> void addMapListener(final String key, final ObjectListener listener) {
+        RMap<String, T> rMap = CLIENT.getMap(key);
+        rMap.addListener(listener);
+    }
+
+    /**
+     * 鑾峰緱缂撳瓨鐨凪ap
+     *
+     * @param key 缂撳瓨鐨勯敭鍊�
+     * @return map瀵硅薄
+     */
+    public static <T> Map<String, T> getCacheMap(final String key) {
+        RMap<String, T> rMap = CLIENT.getMap(key);
+        return rMap.getAll(rMap.keySet());
+    }
+
+    /**
+     * 鑾峰緱缂撳瓨Map鐨刱ey鍒楄〃
+     *
+     * @param key 缂撳瓨鐨勯敭鍊�
+     * @return key鍒楄〃
+     */
+    public static <T> Set<String> getCacheMapKeySet(final String key) {
+        RMap<String, T> rMap = CLIENT.getMap(key);
+        return rMap.keySet();
+    }
+
+    /**
+     * 寰�Hash涓瓨鍏ユ暟鎹�
+     *
+     * @param key   Redis閿�
+     * @param hKey  Hash閿�
+     * @param value 鍊�
+     */
+    public static <T> void setCacheMapValue(final String key, final String hKey, final T value) {
+        RMap<String, T> rMap = CLIENT.getMap(key);
+        rMap.put(hKey, value);
+    }
+
+    /**
+     * 鑾峰彇Hash涓殑鏁版嵁
+     *
+     * @param key  Redis閿�
+     * @param hKey Hash閿�
+     * @return Hash涓殑瀵硅薄
+     */
+    public static <T> T getCacheMapValue(final String key, final String hKey) {
+        RMap<String, T> rMap = CLIENT.getMap(key);
+        return rMap.get(hKey);
+    }
+
+    /**
+     * 鍒犻櫎Hash涓殑鏁版嵁
+     *
+     * @param key  Redis閿�
+     * @param hKey Hash閿�
+     * @return Hash涓殑瀵硅薄
+     */
+    public static <T> T delCacheMapValue(final String key, final String hKey) {
+        RMap<String, T> rMap = CLIENT.getMap(key);
+        return rMap.remove(hKey);
+    }
+
+    /**
+     * 鍒犻櫎Hash涓殑鏁版嵁
+     *
+     * @param key   Redis閿�
+     * @param hKeys Hash閿�
+     */
+    public static <T> void delMultiCacheMapValue(final String key, final Set<String> hKeys) {
+        RBatch batch = CLIENT.createBatch();
+        RMapAsync<String, T> rMap = batch.getMap(key);
+        for (String hKey : hKeys) {
+            rMap.removeAsync(hKey);
+        }
+        batch.execute();
+    }
+
+    /**
+     * 鑾峰彇澶氫釜Hash涓殑鏁版嵁
+     *
+     * @param key   Redis閿�
+     * @param hKeys Hash閿泦鍚�
+     * @return Hash瀵硅薄闆嗗悎
+     */
+    public static <K, V> Map<K, V> getMultiCacheMapValue(final String key, final Set<K> hKeys) {
+        RMap<K, V> rMap = CLIENT.getMap(key);
+        return rMap.getAll(hKeys);
+    }
+
+    /**
+     * 璁剧疆鍘熷瓙鍊�
+     *
+     * @param key   Redis閿�
+     * @param value 鍊�
+     */
+    public static void setAtomicValue(String key, long value) {
+        RAtomicLong atomic = CLIENT.getAtomicLong(key);
+        atomic.set(value);
+    }
+
+    /**
+     * 鑾峰彇鍘熷瓙鍊�
+     *
+     * @param key Redis閿�
+     * @return 褰撳墠鍊�
+     */
+    public static long getAtomicValue(String key) {
+        RAtomicLong atomic = CLIENT.getAtomicLong(key);
+        return atomic.get();
+    }
+
+    /**
+     * 閫掑鍘熷瓙鍊�
+     *
+     * @param key Redis閿�
+     * @return 褰撳墠鍊�
+     */
+    public static long incrAtomicValue(String key) {
+        RAtomicLong atomic = CLIENT.getAtomicLong(key);
+        return atomic.incrementAndGet();
+    }
+
+    /**
+     * 閫掑噺鍘熷瓙鍊�
+     *
+     * @param key Redis閿�
+     * @return 褰撳墠鍊�
+     */
+    public static long decrAtomicValue(String key) {
+        RAtomicLong atomic = CLIENT.getAtomicLong(key);
+        return atomic.decrementAndGet();
+    }
+
+    /**
+     * 鑾峰緱缂撳瓨鐨勫熀鏈璞″垪琛�
+     *
+     * @param pattern 瀛楃涓插墠缂�
+     * @return 瀵硅薄鍒楄〃
+     */
+    public static Collection<String> keys(final String pattern) {
+        Stream<String> stream = CLIENT.getKeys().getKeysStreamByPattern(pattern);
+        return stream.collect(Collectors.toList());
+    }
+
+    /**
+     * 鍒犻櫎缂撳瓨鐨勫熀鏈璞″垪琛�
+     *
+     * @param pattern 瀛楃涓插墠缂�
+     */
+    public static void deleteKeys(final String pattern) {
+        CLIENT.getKeys().deleteByPattern(pattern);
+    }
+
+    /**
+     * 妫�鏌edis涓槸鍚﹀瓨鍦╧ey
+     *
+     * @param key 閿�
+     */
+    public static Boolean hasKey(String key) {
+        RKeys rKeys = CLIENT.getKeys();
+        return rKeys.countExists(key) > 0;
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..934b585
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.redis.config.RedisConfiguration
diff --git a/ruoyi-common/ruoyi-common-satoken/pom.xml b/ruoyi-common/ruoyi-common-satoken/pom.xml
new file mode 100644
index 0000000..93d46eb
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-satoken/pom.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-satoken</artifactId>
+
+    <description>
+        ruoyi-common-satoken
+    </description>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-core</artifactId>
+        </dependency>
+
+        <!-- Sa-Token 鏁村悎 jwt -->
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-jwt</artifactId>
+            <version>${satoken.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>cn.hutool</groupId>
+                    <artifactId>hutool-all</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-jwt</artifactId>
+        </dependency>
+
+        <!-- RuoYi Api System -->
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-api-system</artifactId>
+        </dependency>
+
+        <!-- RuoYi Common Redis-->
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-redis</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>
diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/config/SaTokenConfiguration.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/config/SaTokenConfiguration.java
new file mode 100644
index 0000000..981cada
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/config/SaTokenConfiguration.java
@@ -0,0 +1,44 @@
+package org.dromara.common.satoken.config;
+
+import cn.dev33.satoken.dao.SaTokenDao;
+import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
+import cn.dev33.satoken.stp.StpInterface;
+import cn.dev33.satoken.stp.StpLogic;
+import org.dromara.common.core.factory.YmlPropertySourceFactory;
+import org.dromara.common.satoken.core.dao.PlusSaTokenDao;
+import org.dromara.common.satoken.core.service.SaPermissionImpl;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.PropertySource;
+
+/**
+ * Sa-Token 閰嶇疆
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration
+@PropertySource(value = "classpath:common-satoken.yml", factory = YmlPropertySourceFactory.class)
+public class SaTokenConfiguration {
+
+    @Bean
+    public StpLogic getStpLogicJwt() {
+        return new StpLogicJwtForSimple();
+    }
+
+    /**
+     * 鏉冮檺鎺ュ彛瀹炵幇(浣跨敤bean娉ㄥ叆鏂逛究鐢ㄦ埛鏇挎崲)
+     */
+    @Bean
+    public StpInterface stpInterface() {
+        return new SaPermissionImpl();
+    }
+
+    /**
+     * 鑷畾涔塪ao灞傚瓨鍌�
+     */
+    @Bean
+    public SaTokenDao saTokenDao() {
+        return new PlusSaTokenDao();
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java
new file mode 100644
index 0000000..d885962
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/dao/PlusSaTokenDao.java
@@ -0,0 +1,148 @@
+package org.dromara.common.satoken.core.dao;
+
+import cn.dev33.satoken.dao.SaTokenDao;
+import cn.dev33.satoken.util.SaFoxUtil;
+import org.dromara.common.redis.utils.RedisUtils;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Sa-Token鎸佷箙灞傛帴鍙�(浣跨敤妗嗘灦鑷甫RedisUtils瀹炵幇 鍗忚缁熶竴)
+ *
+ * @author Lion Li
+ */
+public class PlusSaTokenDao implements SaTokenDao {
+
+    /**
+     * 鑾峰彇Value锛屽鏃犺繑绌�
+     */
+    @Override
+    public String get(String key) {
+        return RedisUtils.getCacheObject(key);
+    }
+
+    /**
+     * 鍐欏叆Value锛屽苟璁惧畾瀛樻椿鏃堕棿 (鍗曚綅: 绉�)
+     */
+    @Override
+    public void set(String key, String value, long timeout) {
+        if (timeout == 0 || timeout <= NOT_VALUE_EXPIRE) {
+            return;
+        }
+        // 鍒ゆ柇鏄惁涓烘案涓嶈繃鏈�
+        if (timeout == NEVER_EXPIRE) {
+            RedisUtils.setCacheObject(key, value);
+        } else {
+            RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout));
+        }
+    }
+
+    /**
+     * 淇慨鏀规寚瀹歬ey-value閿�煎 (杩囨湡鏃堕棿涓嶅彉)
+     */
+    @Override
+    public void update(String key, String value) {
+        if (RedisUtils.hasKey(key)) {
+            RedisUtils.setCacheObject(key, value, true);
+        }
+    }
+
+    /**
+     * 鍒犻櫎Value
+     */
+    @Override
+    public void delete(String key) {
+        RedisUtils.deleteObject(key);
+    }
+
+    /**
+     * 鑾峰彇Value鐨勫墿浣欏瓨娲绘椂闂� (鍗曚綅: 绉�)
+     */
+    @Override
+    public long getTimeout(String key) {
+        long timeout = RedisUtils.getTimeToLive(key);
+        return timeout < 0 ? timeout : timeout / 1000;
+    }
+
+    /**
+     * 淇敼Value鐨勫墿浣欏瓨娲绘椂闂� (鍗曚綅: 绉�)
+     */
+    @Override
+    public void updateTimeout(String key, long timeout) {
+        RedisUtils.expire(key, Duration.ofSeconds(timeout));
+    }
+
+
+    /**
+     * 鑾峰彇Object锛屽鏃犺繑绌�
+     */
+    @Override
+    public Object getObject(String key) {
+        return RedisUtils.getCacheObject(key);
+    }
+
+    /**
+     * 鍐欏叆Object锛屽苟璁惧畾瀛樻椿鏃堕棿 (鍗曚綅: 绉�)
+     */
+    @Override
+    public void setObject(String key, Object object, long timeout) {
+        if (timeout == 0 || timeout <= NOT_VALUE_EXPIRE) {
+            return;
+        }
+        // 鍒ゆ柇鏄惁涓烘案涓嶈繃鏈�
+        if (timeout == NEVER_EXPIRE) {
+            RedisUtils.setCacheObject(key, object);
+        } else {
+            RedisUtils.setCacheObject(key, object, Duration.ofSeconds(timeout));
+        }
+    }
+
+    /**
+     * 鏇存柊Object (杩囨湡鏃堕棿涓嶅彉)
+     */
+    @Override
+    public void updateObject(String key, Object object) {
+        if (RedisUtils.hasKey(key)) {
+            RedisUtils.setCacheObject(key, object, true);
+        }
+    }
+
+    /**
+     * 鍒犻櫎Object
+     */
+    @Override
+    public void deleteObject(String key) {
+        RedisUtils.deleteObject(key);
+    }
+
+    /**
+     * 鑾峰彇Object鐨勫墿浣欏瓨娲绘椂闂� (鍗曚綅: 绉�)
+     */
+    @Override
+    public long getObjectTimeout(String key) {
+        long timeout = RedisUtils.getTimeToLive(key);
+        return timeout < 0 ? timeout : timeout / 1000;
+    }
+
+    /**
+     * 淇敼Object鐨勫墿浣欏瓨娲绘椂闂� (鍗曚綅: 绉�)
+     */
+    @Override
+    public void updateObjectTimeout(String key, long timeout) {
+        RedisUtils.expire(key, Duration.ofSeconds(timeout));
+    }
+
+
+    /**
+     * 鎼滅储鏁版嵁
+     */
+    @Override
+    public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
+        Collection<String> keys = RedisUtils.keys(prefix + "*" + keyword + "*");
+        List<String> list = new ArrayList<>(keys);
+        return SaFoxUtil.searchList(list, start, size, sortType);
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/service/SaPermissionImpl.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/service/SaPermissionImpl.java
new file mode 100644
index 0000000..257922b
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/core/service/SaPermissionImpl.java
@@ -0,0 +1,47 @@
+package org.dromara.common.satoken.core.service;
+
+import cn.dev33.satoken.stp.StpInterface;
+import org.dromara.common.core.enums.UserType;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.system.api.model.LoginUser;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * sa-token 鏉冮檺绠$悊瀹炵幇绫�
+ *
+ * @author Lion Li
+ */
+public class SaPermissionImpl implements StpInterface {
+
+    /**
+     * 鑾峰彇鑿滃崟鏉冮檺鍒楄〃
+     */
+    @Override
+    public List<String> getPermissionList(Object loginId, String loginType) {
+        LoginUser loginUser = LoginHelper.getLoginUser();
+        UserType userType = UserType.getUserType(loginUser.getUserType());
+        if (userType == UserType.SYS_USER) {
+            return new ArrayList<>(loginUser.getMenuPermission());
+        } else if (userType == UserType.APP_USER) {
+            // 鍏朵粬绔� 鑷鏍规嵁涓氬姟缂栧啓
+        }
+        return new ArrayList<>();
+    }
+
+    /**
+     * 鑾峰彇瑙掕壊鏉冮檺鍒楄〃
+     */
+    @Override
+    public List<String> getRoleList(Object loginId, String loginType) {
+        LoginUser loginUser = LoginHelper.getLoginUser();
+        UserType userType = UserType.getUserType(loginUser.getUserType());
+        if (userType == UserType.SYS_USER) {
+            return new ArrayList<>(loginUser.getRolePermission());
+        } else if (userType == UserType.APP_USER) {
+            // 鍏朵粬绔� 鑷鏍规嵁涓氬姟缂栧啓
+        }
+        return new ArrayList<>();
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java
new file mode 100644
index 0000000..7310f27
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java
@@ -0,0 +1,176 @@
+package org.dromara.common.satoken.utils;
+
+import cn.dev33.satoken.context.SaHolder;
+import cn.dev33.satoken.context.model.SaStorage;
+import cn.dev33.satoken.session.SaSession;
+import cn.dev33.satoken.stp.SaLoginModel;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.constant.TenantConstants;
+import org.dromara.common.core.constant.UserConstants;
+import org.dromara.common.core.enums.UserType;
+import org.dromara.system.api.model.LoginUser;
+
+import java.util.Set;
+import java.util.function.Supplier;
+
+/**
+ * 鐧诲綍閴存潈鍔╂墜
+ * <p>
+ * user_type 涓� 鐢ㄦ埛绫诲瀷 鍚屼竴涓敤鎴疯〃 鍙互鏈夊绉嶇敤鎴风被鍨� 渚嬪 pc,app
+ * deivce 涓� 璁惧绫诲瀷 鍚屼竴涓敤鎴风被鍨� 鍙互鏈� 澶氱璁惧绫诲瀷 渚嬪 web,ios
+ * 鍙互缁勬垚 鐢ㄦ埛绫诲瀷涓庤澶囩被鍨嬪瀵瑰鐨� 鏉冮檺鐏垫椿鎺у埗
+ * <p>
+ * 澶氱敤鎴蜂綋绯� 閽堝 澶氱鐢ㄦ埛绫诲瀷 浣嗘潈闄愭帶鍒朵笉涓�鑷�
+ * 鍙互缁勬垚 澶氱敤鎴风被鍨嬭〃涓庡璁惧绫诲瀷 鍒嗗埆鎺у埗鏉冮檺
+ *
+ * @author Lion Li
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class LoginHelper {
+
+    public static final String LOGIN_USER_KEY = "loginUser";
+    public static final String TENANT_KEY = "tenantId";
+    public static final String USER_KEY = "userId";
+    public static final String DEPT_KEY = "deptId";
+    public static final String CLIENT_KEY = "clientid";
+    public static final String TENANT_ADMIN_KEY = "isTenantAdmin";
+
+    /**
+     * 鐧诲綍绯荤粺 鍩轰簬 璁惧绫诲瀷
+     * 閽堝鐩稿悓鐢ㄦ埛浣撶郴涓嶅悓璁惧
+     *
+     * @param loginUser 鐧诲綍鐢ㄦ埛淇℃伅
+     * @param model     閰嶇疆鍙傛暟
+     */
+    public static void login(LoginUser loginUser, SaLoginModel model) {
+        SaStorage storage = SaHolder.getStorage();
+        storage.set(LOGIN_USER_KEY, loginUser);
+        storage.set(TENANT_KEY, loginUser.getTenantId());
+        storage.set(USER_KEY, loginUser.getUserId());
+        storage.set(DEPT_KEY, loginUser.getDeptId());
+        model = ObjectUtil.defaultIfNull(model, new SaLoginModel());
+        StpUtil.login(loginUser.getLoginId(),
+            model.setExtra(TENANT_KEY, loginUser.getTenantId())
+                .setExtra(USER_KEY, loginUser.getUserId())
+                .setExtra(DEPT_KEY, loginUser.getDeptId()));
+        SaSession tokenSession = StpUtil.getTokenSession();
+        tokenSession.updateTimeout(model.getTimeout());
+        tokenSession.set(LOGIN_USER_KEY, loginUser);
+    }
+
+    /**
+     * 鑾峰彇鐢ㄦ埛(澶氱骇缂撳瓨)
+     */
+    public static LoginUser getLoginUser() {
+        return (LoginUser) getStorageIfAbsentSet(LOGIN_USER_KEY, () -> {
+            SaSession session = StpUtil.getTokenSession();
+            if (ObjectUtil.isNull(session)) {
+                return null;
+            }
+            return session.get(LOGIN_USER_KEY);
+        });
+    }
+
+    /**
+     * 鑾峰彇鐢ㄦ埛鍩轰簬token
+     */
+    public static LoginUser getLoginUser(String token) {
+        SaSession session = StpUtil.getTokenSessionByToken(token);
+        if (ObjectUtil.isNull(session)) {
+            return null;
+        }
+        return (LoginUser) session.get(LOGIN_USER_KEY);
+    }
+
+    /**
+     * 鑾峰彇鐢ㄦ埛id
+     */
+    public static Long getUserId() {
+        return  Convert.toLong(getExtra(USER_KEY));
+    }
+
+    /**
+     * 鑾峰彇绉熸埛ID
+     */
+    public static String getTenantId() {
+        return Convert.toStr(getExtra(TENANT_KEY));
+    }
+
+    /**
+     * 鑾峰彇閮ㄩ棬ID
+     */
+    public static Long getDeptId() {
+        return Convert.toLong(getExtra(DEPT_KEY));
+    }
+
+    private static Object getExtra(String key) {
+        return getStorageIfAbsentSet(key, () -> StpUtil.getExtra(key));
+    }
+
+    /**
+     * 鑾峰彇鐢ㄦ埛璐︽埛
+     */
+    public static String getUsername() {
+        return getLoginUser().getUsername();
+    }
+
+    /**
+     * 鑾峰彇鐢ㄦ埛绫诲瀷
+     */
+    public static UserType getUserType() {
+        String loginId = StpUtil.getLoginIdAsString();
+        return UserType.getUserType(loginId);
+    }
+
+    /**
+     * 鏄惁涓鸿秴绾х鐞嗗憳
+     *
+     * @param userId 鐢ㄦ埛ID
+     * @return 缁撴灉
+     */
+    public static boolean isSuperAdmin(Long userId) {
+        return UserConstants.SUPER_ADMIN_ID.equals(userId);
+    }
+
+    public static boolean isSuperAdmin() {
+        return isSuperAdmin(getUserId());
+    }
+
+    /**
+     * 鏄惁涓鸿秴绾х鐞嗗憳
+     *
+     * @param rolePermission 瑙掕壊鏉冮檺鏍囪瘑缁�
+     * @return 缁撴灉
+     */
+    public static boolean isTenantAdmin(Set<String> rolePermission) {
+        return rolePermission.contains(TenantConstants.TENANT_ADMIN_ROLE_KEY);
+    }
+
+    public static boolean isTenantAdmin() {
+        Object value = getStorageIfAbsentSet(TENANT_ADMIN_KEY, () -> {
+            return isTenantAdmin(getLoginUser().getRolePermission());
+        });
+        return Convert.toBool(value);
+    }
+
+    public static boolean isLogin() {
+        return getLoginUser() != null;
+    }
+
+    public static Object getStorageIfAbsentSet(String key, Supplier<Object> handle) {
+        try {
+            Object obj = SaHolder.getStorage().get(key);
+            if (ObjectUtil.isNull(obj)) {
+                obj = handle.get();
+                SaHolder.getStorage().set(key, obj);
+            }
+            return obj;
+        } catch (Exception e) {
+            return null;
+        }
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-satoken/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..9103401
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-satoken/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.satoken.config.SaTokenConfiguration
diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/resources/common-satoken.yml b/ruoyi-common/ruoyi-common-satoken/src/main/resources/common-satoken.yml
new file mode 100644
index 0000000..95e1b41
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-satoken/src/main/resources/common-satoken.yml
@@ -0,0 +1,13 @@
+# 鍐呯疆閰嶇疆 涓嶅厑璁镐慨鏀� 濡傞渶淇敼璇峰湪 nacos 涓婂啓鐩稿悓閰嶇疆瑕嗙洊
+# Sa-Token閰嶇疆
+sa-token:
+  # 鍏佽鍔ㄦ�佽缃� token 鏈夋晥鏈�
+  dynamic-active-timeout: true
+  # 鍏佽浠� 璇锋眰鍙傛暟 璇诲彇 token
+  is-read-body: true
+  # 鍏佽浠� header 璇诲彇 token
+  is-read-header: true
+  # 鍏抽棴 cookie 閴存潈 浠庢牴婧愭潨缁� csrf 婕忔礊椋庨櫓
+  is-read-cookie: false
+  # token鍓嶇紑
+  token-prefix: "Bearer"
diff --git a/ruoyi-common/ruoyi-common-seata/pom.xml b/ruoyi-common/ruoyi-common-seata/pom.xml
new file mode 100644
index 0000000..b2b51f8
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-seata/pom.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-seata</artifactId>
+
+    <description>
+        ruoyi-common-seata 鍒嗗竷寮忎簨鍔�
+    </description>
+
+	<dependencies>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.dubbo.extensions</groupId>
+            <artifactId>dubbo-filter-seata</artifactId>
+            <version>1.0.1</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>io.seata</groupId>
+                    <artifactId>seata-core</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!-- SpringBoot Seata -->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.logging.log4j</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.dubbo.extensions</groupId>
+                    <artifactId>dubbo-filter-seata</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/ruoyi-common/ruoyi-common-seata/src/main/java/org/dromara/common/seata/config/SeataConfiguration.java b/ruoyi-common/ruoyi-common-seata/src/main/java/org/dromara/common/seata/config/SeataConfiguration.java
new file mode 100644
index 0000000..6b4ddb2
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-seata/src/main/java/org/dromara/common/seata/config/SeataConfiguration.java
@@ -0,0 +1,16 @@
+package org.dromara.common.seata.config;
+
+import org.dromara.common.core.factory.YmlPropertySourceFactory;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.PropertySource;
+
+/**
+ * seata 閰嶇疆
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration
+@PropertySource(value = "classpath:common-seata.yml", factory = YmlPropertySourceFactory.class)
+public class SeataConfiguration {
+
+}
diff --git a/ruoyi-common/ruoyi-common-seata/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-seata/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..c51e1d0
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-seata/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.seata.config.SeataConfiguration
diff --git a/ruoyi-common/ruoyi-common-seata/src/main/resources/common-seata.yml b/ruoyi-common/ruoyi-common-seata/src/main/resources/common-seata.yml
new file mode 100644
index 0000000..698f2dd
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-seata/src/main/resources/common-seata.yml
@@ -0,0 +1,19 @@
+# 鍐呯疆閰嶇疆 涓嶅厑璁镐慨鏀� 濡傞渶淇敼璇峰湪 nacos 涓婂啓鐩稿悓閰嶇疆瑕嗙洊
+# seata閰嶇疆
+seata:
+  config:
+    type: nacos
+    nacos:
+      server-addr: ${spring.cloud.nacos.server-addr}
+      group: ${spring.cloud.nacos.config.group}
+      namespace: ${spring.profiles.active}
+      data-id: seata-server.properties
+  registry:
+    type: nacos
+    nacos:
+      application: ruoyi-seata-server
+      server-addr: ${spring.cloud.nacos.server-addr}
+      group: ${spring.cloud.nacos.discovery.group}
+      namespace: ${spring.profiles.active}
+  # 鍏抽棴鑷姩浠g悊
+  enable-auto-data-source-proxy: false
diff --git a/ruoyi-common/ruoyi-common-security/pom.xml b/ruoyi-common/ruoyi-common-security/pom.xml
new file mode 100644
index 0000000..ee499f1
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-security/pom.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-security</artifactId>
+
+    <description>
+        ruoyi-common-security 瀹夊叏妯″潡
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-satoken</artifactId>
+        </dependency>
+
+        <!-- Sa-Token 鏉冮檺璁よ瘉, 鍦ㄧ嚎鏂囨。锛歨ttp://sa-token.dev33.cn/ -->
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-spring-boot3-starter</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>
diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/config/SecurityConfiguration.java b/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/config/SecurityConfiguration.java
new file mode 100644
index 0000000..efd34fc
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/config/SecurityConfiguration.java
@@ -0,0 +1,47 @@
+package org.dromara.common.security.config;
+
+import cn.dev33.satoken.SaManager;
+import cn.dev33.satoken.filter.SaServletFilter;
+import cn.dev33.satoken.interceptor.SaInterceptor;
+import cn.dev33.satoken.same.SaSameUtil;
+import cn.dev33.satoken.util.SaResult;
+import org.dromara.common.core.constant.HttpStatus;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * 鏉冮檺瀹夊叏閰嶇疆
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration
+public class SecurityConfiguration implements WebMvcConfigurer {
+
+    /**
+     * 娉ㄥ唽sa-token鐨勬嫤鎴櫒
+     */
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        // 娉ㄥ唽璺敱鎷︽埅鍣紝鑷畾涔夐獙璇佽鍒�
+        registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
+    }
+
+    /**
+     * 鏍¢獙鏄惁浠庣綉鍏宠浆鍙�
+     */
+    @Bean
+    public SaServletFilter getSaServletFilter() {
+        return new SaServletFilter()
+            .addInclude("/**")
+            .addExclude("/actuator/**")
+            .setAuth(obj -> {
+                if (SaManager.getConfig().getCheckSameToken()) {
+                    SaSameUtil.checkCurrentRequestToken();
+                }
+            })
+            .setError(e -> SaResult.error("璁よ瘉澶辫触锛屾棤娉曡闂郴缁熻祫婧�").setCode(HttpStatus.UNAUTHORIZED));
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/handler/GlobalExceptionHandler.java b/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/handler/GlobalExceptionHandler.java
new file mode 100644
index 0000000..8fb1f76
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-security/src/main/java/org/dromara/common/security/handler/GlobalExceptionHandler.java
@@ -0,0 +1,175 @@
+package org.dromara.common.security.handler;
+
+import cn.dev33.satoken.exception.NotLoginException;
+import cn.dev33.satoken.exception.NotPermissionException;
+import cn.dev33.satoken.exception.NotRoleException;
+import cn.dev33.satoken.exception.SameTokenInvalidException;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.http.HttpStatus;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.ConstraintViolationException;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.exception.base.BaseException;
+import org.dromara.common.core.utils.StreamUtils;
+import org.springframework.context.support.DefaultMessageSourceResolvable;
+import org.springframework.validation.BindException;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.MissingPathVariableException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
+
+/**
+ * 鍏ㄥ眬寮傚父澶勭悊鍣�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+    /**
+     * 鏉冮檺鐮佸紓甯�
+     */
+    @ExceptionHandler(NotPermissionException.class)
+    public R<Void> handleNotPermissionException(NotPermissionException e, HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        log.error("璇锋眰鍦板潃'{}',鏉冮檺鐮佹牎楠屽け璐�'{}'", requestURI, e.getMessage());
+        return R.fail(HttpStatus.HTTP_FORBIDDEN, "娌℃湁璁块棶鏉冮檺锛岃鑱旂郴绠$悊鍛樻巿鏉�");
+    }
+
+    /**
+     * 瑙掕壊鏉冮檺寮傚父
+     */
+    @ExceptionHandler(NotRoleException.class)
+    public R<Void> handleNotRoleException(NotRoleException e, HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        log.error("璇锋眰鍦板潃'{}',瑙掕壊鏉冮檺鏍¢獙澶辫触'{}'", requestURI, e.getMessage());
+        return R.fail(HttpStatus.HTTP_FORBIDDEN, "娌℃湁璁块棶鏉冮檺锛岃鑱旂郴绠$悊鍛樻巿鏉�");
+    }
+
+    /**
+     * 璁よ瘉澶辫触
+     */
+    @ExceptionHandler(NotLoginException.class)
+    public R<Void> handleNotLoginException(NotLoginException e, HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        log.error("璇锋眰鍦板潃'{}',璁よ瘉澶辫触'{}',鏃犳硶璁块棶绯荤粺璧勬簮", requestURI, e.getMessage());
+        return R.fail(HttpStatus.HTTP_UNAUTHORIZED, "璁よ瘉澶辫触锛屾棤娉曡闂郴缁熻祫婧�");
+    }
+
+    /**
+     * 鏃犳晥璁よ瘉
+     */
+    @ExceptionHandler(SameTokenInvalidException.class)
+    public R<Void> handleSameTokenInvalidException(SameTokenInvalidException e, HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        log.error("璇锋眰鍦板潃'{}',鍐呯綉璁よ瘉澶辫触'{}',鏃犳硶璁块棶绯荤粺璧勬簮", requestURI, e.getMessage());
+        return R.fail(HttpStatus.HTTP_UNAUTHORIZED, "璁よ瘉澶辫触锛屾棤娉曡闂郴缁熻祫婧�");
+    }
+
+    /**
+     * 璇锋眰鏂瑰紡涓嶆敮鎸�
+     */
+    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
+    public R<Void> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
+                                                                HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        log.error("璇锋眰鍦板潃'{}',涓嶆敮鎸�'{}'璇锋眰", requestURI, e.getMethod());
+        return R.fail(e.getMessage());
+    }
+
+    /**
+     * 涓氬姟寮傚父
+     */
+    @ExceptionHandler(ServiceException.class)
+    public R<Void> handleServiceException(ServiceException e, HttpServletRequest request) {
+        log.error(e.getMessage());
+        Integer code = e.getCode();
+        return ObjectUtil.isNotNull(code) ? R.fail(code, e.getMessage()) : R.fail(e.getMessage());
+    }
+
+    /**
+     * 涓氬姟寮傚父
+     */
+    @ExceptionHandler(BaseException.class)
+    public R<Void> handleBaseException(BaseException e, HttpServletRequest request) {
+        log.error(e.getMessage());
+        return R.fail(e.getMessage());
+    }
+
+    /**
+     * 璇锋眰璺緞涓己灏戝繀闇�鐨勮矾寰勫彉閲�
+     */
+    @ExceptionHandler(MissingPathVariableException.class)
+    public R<Void> handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        log.error("璇锋眰璺緞涓己灏戝繀闇�鐨勮矾寰勫彉閲�'{}',鍙戠敓绯荤粺寮傚父.", requestURI);
+        return R.fail(String.format("璇锋眰璺緞涓己灏戝繀闇�鐨勮矾寰勫彉閲廩%s]", e.getVariableName()));
+    }
+
+    /**
+     * 璇锋眰鍙傛暟绫诲瀷涓嶅尮閰�
+     */
+    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
+    public R<Void> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        log.error("璇锋眰鍙傛暟绫诲瀷涓嶅尮閰�'{}',鍙戠敓绯荤粺寮傚父.", requestURI);
+        return R.fail(String.format("璇锋眰鍙傛暟绫诲瀷涓嶅尮閰嶏紝鍙傛暟[%s]瑕佹眰绫诲瀷涓猴細'%s'锛屼絾杈撳叆鍊间负锛�'%s'", e.getName(), e.getRequiredType().getName(), e.getValue()));
+    }
+
+    /**
+     * 鎷︽埅鏈煡鐨勮繍琛屾椂寮傚父
+     */
+    @ExceptionHandler(RuntimeException.class)
+    public R<Void> handleRuntimeException(RuntimeException e, HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        log.error("璇锋眰鍦板潃'{}',鍙戠敓鏈煡寮傚父.", requestURI, e);
+        return R.fail(e.getMessage());
+    }
+
+    /**
+     * 绯荤粺寮傚父
+     */
+    @ExceptionHandler(Exception.class)
+    public R<Void> handleException(Exception e, HttpServletRequest request) {
+        String requestURI = request.getRequestURI();
+        log.error("璇锋眰鍦板潃'{}',鍙戠敓绯荤粺寮傚父.", requestURI, e);
+        return R.fail(e.getMessage());
+    }
+
+    /**
+     * 鑷畾涔夐獙璇佸紓甯�
+     */
+    @ExceptionHandler(BindException.class)
+    public R<Void> handleBindException(BindException e) {
+        log.error(e.getMessage());
+        String message = StreamUtils.join(e.getAllErrors(), DefaultMessageSourceResolvable::getDefaultMessage, ", ");
+        return R.fail(message);
+    }
+
+    /**
+     * 鑷畾涔夐獙璇佸紓甯�
+     */
+    @ExceptionHandler(ConstraintViolationException.class)
+    public R<Void> constraintViolationException(ConstraintViolationException e) {
+        log.error(e.getMessage());
+        String message = StreamUtils.join(e.getConstraintViolations(), ConstraintViolation::getMessage, ", ");
+        return R.fail(message);
+    }
+
+    /**
+     * 鑷畾涔夐獙璇佸紓甯�
+     */
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    public R<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
+        log.error(e.getMessage());
+        String message = e.getBindingResult().getFieldError().getDefaultMessage();
+        return R.fail(message);
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..4c96c4e
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1,2 @@
+org.dromara.common.security.handler.GlobalExceptionHandler
+org.dromara.common.security.config.SecurityConfiguration
diff --git a/ruoyi-common/ruoyi-common-sensitive/pom.xml b/ruoyi-common/ruoyi-common-sensitive/pom.xml
new file mode 100644
index 0000000..fecdf09
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-sensitive/pom.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-sensitive</artifactId>
+
+    <description>
+        ruoyi-common-sensitive 鑴辨晱妯″潡
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-json</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/annotation/Sensitive.java b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/annotation/Sensitive.java
new file mode 100644
index 0000000..1dfc896
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/annotation/Sensitive.java
@@ -0,0 +1,28 @@
+package org.dromara.common.sensitive.annotation;
+
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import org.dromara.common.sensitive.core.SensitiveStrategy;
+import org.dromara.common.sensitive.handler.SensitiveHandler;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 鏁版嵁鑴辨晱娉ㄨВ
+ *
+ * @author zhujie
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+@JacksonAnnotationsInside
+@JsonSerialize(using = SensitiveHandler.class)
+public @interface Sensitive {
+    SensitiveStrategy strategy();
+
+    String roleKey() default "";
+
+    String perms() default "";
+}
diff --git a/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveService.java b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveService.java
new file mode 100644
index 0000000..7b5264b
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveService.java
@@ -0,0 +1,18 @@
+package org.dromara.common.sensitive.core;
+
+/**
+ * 鑴辨晱鏈嶅姟
+ * 榛樿绠$悊鍛樹笉杩囨护
+ * 闇�鑷鏍规嵁涓氬姟閲嶅啓瀹炵幇
+ *
+ * @author Lion Li
+ * @version 3.6.0
+ */
+public interface SensitiveService {
+
+    /**
+     * 鏄惁鑴辨晱
+     */
+    boolean isSensitive(String roleKey, String perms);
+
+}
diff --git a/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveStrategy.java b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveStrategy.java
new file mode 100644
index 0000000..9d1978a
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/core/SensitiveStrategy.java
@@ -0,0 +1,49 @@
+package org.dromara.common.sensitive.core;
+
+import cn.hutool.core.util.DesensitizedUtil;
+import lombok.AllArgsConstructor;
+
+import java.util.function.Function;
+
+/**
+ * 鑴辨晱绛栫暐
+ *
+ * @author Yjoioooo
+ * @version 3.6.0
+ */
+@AllArgsConstructor
+public enum SensitiveStrategy {
+
+    /**
+     * 韬唤璇佽劚鏁�
+     */
+    ID_CARD(s -> DesensitizedUtil.idCardNum(s, 3, 4)),
+
+    /**
+     * 鎵嬫満鍙疯劚鏁�
+     */
+    PHONE(DesensitizedUtil::mobilePhone),
+
+    /**
+     * 鍦板潃鑴辨晱
+     */
+    ADDRESS(s -> DesensitizedUtil.address(s, 8)),
+
+    /**
+     * 閭鑴辨晱
+     */
+    EMAIL(DesensitizedUtil::email),
+
+    /**
+     * 閾惰鍗�
+     */
+    BANK_CARD(DesensitizedUtil::bankCard);
+
+    //鍙嚜琛屾坊鍔犲叾浠栬劚鏁忕瓥鐣�
+
+    private final Function<String, String> desensitizer;
+
+    public Function<String, String> desensitizer() {
+        return desensitizer;
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/handler/SensitiveHandler.java b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/handler/SensitiveHandler.java
new file mode 100644
index 0000000..c76c83a
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-sensitive/src/main/java/org/dromara/common/sensitive/handler/SensitiveHandler.java
@@ -0,0 +1,58 @@
+package org.dromara.common.sensitive.handler;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.ContextualSerializer;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.sensitive.annotation.Sensitive;
+import org.dromara.common.sensitive.core.SensitiveService;
+import org.dromara.common.sensitive.core.SensitiveStrategy;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeansException;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * 鏁版嵁鑴辨晱json搴忓垪鍖栧伐鍏�
+ *
+ * @author Yjoioooo
+ */
+@Slf4j
+public class SensitiveHandler extends JsonSerializer<String> implements ContextualSerializer {
+
+    private SensitiveStrategy strategy;
+    private String roleKey;
+    private String perms;
+
+    @Override
+    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+        try {
+            SensitiveService sensitiveService = SpringUtils.getBean(SensitiveService.class);
+            if (ObjectUtil.isNotNull(sensitiveService) && sensitiveService.isSensitive(roleKey, perms)) {
+                gen.writeString(strategy.desensitizer().apply(value));
+            } else {
+                gen.writeString(value);
+            }
+        } catch (BeansException e) {
+            log.error("鑴辨晱瀹炵幇涓嶅瓨鍦�, 閲囩敤榛樿澶勭悊 => {}", e.getMessage());
+            gen.writeString(value);
+        }
+    }
+
+    @Override
+    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
+        Sensitive annotation = property.getAnnotation(Sensitive.class);
+        if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) {
+            this.strategy = annotation.strategy();
+            this.roleKey = annotation.roleKey();
+            this.perms = annotation.perms();
+            return this;
+        }
+        return prov.findValueSerializer(property.getType(), property);
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-sentinel/pom.xml b/ruoyi-common/ruoyi-common-sentinel/pom.xml
new file mode 100644
index 0000000..3185baf
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-sentinel/pom.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-sentinel</artifactId>
+
+    <description>
+        ruoyi-common-sentinel 闄愭祦妯″潡
+    </description>
+
+    <dependencies>
+        <!-- SpringCloud Alibaba Sentinel -->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
+        </dependency>
+
+        <!-- Sentinel Datasource Nacos -->
+        <dependency>
+            <groupId>com.alibaba.csp</groupId>
+            <artifactId>sentinel-datasource-nacos</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.csp</groupId>
+            <artifactId>sentinel-apache-dubbo3-adapter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.dataformat</groupId>
+            <artifactId>jackson-dataformat-xml</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-autoconfigure</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/ruoyi-common/ruoyi-common-sentinel/src/main/java/com/alibaba/cloud/sentinel/custom/SentinelAutoConfiguration.java b/ruoyi-common/ruoyi-common-sentinel/src/main/java/com/alibaba/cloud/sentinel/custom/SentinelAutoConfiguration.java
new file mode 100644
index 0000000..df23c49
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-sentinel/src/main/java/com/alibaba/cloud/sentinel/custom/SentinelAutoConfiguration.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2013-2018 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.alibaba.cloud.sentinel.custom;
+
+import com.alibaba.cloud.commons.lang.StringUtils;
+import com.alibaba.cloud.sentinel.SentinelProperties;
+import com.alibaba.cloud.sentinel.datasource.converter.JsonConverter;
+import com.alibaba.cloud.sentinel.datasource.converter.XmlConverter;
+import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
+import com.alibaba.csp.sentinel.config.SentinelConfig;
+import com.alibaba.csp.sentinel.init.InitExecutor;
+import com.alibaba.csp.sentinel.log.LogBase;
+import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
+import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
+import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
+import com.alibaba.csp.sentinel.slots.system.SystemRule;
+import com.alibaba.csp.sentinel.transport.config.TransportConfig;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.xml.XmlMapper;
+import jakarta.annotation.PostConstruct;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.sentinel.config.properties.SentinelCustomProperties;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.client.discovery.DiscoveryClient;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.env.Environment;
+
+import java.util.List;
+
+import static com.alibaba.cloud.sentinel.SentinelConstants.BLOCK_PAGE_URL_CONF_KEY;
+import static com.alibaba.csp.sentinel.config.SentinelConfig.setConfig;
+
+/**
+ * 鏀归�爏entinel鑷姩閰嶇疆 鏀寔鏈嶅姟鍚嶆敞鍐�
+ *
+ * @author Lion Li
+ *
+ * @author xiaojing
+ * @author jiashuai.xie
+ * @author <a href="mailto:fangjian0423@gmail.com">Jim</a>
+ */
+@Configuration(proxyBeanMethods = false)
+@ConditionalOnProperty(name = "spring.cloud.sentinel.enabled", matchIfMissing = true)
+@EnableConfigurationProperties({SentinelProperties.class, SentinelCustomProperties.class})
+public class SentinelAutoConfiguration {
+
+	@Value("${project.name:${spring.application.name:}}")
+	private String projectName;
+
+	@Autowired
+	private SentinelProperties properties;
+    @Autowired
+    private SentinelCustomProperties customProperties;
+    @Autowired
+    private DiscoveryClient discoveryClient;
+
+	@PostConstruct
+	private void init() {
+		if (StringUtils.isEmpty(System.getProperty(LogBase.LOG_DIR))
+				&& StringUtils.isNotBlank(properties.getLog().getDir())) {
+			System.setProperty(LogBase.LOG_DIR, properties.getLog().getDir());
+		}
+		if (StringUtils.isEmpty(System.getProperty(LogBase.LOG_NAME_USE_PID))
+				&& properties.getLog().isSwitchPid()) {
+			System.setProperty(LogBase.LOG_NAME_USE_PID,
+					String.valueOf(properties.getLog().isSwitchPid()));
+		}
+		if (StringUtils.isEmpty(System.getProperty(SentinelConfig.APP_NAME_PROP_KEY))
+				&& StringUtils.isNotBlank(projectName)) {
+			System.setProperty(SentinelConfig.APP_NAME_PROP_KEY, projectName);
+		}
+		if (StringUtils.isEmpty(System.getProperty(TransportConfig.SERVER_PORT))
+				&& StringUtils.isNotBlank(properties.getTransport().getPort())) {
+			System.setProperty(TransportConfig.SERVER_PORT,
+					properties.getTransport().getPort());
+		}
+
+        if (StringUtils.isNotBlank(customProperties.getServerName())) {
+            List<ServiceInstance> instances = discoveryClient.getInstances(customProperties.getServerName());
+            String serverList = StreamUtils.join(instances, instance ->
+                String.format("http://%s:%s", instance.getHost(), instance.getPort()));
+            System.setProperty(TransportConfig.CONSOLE_SERVER, serverList);
+        } else {
+            if (StringUtils.isEmpty(System.getProperty(TransportConfig.CONSOLE_SERVER))
+                && StringUtils.isNotBlank(properties.getTransport().getDashboard())) {
+                System.setProperty(TransportConfig.CONSOLE_SERVER,
+                    properties.getTransport().getDashboard());
+            }
+        }
+
+		if (StringUtils.isEmpty(System.getProperty(TransportConfig.HEARTBEAT_INTERVAL_MS))
+				&& StringUtils
+						.isNotBlank(properties.getTransport().getHeartbeatIntervalMs())) {
+			System.setProperty(TransportConfig.HEARTBEAT_INTERVAL_MS,
+					properties.getTransport().getHeartbeatIntervalMs());
+		}
+		if (StringUtils.isEmpty(System.getProperty(TransportConfig.HEARTBEAT_CLIENT_IP))
+				&& StringUtils.isNotBlank(properties.getTransport().getClientIp())) {
+			System.setProperty(TransportConfig.HEARTBEAT_CLIENT_IP,
+					properties.getTransport().getClientIp());
+		}
+		if (StringUtils.isEmpty(System.getProperty(SentinelConfig.CHARSET))
+				&& StringUtils.isNotBlank(properties.getMetric().getCharset())) {
+			System.setProperty(SentinelConfig.CHARSET,
+					properties.getMetric().getCharset());
+		}
+		if (StringUtils
+				.isEmpty(System.getProperty(SentinelConfig.SINGLE_METRIC_FILE_SIZE))
+				&& StringUtils.isNotBlank(properties.getMetric().getFileSingleSize())) {
+			System.setProperty(SentinelConfig.SINGLE_METRIC_FILE_SIZE,
+					properties.getMetric().getFileSingleSize());
+		}
+		if (StringUtils
+				.isEmpty(System.getProperty(SentinelConfig.TOTAL_METRIC_FILE_COUNT))
+				&& StringUtils.isNotBlank(properties.getMetric().getFileTotalCount())) {
+			System.setProperty(SentinelConfig.TOTAL_METRIC_FILE_COUNT,
+					properties.getMetric().getFileTotalCount());
+		}
+		if (StringUtils.isEmpty(System.getProperty(SentinelConfig.COLD_FACTOR))
+				&& StringUtils.isNotBlank(properties.getFlow().getColdFactor())) {
+			System.setProperty(SentinelConfig.COLD_FACTOR,
+					properties.getFlow().getColdFactor());
+		}
+		if (StringUtils.isNotBlank(properties.getBlockPage())) {
+			setConfig(BLOCK_PAGE_URL_CONF_KEY, properties.getBlockPage());
+		}
+
+		// earlier initialize
+		if (properties.isEager()) {
+			InitExecutor.doInit();
+		}
+
+	}
+
+	@Bean
+	@ConditionalOnMissingBean
+	public SentinelResourceAspect sentinelResourceAspect() {
+		return new SentinelResourceAspect();
+	}
+
+	@Bean
+	@ConditionalOnMissingBean
+	@ConditionalOnClass(name = "org.springframework.web.client.RestTemplate")
+	@ConditionalOnProperty(name = "resttemplate.sentinel.enabled", havingValue = "true",
+			matchIfMissing = true)
+	public SentinelBeanPostProcessor sentinelBeanPostProcessor(
+			ApplicationContext applicationContext) {
+		return new SentinelBeanPostProcessor(applicationContext);
+	}
+
+	@Bean
+	@ConditionalOnMissingBean
+	public SentinelDataSourceHandler sentinelDataSourceHandler(
+			DefaultListableBeanFactory beanFactory, SentinelProperties sentinelProperties,
+			Environment env) {
+		return new SentinelDataSourceHandler(beanFactory, sentinelProperties, env);
+	}
+
+	@ConditionalOnClass(ObjectMapper.class)
+	@Configuration(proxyBeanMethods = false)
+	protected static class SentinelConverterConfiguration {
+
+		@Configuration(proxyBeanMethods = false)
+		protected static class SentinelJsonConfiguration {
+
+			private ObjectMapper objectMapper = new ObjectMapper();
+
+			public SentinelJsonConfiguration() {
+				objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
+						false);
+			}
+
+			@Bean("sentinel-json-flow-converter")
+			public JsonConverter jsonFlowConverter() {
+				return new JsonConverter(objectMapper, FlowRule.class);
+			}
+
+			@Bean("sentinel-json-degrade-converter")
+			public JsonConverter jsonDegradeConverter() {
+				return new JsonConverter(objectMapper, DegradeRule.class);
+			}
+
+			@Bean("sentinel-json-system-converter")
+			public JsonConverter jsonSystemConverter() {
+				return new JsonConverter(objectMapper, SystemRule.class);
+			}
+
+			@Bean("sentinel-json-authority-converter")
+			public JsonConverter jsonAuthorityConverter() {
+				return new JsonConverter(objectMapper, AuthorityRule.class);
+			}
+
+			@Bean("sentinel-json-param-flow-converter")
+			public JsonConverter jsonParamFlowConverter() {
+				return new JsonConverter(objectMapper, ParamFlowRule.class);
+			}
+
+		}
+
+		@ConditionalOnClass(XmlMapper.class)
+		@Configuration(proxyBeanMethods = false)
+		protected static class SentinelXmlConfiguration {
+
+			private XmlMapper xmlMapper = new XmlMapper();
+
+			public SentinelXmlConfiguration() {
+				xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
+						false);
+			}
+
+			@Bean("sentinel-xml-flow-converter")
+			public XmlConverter xmlFlowConverter() {
+				return new XmlConverter(xmlMapper, FlowRule.class);
+			}
+
+			@Bean("sentinel-xml-degrade-converter")
+			public XmlConverter xmlDegradeConverter() {
+				return new XmlConverter(xmlMapper, DegradeRule.class);
+			}
+
+			@Bean("sentinel-xml-system-converter")
+			public XmlConverter xmlSystemConverter() {
+				return new XmlConverter(xmlMapper, SystemRule.class);
+			}
+
+			@Bean("sentinel-xml-authority-converter")
+			public XmlConverter xmlAuthorityConverter() {
+				return new XmlConverter(xmlMapper, AuthorityRule.class);
+			}
+
+			@Bean("sentinel-xml-param-flow-converter")
+			public XmlConverter xmlParamFlowConverter() {
+				return new XmlConverter(xmlMapper, ParamFlowRule.class);
+			}
+
+		}
+
+	}
+
+}
diff --git a/ruoyi-common/ruoyi-common-sentinel/src/main/java/org/dromara/common/sentinel/config/properties/SentinelCustomProperties.java b/ruoyi-common/ruoyi-common-sentinel/src/main/java/org/dromara/common/sentinel/config/properties/SentinelCustomProperties.java
new file mode 100644
index 0000000..934cd36
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-sentinel/src/main/java/org/dromara/common/sentinel/config/properties/SentinelCustomProperties.java
@@ -0,0 +1,17 @@
+package org.dromara.common.sentinel.config.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * sentinel鑷畾涔夐厤缃被
+ *
+ * @author Lion Li
+ */
+@Data
+@ConfigurationProperties(prefix = "spring.cloud.sentinel.transport")
+public class SentinelCustomProperties {
+
+    private String serverName;
+
+}
diff --git a/ruoyi-common/ruoyi-common-sentinel/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-sentinel/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-sentinel/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+
diff --git a/ruoyi-common/ruoyi-common-skylog/pom.xml b/ruoyi-common/ruoyi-common-skylog/pom.xml
new file mode 100644
index 0000000..212bcf0
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-skylog/pom.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-skylog</artifactId>
+
+    <description>
+        ruoyi-common-skylog skywalking鏃ュ織鏀堕泦妯″潡
+    </description>
+
+    <dependencies>
+        <!-- skywalking 鏁村悎 logback -->
+        <dependency>
+            <groupId>org.apache.skywalking</groupId>
+            <artifactId>apm-toolkit-logback-1.x</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.skywalking</groupId>
+            <artifactId>apm-toolkit-trace</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/ruoyi-common/ruoyi-common-skylog/src/main/resources/logback-skylog.xml b/ruoyi-common/ruoyi-common-skylog/src/main/resources/logback-skylog.xml
new file mode 100644
index 0000000..7e64cc5
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-skylog/src/main/resources/logback-skylog.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<included>
+
+    <!-- 鎺у埗鍙拌緭鍑� tid -->
+    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
+            <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
+                <pattern>[%tid] ${console.log.pattern}</pattern>
+            </layout>
+            <charset>utf-8</charset>
+        </encoder>
+    </appender>
+
+    <!-- skywalking 閲囬泦鏃ュ織 -->
+    <appender name="sky_log" class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender">
+        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
+            <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
+                <pattern>[%tid] ${console.log.pattern}</pattern>
+            </layout>
+            <charset>utf-8</charset>
+        </encoder>
+    </appender>
+
+    <root level="info">
+        <appender-ref ref="console"/>
+        <appender-ref ref="sky_log"/>
+    </root>
+</included>
diff --git a/ruoyi-common/ruoyi-common-sms/pom.xml b/ruoyi-common/ruoyi-common-sms/pom.xml
new file mode 100644
index 0000000..64e5242
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-sms/pom.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-sms</artifactId>
+
+    <description>
+        ruoyi-common-sms 鐭俊妯″潡
+    </description>
+
+	<dependencies>
+
+        <dependency>
+            <groupId>org.dromara.sms4j</groupId>
+            <artifactId>sms4j-spring-boot-starter</artifactId>
+            <exclusions>
+                <!-- 鎺掗櫎浜笢鐭俊鍐呭瓨鍦ㄧ殑fastjson绛夊緟浣滆�呭悗缁慨澶� -->
+                <exclusion>
+                    <groupId>com.alibaba</groupId>
+                    <artifactId>fastjson</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+    </dependencies>
+</project>
diff --git a/ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/config/SmsAutoConfiguration.java b/ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/config/SmsAutoConfiguration.java
new file mode 100644
index 0000000..29f60fc
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-sms/src/main/java/org/dromara/common/sms/config/SmsAutoConfiguration.java
@@ -0,0 +1,14 @@
+package org.dromara.common.sms.config;
+
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+
+/**
+ * 鐭俊閰嶇疆绫�(鏆傛椂娌$敤 棰勭暀鎵╁睍)
+ *
+ * @author Lion Li
+ * @version 4.2.0
+ */
+@AutoConfiguration
+public class SmsAutoConfiguration {
+
+}
diff --git a/ruoyi-common/ruoyi-common-sms/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-sms/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..5919ce3
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-sms/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.sms.config.SmsAutoConfiguration
diff --git a/ruoyi-common/ruoyi-common-social/pom.xml b/ruoyi-common/ruoyi-common-social/pom.xml
new file mode 100644
index 0000000..887a8e0
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-social/pom.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-social</artifactId>
+
+    <description>
+        ruoyi-common-social 鎺堟潈璁よ瘉
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>me.zhyd.oauth</groupId>
+            <artifactId>JustAuth</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-json</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-redis</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/SocialAutoConfiguration.java b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/SocialAutoConfiguration.java
new file mode 100644
index 0000000..19b39d8
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/SocialAutoConfiguration.java
@@ -0,0 +1,23 @@
+package org.dromara.common.social.config;
+
+import me.zhyd.oauth.cache.AuthStateCache;
+import org.dromara.common.social.config.properties.SocialProperties;
+import org.dromara.common.social.utils.AuthRedisStateCache;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * Social 閰嶇疆灞炴��
+ * @author thiszhc
+ */
+@AutoConfiguration
+@EnableConfigurationProperties(SocialProperties.class)
+public class SocialAutoConfiguration {
+
+    @Bean
+    public AuthStateCache authStateCache() {
+        return new AuthRedisStateCache();
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialLoginConfigProperties.java b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialLoginConfigProperties.java
new file mode 100644
index 0000000..76be234
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialLoginConfigProperties.java
@@ -0,0 +1,68 @@
+package org.dromara.common.social.config.properties;
+
+import lombok.Data;
+
+/**
+ * 绀句氦鐧诲綍閰嶇疆
+ *
+ * @author thiszhc
+ */
+@Data
+public class SocialLoginConfigProperties {
+
+    /**
+     * 搴旂敤 ID
+     */
+    private String clientId;
+
+    /**
+     * 搴旂敤瀵嗛挜
+     */
+    private String clientSecret;
+
+    /**
+     * 鍥炶皟鍦板潃
+     */
+    private String redirectUri;
+
+    /**
+     * 鏄惁鑾峰彇unionId
+     */
+    private boolean unionId;
+
+    /**
+     * Coding 浼佷笟鍚嶇О
+     */
+    private String codingGroupName;
+
+    /**
+     * 鏀粯瀹濆叕閽�
+     */
+    private String alipayPublicKey;
+
+    /**
+     * 浼佷笟寰俊搴旂敤ID
+     */
+    private String agentId;
+
+    /**
+     * stackoverflow api key
+     */
+    private String stackOverflowKey;
+
+    /**
+     * 璁惧ID
+     */
+    private String deviceId;
+
+    /**
+     * 瀹㈡埛绔郴缁熺被鍨�
+     */
+    private String clientOsType;
+
+    /**
+     * maxkey 鏈嶅姟鍣ㄥ湴鍧�
+     */
+    private String serverUrl;
+
+}
diff --git a/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialProperties.java b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialProperties.java
new file mode 100644
index 0000000..1beb7d0
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialProperties.java
@@ -0,0 +1,29 @@
+package org.dromara.common.social.config.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+
+/**
+ * Social 閰嶇疆灞炴��
+ *
+ * @author thiszhc
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "justauth")
+public class SocialProperties {
+
+    /**
+     * 鏄惁鍚敤
+     */
+    private Boolean enabled;
+
+    /**
+     * 鎺堟潈绫诲瀷
+     */
+    private Map<String, SocialLoginConfigProperties> type;
+
+}
diff --git a/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeyRequest.java b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeyRequest.java
new file mode 100644
index 0000000..b95c19e
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeyRequest.java
@@ -0,0 +1,80 @@
+package org.dromara.common.social.maxkey;
+
+import cn.hutool.core.lang.Dict;
+import me.zhyd.oauth.cache.AuthStateCache;
+import me.zhyd.oauth.config.AuthConfig;
+import me.zhyd.oauth.exception.AuthException;
+import me.zhyd.oauth.model.AuthCallback;
+import me.zhyd.oauth.model.AuthToken;
+import me.zhyd.oauth.model.AuthUser;
+import me.zhyd.oauth.request.AuthDefaultRequest;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.json.utils.JsonUtils;
+
+/**
+ *  @author 闀挎槬鍙摜 2023骞�03鏈�26鏃�
+ */
+public class AuthMaxKeyRequest extends AuthDefaultRequest {
+
+    public static final String SERVER_URL = SpringUtils.getProperty("justauth.type.maxkey.server-url");
+
+    /**
+     * 璁惧畾褰掑睘鍩�
+     */
+    public AuthMaxKeyRequest(AuthConfig config) {
+        super(config, AuthMaxKeySource.MAXKEY);
+    }
+
+    public AuthMaxKeyRequest(AuthConfig config, AuthStateCache authStateCache) {
+        super(config, AuthMaxKeySource.MAXKEY, authStateCache);
+    }
+
+    @Override
+    protected AuthToken getAccessToken(AuthCallback authCallback) {
+        String body = doPostAuthorizationCode(authCallback.getCode());
+        Dict object = JsonUtils.parseMap(body);
+        // oauth/token 楠岃瘉寮傚父
+        if (object.containsKey("error")) {
+            throw new AuthException(object.getStr("error_description"));
+        }
+        // user 楠岃瘉寮傚父
+        if (object.containsKey("message")) {
+            throw new AuthException(object.getStr("message"));
+        }
+        return AuthToken.builder()
+            .accessToken(object.getStr("access_token"))
+            .refreshToken(object.getStr("refresh_token"))
+            .idToken(object.getStr("id_token"))
+            .tokenType(object.getStr("token_type"))
+            .scope(object.getStr("scope"))
+            .build();
+    }
+
+    @Override
+    protected AuthUser getUserInfo(AuthToken authToken) {
+        String body = doGetUserInfo(authToken);
+        Dict object = JsonUtils.parseMap(body);
+        // oauth/token 楠岃瘉寮傚父
+        if (object.containsKey("error")) {
+            throw new AuthException(object.getStr("error_description"));
+        }
+        // user 楠岃瘉寮傚父
+        if (object.containsKey("message")) {
+            throw new AuthException(object.getStr("message"));
+        }
+        return AuthUser.builder()
+            .uuid(object.getStr("userId"))
+            .username(object.getStr("username"))
+            .nickname(object.getStr("displayName"))
+            .avatar(object.getStr("avatar_url"))
+            .blog(object.getStr("web_url"))
+            .company(object.getStr("organization"))
+            .location(object.getStr("location"))
+            .email(object.getStr("email"))
+            .remark(object.getStr("bio"))
+            .token(authToken)
+            .source(source.toString())
+            .build();
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeySource.java b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeySource.java
new file mode 100644
index 0000000..1ff57f7
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeySource.java
@@ -0,0 +1,52 @@
+package org.dromara.common.social.maxkey;
+
+import me.zhyd.oauth.config.AuthSource;
+import me.zhyd.oauth.request.AuthDefaultRequest;
+
+/**
+ * Oauth2 榛樿鎺ュ彛璇存槑
+ *
+ * @author 闀挎槬鍙摜 2023骞�03鏈�26鏃�
+ *
+ */
+public enum AuthMaxKeySource implements AuthSource {
+
+    /**
+     * 鑷繁鎼缓鐨� maxkey 绉佹湇
+     */
+    MAXKEY {
+
+        /**
+         * 鎺堟潈鐨刟pi
+         */
+        @Override
+        public String authorize() {
+            return AuthMaxKeyRequest.SERVER_URL + "/sign/authz/oauth/v20/authorize";
+        }
+
+        /**
+         * 鑾峰彇accessToken鐨刟pi
+         */
+        @Override
+        public String accessToken() {
+            return AuthMaxKeyRequest.SERVER_URL + "/sign/authz/oauth/v20/token";
+        }
+
+        /**
+         * 鑾峰彇鐢ㄦ埛淇℃伅鐨刟pi
+         */
+        @Override
+        public String userInfo() {
+            return AuthMaxKeyRequest.SERVER_URL + "/sign/api/oauth/v20/me";
+        }
+
+        /**
+         * 骞冲彴瀵瑰簲鐨� AuthRequest 瀹炵幇绫伙紝蹇呴』缁ф壙鑷� {@link AuthDefaultRequest}
+         */
+        @Override
+        public Class<? extends AuthDefaultRequest> getTargetClass() {
+            return AuthMaxKeyRequest.class;
+        }
+
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/AuthRedisStateCache.java b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/AuthRedisStateCache.java
new file mode 100644
index 0000000..0b6ec20
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/AuthRedisStateCache.java
@@ -0,0 +1,61 @@
+package org.dromara.common.social.utils;
+
+import lombok.AllArgsConstructor;
+import me.zhyd.oauth.cache.AuthStateCache;
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.redis.utils.RedisUtils;
+
+import java.time.Duration;
+
+/**
+ * 鎺堟潈鐘舵�佺紦瀛�
+ */
+@AllArgsConstructor
+public class AuthRedisStateCache implements AuthStateCache {
+
+    /**
+     * 瀛樺叆缂撳瓨
+     *
+     * @param key   缂撳瓨key
+     * @param value 缂撳瓨鍐呭
+     */
+    @Override
+    public void cache(String key, String value) {
+        // 鎺堟潈瓒呮椂鏃堕棿 榛樿涓夊垎閽�
+        RedisUtils.setCacheObject(GlobalConstants.SOCIAL_AUTH_CODE_KEY + key, value, Duration.ofMinutes(3));
+    }
+
+    /**
+     * 瀛樺叆缂撳瓨
+     *
+     * @param key     缂撳瓨key
+     * @param value   缂撳瓨鍐呭
+     * @param timeout 鎸囧畾缂撳瓨杩囨湡鏃堕棿(姣)
+     */
+    @Override
+    public void cache(String key, String value, long timeout) {
+        RedisUtils.setCacheObject(GlobalConstants.SOCIAL_AUTH_CODE_KEY + key, value, Duration.ofMillis(timeout));
+    }
+
+    /**
+     * 鑾峰彇缂撳瓨鍐呭
+     *
+     * @param key 缂撳瓨key
+     * @return 缂撳瓨鍐呭
+     */
+    @Override
+    public String get(String key) {
+        return RedisUtils.getCacheObject(GlobalConstants.SOCIAL_AUTH_CODE_KEY + key);
+    }
+
+    /**
+     * 鏄惁瀛樺湪key锛屽鏋滃搴攌ey鐨剉alue鍊煎凡杩囨湡锛屼篃杩斿洖false
+     *
+     * @param key 缂撳瓨key
+     * @return true锛氬瓨鍦╧ey锛屽苟涓攙alue娌¤繃鏈燂紱false锛歬ey涓嶅瓨鍦ㄦ垨鑰呭凡杩囨湡
+     */
+    @Override
+    public boolean containsKey(String key) {
+        return RedisUtils.hasKey(GlobalConstants.SOCIAL_AUTH_CODE_KEY + key);
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java
new file mode 100644
index 0000000..a5ed6f1
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java
@@ -0,0 +1,70 @@
+package org.dromara.common.social.utils;
+
+import cn.hutool.core.util.ObjectUtil;
+import me.zhyd.oauth.config.AuthConfig;
+import me.zhyd.oauth.exception.AuthException;
+import me.zhyd.oauth.model.AuthCallback;
+import me.zhyd.oauth.model.AuthResponse;
+import me.zhyd.oauth.model.AuthUser;
+import me.zhyd.oauth.request.*;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.social.config.properties.SocialLoginConfigProperties;
+import org.dromara.common.social.config.properties.SocialProperties;
+import org.dromara.common.social.maxkey.AuthMaxKeyRequest;
+
+/**
+ * 璁よ瘉鎺堟潈宸ュ叿绫�
+ *
+ * @author thiszhc
+ */
+public class SocialUtils  {
+
+    private static final AuthRedisStateCache STATE_CACHE = SpringUtils.getBean(AuthRedisStateCache.class);
+
+    @SuppressWarnings("unchecked")
+    public static AuthResponse<AuthUser> loginAuth(String source, String code, String state, SocialProperties socialProperties) throws AuthException {
+        AuthRequest authRequest = getAuthRequest(source, socialProperties);
+        AuthCallback callback = new AuthCallback();
+        callback.setCode(code);
+        callback.setState(state);
+        return authRequest.login(callback);
+    }
+
+    public static AuthRequest getAuthRequest(String source, SocialProperties socialProperties) throws AuthException {
+        SocialLoginConfigProperties obj = socialProperties.getType().get(source);
+         if (ObjectUtil.isNull(obj)) {
+            throw new AuthException("涓嶆敮鎸佺殑绗笁鏂圭櫥褰曠被鍨�");
+        }
+        final AuthConfig.AuthConfigBuilder builder = AuthConfig.builder()
+            .clientId(obj.getClientId())
+            .clientSecret(obj.getClientSecret())
+            .redirectUri(obj.getRedirectUri());
+        return switch (source.toLowerCase()) {
+            case "dingtalk" -> new AuthDingTalkRequest(builder.build(), STATE_CACHE);
+            case "baidu" -> new AuthBaiduRequest(builder.build(), STATE_CACHE);
+            case "github" -> new AuthGithubRequest(builder.build(), STATE_CACHE);
+            case "gitee" -> new AuthGiteeRequest(builder.build(), STATE_CACHE);
+            case "weibo" -> new AuthWeiboRequest(builder.build(), STATE_CACHE);
+            case "coding" -> new AuthCodingRequest(builder.build(), STATE_CACHE);
+            case "oschina" -> new AuthOschinaRequest(builder.build(), STATE_CACHE);
+            // 鏀粯瀹濆湪鍒涘缓鍥炶皟鍦板潃鏃讹紝涓嶅厑璁镐娇鐢╨ocalhost鎴栬��127.0.0.1锛屾墍浠ヨ繖鍎跨殑鍥炶皟鍦板潃浣跨敤鐨勫眬鍩熺綉鍐呯殑ip
+            case "alipay_wallet" -> new AuthAlipayRequest(builder.build(), socialProperties.getType().get("alipay_wallet").getAlipayPublicKey(), STATE_CACHE);
+            case "qq" -> new AuthQqRequest(builder.build(), STATE_CACHE);
+            case "wechat_open" -> new AuthWeChatOpenRequest(builder.build(), STATE_CACHE);
+            case "taobao" -> new AuthTaobaoRequest(builder.build(), STATE_CACHE);
+            case "douyin" -> new AuthDouyinRequest(builder.build(), STATE_CACHE);
+            case "linkedin" -> new AuthLinkedinRequest(builder.build(), STATE_CACHE);
+            case "microsoft" -> new AuthMicrosoftRequest(builder.build(), STATE_CACHE);
+            case "renren" -> new AuthRenrenRequest(builder.build(), STATE_CACHE);
+            case "stack_overflow" -> new AuthStackOverflowRequest(builder.stackOverflowKey("").build(), STATE_CACHE);
+            case "huawei" -> new AuthHuaweiRequest(builder.build(), STATE_CACHE);
+            case "wechat_enterprise" -> new AuthWeChatEnterpriseQrcodeRequest(builder.agentId("").build(), STATE_CACHE);
+            case "gitlab" -> new AuthGitlabRequest(builder.build(), STATE_CACHE);
+            case "wechat_mp" -> new AuthWeChatMpRequest(builder.build(), STATE_CACHE);
+            case "aliyun" -> new AuthAliyunRequest(builder.build(), STATE_CACHE);
+            case "maxkey" -> new AuthMaxKeyRequest(builder.build(), STATE_CACHE);
+            default -> throw new AuthException("鏈幏鍙栧埌鏈夋晥鐨凙uth閰嶇疆");
+        };
+    }
+}
+
diff --git a/ruoyi-common/ruoyi-common-social/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-social/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..fc544a0
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-social/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.social.config.SocialAutoConfiguration
diff --git a/ruoyi-common/ruoyi-common-tenant/pom.xml b/ruoyi-common/ruoyi-common-tenant/pom.xml
new file mode 100644
index 0000000..2b87bf9
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-tenant/pom.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-tenant</artifactId>
+
+    <description>
+        ruoyi-common-tenant 绉熸埛妯″潡
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-mybatis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-redis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>transmittable-thread-local</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>
diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/config/TenantConfiguration.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/config/TenantConfiguration.java
new file mode 100644
index 0000000..f2c708a
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/config/TenantConfiguration.java
@@ -0,0 +1,106 @@
+package org.dromara.common.tenant.config;
+
+import cn.dev33.satoken.dao.SaTokenDao;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
+import org.dromara.common.core.utils.reflect.ReflectUtils;
+import org.dromara.common.mybatis.config.MybatisPlusConfiguration;
+import org.dromara.common.redis.config.RedisConfiguration;
+import org.dromara.common.redis.config.properties.RedissonProperties;
+import org.dromara.common.tenant.core.TenantSaTokenDao;
+import org.dromara.common.tenant.handle.PlusTenantLineHandler;
+import org.dromara.common.tenant.handle.TenantKeyPrefixHandler;
+import org.dromara.common.tenant.manager.TenantSpringCacheManager;
+import org.dromara.common.tenant.properties.TenantProperties;
+import org.redisson.config.ClusterServersConfig;
+import org.redisson.config.SingleServerConfig;
+import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.cache.CacheManager;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 绉熸埛閰嶇疆绫�
+ *
+ * @author Lion Li
+ */
+@EnableConfigurationProperties(TenantProperties.class)
+@AutoConfiguration(after = {RedisConfiguration.class})
+@ConditionalOnProperty(value = "tenant.enable", havingValue = "true")
+public class TenantConfiguration {
+
+    @ConditionalOnBean(MybatisPlusConfiguration.class)
+    @AutoConfiguration(after = {MybatisPlusConfiguration.class})
+    static class MybatisPlusConfig {
+
+        /**
+         * 鍒濆鍖栫鎴烽厤缃�
+         */
+        @Bean
+        public boolean tenantInit(MybatisPlusInterceptor mybatisPlusInterceptor,
+                                  TenantProperties tenantProperties) {
+            List<InnerInterceptor> interceptors = new ArrayList<>();
+            // 澶氱鎴锋彃浠� 蹇呴』鏀惧埌绗竴浣�
+            interceptors.add(tenantLineInnerInterceptor(tenantProperties));
+            interceptors.addAll(mybatisPlusInterceptor.getInterceptors());
+            mybatisPlusInterceptor.setInterceptors(interceptors);
+            return true;
+        }
+
+        /**
+         * 澶氱鎴锋彃浠�
+         */
+        public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantProperties tenantProperties) {
+            return new TenantLineInnerInterceptor(new PlusTenantLineHandler(tenantProperties));
+        }
+    }
+
+    @Bean
+    public RedissonAutoConfigurationCustomizer tenantRedissonCustomizer(RedissonProperties redissonProperties) {
+        return config -> {
+            TenantKeyPrefixHandler nameMapper = new TenantKeyPrefixHandler(redissonProperties.getKeyPrefix());
+            SingleServerConfig singleServerConfig = ReflectUtils.invokeGetter(config, "singleServerConfig");
+            if (ObjectUtil.isNotNull(singleServerConfig)) {
+                // 浣跨敤鍗曟満妯″紡
+                // 璁剧疆澶氱鎴� redis key鍓嶇紑
+                singleServerConfig.setNameMapper(nameMapper);
+                ReflectUtils.invokeSetter(config, "singleServerConfig", singleServerConfig);
+            }
+            ClusterServersConfig clusterServersConfig = ReflectUtils.invokeGetter(config, "clusterServersConfig");
+            // 闆嗙兢閰嶇疆鏂瑰紡 鍙傝�冧笅鏂规敞閲�
+            if (ObjectUtil.isNotNull(clusterServersConfig)) {
+                // 璁剧疆澶氱鎴� redis key鍓嶇紑
+                clusterServersConfig.setNameMapper(nameMapper);
+                ReflectUtils.invokeSetter(config, "clusterServersConfig", clusterServersConfig);
+            }
+        };
+    }
+
+    /**
+     * 澶氱鎴风紦瀛樼鐞嗗櫒
+     */
+    @Primary
+    @Bean
+    public CacheManager tenantCacheManager() {
+        return new TenantSpringCacheManager();
+    }
+
+    /**
+     * 澶氱鎴烽壌鏉僤ao瀹炵幇
+     */
+    @Primary
+    @Bean
+    public SaTokenDao tenantSaTokenDao() {
+        return new TenantSaTokenDao();
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/core/TenantEntity.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/core/TenantEntity.java
new file mode 100644
index 0000000..8ad0d2c
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/core/TenantEntity.java
@@ -0,0 +1,21 @@
+package org.dromara.common.tenant.core;
+
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 绉熸埛鍩虹被
+ *
+ * @author Michelle.Chung
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class TenantEntity extends BaseEntity {
+
+    /**
+     * 绉熸埛缂栧彿
+     */
+    private String tenantId;
+
+}
diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/core/TenantSaTokenDao.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/core/TenantSaTokenDao.java
new file mode 100644
index 0000000..b8da28e
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/core/TenantSaTokenDao.java
@@ -0,0 +1,148 @@
+package org.dromara.common.tenant.core;
+
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.satoken.core.dao.PlusSaTokenDao;
+
+import java.time.Duration;
+import java.util.List;
+
+/**
+ * SaToken 璁よ瘉鏁版嵁鎸佷箙灞� 閫傞厤澶氱鎴�
+ *
+ * @author Lion Li
+ */
+public class TenantSaTokenDao extends PlusSaTokenDao {
+
+    @Override
+    public String get(String key) {
+        return super.get(GlobalConstants.GLOBAL_REDIS_KEY + key);
+    }
+
+    @Override
+    public void set(String key, String value, long timeout) {
+        super.set(GlobalConstants.GLOBAL_REDIS_KEY + key, value, timeout);
+    }
+
+    /**
+     * 淇慨鏀规寚瀹歬ey-value閿�煎 (杩囨湡鏃堕棿涓嶅彉)
+     */
+    @Override
+    public void update(String key, String value) {
+        long expire = getTimeout(key);
+        // -2 = 鏃犳閿�
+        if (expire == NOT_VALUE_EXPIRE) {
+            return;
+        }
+        this.set(key, value, expire);
+    }
+
+    /**
+     * 鍒犻櫎Value
+     */
+    @Override
+    public void delete(String key) {
+        super.delete(GlobalConstants.GLOBAL_REDIS_KEY + key);
+    }
+
+    /**
+     * 鑾峰彇Value鐨勫墿浣欏瓨娲绘椂闂� (鍗曚綅: 绉�)
+     */
+    @Override
+    public long getTimeout(String key) {
+        return super.getTimeout(GlobalConstants.GLOBAL_REDIS_KEY + key);
+    }
+
+    /**
+     * 淇敼Value鐨勫墿浣欏瓨娲绘椂闂� (鍗曚綅: 绉�)
+     */
+    @Override
+    public void updateTimeout(String key, long timeout) {
+        // 鍒ゆ柇鏄惁鎯宠璁剧疆涓烘案涔�
+        if (timeout == NEVER_EXPIRE) {
+            long expire = getTimeout(key);
+            if (expire == NEVER_EXPIRE) {
+                // 濡傛灉鍏跺凡缁忚璁剧疆涓烘案涔咃紝鍒欎笉浣滀换浣曞鐞�
+            } else {
+                // 濡傛灉灏氭湭琚缃负姘镐箙锛岄偅涔堝啀娆et涓�娆�
+                this.set(key, this.get(key), timeout);
+            }
+            return;
+        }
+        RedisUtils.expire(GlobalConstants.GLOBAL_REDIS_KEY + key, Duration.ofSeconds(timeout));
+    }
+
+
+    /**
+     * 鑾峰彇Object锛屽鏃犺繑绌�
+     */
+    @Override
+    public Object getObject(String key) {
+        return super.getObject(GlobalConstants.GLOBAL_REDIS_KEY + key);
+    }
+
+    /**
+     * 鍐欏叆Object锛屽苟璁惧畾瀛樻椿鏃堕棿 (鍗曚綅: 绉�)
+     */
+    @Override
+    public void setObject(String key, Object object, long timeout) {
+        super.setObject(GlobalConstants.GLOBAL_REDIS_KEY + key, object, timeout);
+    }
+
+    /**
+     * 鏇存柊Object (杩囨湡鏃堕棿涓嶅彉)
+     */
+    @Override
+    public void updateObject(String key, Object object) {
+        long expire = getObjectTimeout(key);
+        // -2 = 鏃犳閿�
+        if (expire == NOT_VALUE_EXPIRE) {
+            return;
+        }
+        this.setObject(key, object, expire);
+    }
+
+    /**
+     * 鍒犻櫎Object
+     */
+    @Override
+    public void deleteObject(String key) {
+        super.deleteObject(GlobalConstants.GLOBAL_REDIS_KEY + key);
+    }
+
+    /**
+     * 鑾峰彇Object鐨勫墿浣欏瓨娲绘椂闂� (鍗曚綅: 绉�)
+     */
+    @Override
+    public long getObjectTimeout(String key) {
+        return super.getObjectTimeout(GlobalConstants.GLOBAL_REDIS_KEY + key);
+    }
+
+    /**
+     * 淇敼Object鐨勫墿浣欏瓨娲绘椂闂� (鍗曚綅: 绉�)
+     */
+    @Override
+    public void updateObjectTimeout(String key, long timeout) {
+        // 鍒ゆ柇鏄惁鎯宠璁剧疆涓烘案涔�
+        if (timeout == NEVER_EXPIRE) {
+            long expire = getObjectTimeout(key);
+            if (expire == NEVER_EXPIRE) {
+                // 濡傛灉鍏跺凡缁忚璁剧疆涓烘案涔咃紝鍒欎笉浣滀换浣曞鐞�
+            } else {
+                // 濡傛灉灏氭湭琚缃负姘镐箙锛岄偅涔堝啀娆et涓�娆�
+                this.setObject(key, this.getObject(key), timeout);
+            }
+            return;
+        }
+        RedisUtils.expire(GlobalConstants.GLOBAL_REDIS_KEY + key, Duration.ofSeconds(timeout));
+    }
+
+
+    /**
+     * 鎼滅储鏁版嵁
+     */
+    @Override
+    public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
+        return super.searchData(GlobalConstants.GLOBAL_REDIS_KEY + prefix, keyword, start, size, sortType);
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/exception/TenantException.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/exception/TenantException.java
new file mode 100644
index 0000000..ee2bc97
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/exception/TenantException.java
@@ -0,0 +1,20 @@
+package org.dromara.common.tenant.exception;
+
+import org.dromara.common.core.exception.base.BaseException;
+
+import java.io.Serial;
+
+/**
+ * 绉熸埛寮傚父绫�
+ *
+ * @author Lion Li
+ */
+public class TenantException extends BaseException {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    public TenantException(String code, Object... args) {
+        super("tenant", code, args, null);
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/PlusTenantLineHandler.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/PlusTenantLineHandler.java
new file mode 100644
index 0000000..6c93ee5
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/PlusTenantLineHandler.java
@@ -0,0 +1,56 @@
+package org.dromara.common.tenant.handle;
+
+import cn.hutool.core.collection.ListUtil;
+import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.expression.NullValue;
+import net.sf.jsqlparser.expression.StringValue;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.common.tenant.properties.TenantProperties;
+
+import java.util.List;
+
+/**
+ * 鑷畾涔夌鎴峰鐞嗗櫒
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@AllArgsConstructor
+public class PlusTenantLineHandler implements TenantLineHandler {
+
+    private final TenantProperties tenantProperties;
+
+    @Override
+    public Expression getTenantId() {
+        String tenantId = TenantHelper.getTenantId();
+        if (StringUtils.isBlank(tenantId)) {
+            log.error("鏃犳硶鑾峰彇鏈夋晥鐨勭鎴穒d -> Null");
+            return new NullValue();
+        }
+        // 杩斿洖鍥哄畾绉熸埛
+        return new StringValue(tenantId);
+    }
+
+    @Override
+    public boolean ignoreTable(String tableName) {
+        String tenantId = TenantHelper.getTenantId();
+        // 鍒ゆ柇鏄惁鏈夌鎴�
+        if (StringUtils.isNotBlank(tenantId)) {
+            // 涓嶉渶瑕佽繃婊ょ鎴风殑琛�
+            List<String> excludes = tenantProperties.getExcludes();
+            // 闈炰笟鍔¤〃
+            List<String> tables = ListUtil.toList(
+                "gen_table",
+                "gen_table_column"
+            );
+            tables.addAll(excludes);
+            return tables.contains(tableName);
+        }
+        return true;
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/TenantKeyPrefixHandler.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/TenantKeyPrefixHandler.java
new file mode 100644
index 0000000..14dad1a
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/handle/TenantKeyPrefixHandler.java
@@ -0,0 +1,66 @@
+package org.dromara.common.tenant.handle;
+
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.redis.handler.KeyPrefixHandler;
+import org.dromara.common.tenant.helper.TenantHelper;
+
+/**
+ * 澶氱鎴穜edis缂撳瓨key鍓嶇紑澶勭悊
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class TenantKeyPrefixHandler extends KeyPrefixHandler {
+
+    public TenantKeyPrefixHandler(String keyPrefix) {
+        super(keyPrefix);
+    }
+
+    /**
+     * 澧炲姞鍓嶇紑
+     */
+    @Override
+    public String map(String name) {
+        if (StringUtils.isBlank(name)) {
+            return null;
+        }
+        if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
+            return super.map(name);
+        }
+        String tenantId = TenantHelper.getTenantId();
+        if (StringUtils.isBlank(tenantId)) {
+            log.error("鏃犳硶鑾峰彇鏈夋晥鐨勭鎴穒d -> Null");
+        }
+        if (StringUtils.startsWith(name, tenantId + "")) {
+            // 濡傛灉瀛樺湪鍒欑洿鎺ヨ繑鍥�
+            return super.map(name);
+        }
+        return super.map(tenantId + ":" + name);
+    }
+
+    /**
+     * 鍘婚櫎鍓嶇紑
+     */
+    @Override
+    public String unmap(String name) {
+        String unmap = super.unmap(name);
+        if (StringUtils.isBlank(unmap)) {
+            return null;
+        }
+        if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
+            return super.unmap(name);
+        }
+        String tenantId = TenantHelper.getTenantId();
+        if (StringUtils.isBlank(tenantId)) {
+            log.error("鏃犳硶鑾峰彇鏈夋晥鐨勭鎴穒d -> Null");
+        }
+        if (StringUtils.startsWith(unmap, tenantId + "")) {
+            // 濡傛灉瀛樺湪鍒欏垹闄�
+            return unmap.substring((tenantId + ":").length());
+        }
+        return unmap;
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/helper/TenantHelper.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/helper/TenantHelper.java
new file mode 100644
index 0000000..e830c19
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/helper/TenantHelper.java
@@ -0,0 +1,189 @@
+package org.dromara.common.tenant.helper;
+
+import cn.dev33.satoken.context.SaHolder;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.hutool.core.convert.Convert;
+import com.alibaba.ttl.TransmittableThreadLocal;
+import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy;
+import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+
+import java.util.function.Supplier;
+
+/**
+ * 绉熸埛鍔╂墜
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class TenantHelper {
+
+    private static final String DYNAMIC_TENANT_KEY = GlobalConstants.GLOBAL_REDIS_KEY + "dynamicTenant";
+
+    private static final ThreadLocal<String> TEMP_DYNAMIC_TENANT = new TransmittableThreadLocal<>();
+
+    /**
+     * 绉熸埛鍔熻兘鏄惁鍚敤
+     */
+    public static boolean isEnable() {
+        return Convert.toBool(SpringUtils.getProperty("tenant.enable"), false);
+    }
+
+    /**
+     * 寮�鍚拷鐣ョ鎴�(寮�鍚悗闇�鎵嬪姩璋冪敤 {@link #disableIgnore()} 鍏抽棴)
+     */
+    public static void enableIgnore() {
+        InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build());
+    }
+
+    /**
+     * 鍏抽棴蹇界暐绉熸埛
+     */
+    public static void disableIgnore() {
+        InterceptorIgnoreHelper.clearIgnoreStrategy();
+    }
+
+    /**
+     * 鍦ㄥ拷鐣ョ鎴蜂腑鎵ц
+     *
+     * @param handle 澶勭悊鎵ц鏂规硶
+     */
+    public static void ignore(Runnable handle) {
+        enableIgnore();
+        try {
+            handle.run();
+        } finally {
+            disableIgnore();
+        }
+    }
+
+    /**
+     * 鍦ㄥ拷鐣ョ鎴蜂腑鎵ц
+     *
+     * @param handle 澶勭悊鎵ц鏂规硶
+     */
+    public static <T> T ignore(Supplier<T> handle) {
+        enableIgnore();
+        try {
+            return handle.get();
+        } finally {
+            disableIgnore();
+        }
+    }
+
+    /**
+     * 璁剧疆鍔ㄦ�佺鎴�(涓�鐩存湁鏁� 闇�瑕佹墜鍔ㄦ竻鐞�)
+     * <p>
+     * 濡傛灉涓烘湭鐧诲綍鐘舵�佷笅 閭d箞鍙湪褰撳墠绾跨▼鍐呯敓鏁�
+     */
+    public static void setDynamic(String tenantId) {
+        if (!isEnable()) {
+            return;
+        }
+        if (!isLogin()) {
+            TEMP_DYNAMIC_TENANT.set(tenantId);
+            return;
+        }
+        String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId();
+        RedisUtils.setCacheObject(cacheKey, tenantId);
+        SaHolder.getStorage().set(cacheKey, tenantId);
+    }
+
+    /**
+     * 鑾峰彇鍔ㄦ�佺鎴�(涓�鐩存湁鏁� 闇�瑕佹墜鍔ㄦ竻鐞�)
+     * <p>
+     * 濡傛灉涓烘湭鐧诲綍鐘舵�佷笅 閭d箞鍙湪褰撳墠绾跨▼鍐呯敓鏁�
+     */
+    public static String getDynamic() {
+        if (!isEnable()) {
+            return null;
+        }
+        if (!isLogin()) {
+            return TEMP_DYNAMIC_TENANT.get();
+        }
+        String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId();
+        String tenantId = (String) SaHolder.getStorage().get(cacheKey);
+        if (StringUtils.isNotBlank(tenantId)) {
+            return tenantId;
+        }
+        tenantId = RedisUtils.getCacheObject(cacheKey);
+        SaHolder.getStorage().set(cacheKey, tenantId);
+        return tenantId;
+    }
+
+    /**
+     * 娓呴櫎鍔ㄦ�佺鎴�
+     */
+    public static void clearDynamic() {
+        if (!isEnable()) {
+            return;
+        }
+        if (!isLogin()) {
+            TEMP_DYNAMIC_TENANT.remove();
+            return;
+        }
+        String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId();
+        RedisUtils.deleteObject(cacheKey);
+        SaHolder.getStorage().delete(cacheKey);
+    }
+
+    /**
+     * 鍦ㄥ姩鎬佺鎴蜂腑鎵ц
+     *
+     * @param handle 澶勭悊鎵ц鏂规硶
+     */
+    public static void dynamic(String tenantId, Runnable handle) {
+        setDynamic(tenantId);
+        try {
+            handle.run();
+        } finally {
+            clearDynamic();
+        }
+    }
+
+    /**
+     * 鍦ㄥ姩鎬佺鎴蜂腑鎵ц
+     *
+     * @param handle 澶勭悊鎵ц鏂规硶
+     */
+    public static <T> T dynamic(String tenantId, Supplier<T> handle) {
+        setDynamic(tenantId);
+        try {
+            return handle.get();
+        } finally {
+            clearDynamic();
+        }
+    }
+
+    /**
+     * 鑾峰彇褰撳墠绉熸埛id(鍔ㄦ�佺鎴蜂紭鍏�)
+     */
+    public static String getTenantId() {
+        if (!isEnable()) {
+            return null;
+        }
+        String tenantId = TenantHelper.getDynamic();
+        if (StringUtils.isBlank(tenantId)) {
+            tenantId = LoginHelper.getTenantId();
+        }
+        return tenantId;
+    }
+
+    private static boolean isLogin() {
+        try {
+            StpUtil.checkLogin();
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/manager/TenantSpringCacheManager.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/manager/TenantSpringCacheManager.java
new file mode 100644
index 0000000..d230afc
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/manager/TenantSpringCacheManager.java
@@ -0,0 +1,32 @@
+package org.dromara.common.tenant.manager;
+
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.redis.manager.PlusSpringCacheManager;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.springframework.cache.Cache;
+
+/**
+ * 閲嶅啓 cacheName 澶勭悊鏂规硶 鏀寔澶氱鎴�
+ *
+ * @author Lion Li
+ */
+public class TenantSpringCacheManager extends PlusSpringCacheManager {
+
+    public TenantSpringCacheManager() {
+    }
+
+    @Override
+    public Cache getCache(String name) {
+        if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
+            return super.getCache(name);
+        }
+        String tenantId = TenantHelper.getTenantId();
+        if (StringUtils.startsWith(name, tenantId)) {
+            // 濡傛灉瀛樺湪鍒欑洿鎺ヨ繑鍥�
+            return super.getCache(name);
+        }
+        return super.getCache(tenantId + ":" + name);
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/properties/TenantProperties.java b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/properties/TenantProperties.java
new file mode 100644
index 0000000..1675ccf
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-tenant/src/main/java/org/dromara/common/tenant/properties/TenantProperties.java
@@ -0,0 +1,27 @@
+package org.dromara.common.tenant.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.List;
+
+/**
+ * 绉熸埛 閰嶇疆灞炴��
+ *
+ * @author Lion Li
+ */
+@Data
+@ConfigurationProperties(prefix = "tenant")
+public class TenantProperties {
+
+    /**
+     * 鏄惁鍚敤
+     */
+    private Boolean enable;
+
+    /**
+     * 鎺掗櫎琛�
+     */
+    private List<String> excludes;
+
+}
diff --git a/ruoyi-common/ruoyi-common-tenant/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-tenant/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..c34f2c5
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-tenant/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.tenant.config.TenantConfiguration
diff --git a/ruoyi-common/ruoyi-common-translation/pom.xml b/ruoyi-common/ruoyi-common-translation/pom.xml
new file mode 100644
index 0000000..030dd7d
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-translation/pom.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-translation</artifactId>
+
+    <description>
+        ruoyi-common-translation 閫氱敤缈昏瘧鍔熻兘
+    </description>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-json</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-dict</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-dubbo</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-api-resource</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>
diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/annotation/Translation.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/annotation/Translation.java
new file mode 100644
index 0000000..6c1227f
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/annotation/Translation.java
@@ -0,0 +1,39 @@
+package org.dromara.common.translation.annotation;
+
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import org.dromara.common.translation.core.handler.TranslationHandler;
+
+import java.lang.annotation.*;
+
+/**
+ * 閫氱敤缈昏瘧娉ㄨВ
+ *
+ * @author Lion Li
+ */
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+@Documented
+@JacksonAnnotationsInside
+@JsonSerialize(using = TranslationHandler.class)
+public @interface Translation {
+
+    /**
+     * 绫诲瀷 (闇�涓庡疄鐜扮被涓婄殑 {@link org.dromara.common.translation.annotation.TranslationType} 娉ㄨВtype瀵瑰簲)
+     * <p>
+     * 榛樿鍙栧綋鍓嶅瓧娈电殑鍊� 濡傛灉璁剧疆浜� @{@link Translation#mapper()} 鍒欏彇鏄犲皠瀛楁鐨勫��
+     */
+    String type();
+
+    /**
+     * 鏄犲皠瀛楁 (濡傛灉涓嶄负绌哄垯鍙栨瀛楁鐨勫��)
+     */
+    String mapper() default "";
+
+    /**
+     * 鍏朵粬鏉′欢 渚嬪: 瀛楀吀type(sys_user_sex)
+     */
+    String other() default "";
+
+}
diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/annotation/TranslationType.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/annotation/TranslationType.java
new file mode 100644
index 0000000..43bfab0
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/annotation/TranslationType.java
@@ -0,0 +1,23 @@
+package org.dromara.common.translation.annotation;
+
+import org.dromara.common.translation.core.TranslationInterface;
+
+import java.lang.annotation.*;
+
+/**
+ * 缈昏瘧绫诲瀷娉ㄨВ (鏍囨敞鍒皗@link TranslationInterface} 鐨勫疄鐜扮被)
+ *
+ * @author Lion Li
+ */
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+@Documented
+public @interface TranslationType {
+
+    /**
+     * 绫诲瀷
+     */
+    String type();
+
+}
diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/config/TranslationConfig.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/config/TranslationConfig.java
new file mode 100644
index 0000000..5dcd0c1
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/config/TranslationConfig.java
@@ -0,0 +1,50 @@
+package org.dromara.common.translation.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.dromara.common.translation.annotation.TranslationType;
+import org.dromara.common.translation.core.TranslationInterface;
+import org.dromara.common.translation.core.handler.TranslationBeanSerializerModifier;
+import org.dromara.common.translation.core.handler.TranslationHandler;
+import jakarta.annotation.PostConstruct;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 缈昏瘧妯″潡閰嶇疆绫�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@AutoConfiguration
+public class TranslationConfig {
+
+    @Autowired
+    private List<TranslationInterface<?>> list;
+
+    @Autowired
+    private ObjectMapper objectMapper;
+
+    @PostConstruct
+    public void init() {
+        Map<String, TranslationInterface<?>> map = new HashMap<>(list.size());
+        for (TranslationInterface<?> trans : list) {
+            if (trans.getClass().isAnnotationPresent(TranslationType.class)) {
+                TranslationType annotation = trans.getClass().getAnnotation(TranslationType.class);
+                map.put(annotation.type(), trans);
+            } else {
+                log.warn(trans.getClass().getName() + " 缈昏瘧瀹炵幇绫绘湭鏍囨敞 TranslationType 娉ㄨВ!");
+            }
+        }
+        TranslationHandler.TRANSLATION_MAPPER.putAll(map);
+        // 璁剧疆 Bean 搴忓垪鍖栦慨鏀瑰櫒
+        objectMapper.setSerializerFactory(
+            objectMapper.getSerializerFactory()
+                .withSerializerModifier(new TranslationBeanSerializerModifier()));
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/constant/TransConstant.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/constant/TransConstant.java
new file mode 100644
index 0000000..a6ecb2d
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/constant/TransConstant.java
@@ -0,0 +1,36 @@
+package org.dromara.common.translation.constant;
+
+/**
+ * 缈昏瘧甯搁噺
+ *
+ * @author Lion Li
+ */
+public interface TransConstant {
+
+    /**
+     * 鐢ㄦ埛id杞处鍙�
+     */
+    String USER_ID_TO_NAME = "user_id_to_name";
+
+    /**
+     * 鐢ㄦ埛id杞敤鎴锋樀绉�
+     */
+    String USER_ID_TO_NICKNAME = "user_id_to_nickname";
+
+
+    /**
+     * 閮ㄩ棬id杞悕绉�
+     */
+    String DEPT_ID_TO_NAME = "dept_id_to_name";
+
+    /**
+     * 瀛楀吀type杞琹abel
+     */
+    String DICT_TYPE_TO_LABEL = "dict_type_to_label";
+
+    /**
+     * ossId杞瑄rl
+     */
+    String OSS_ID_TO_URL = "oss_id_to_url";
+
+}
diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/TranslationInterface.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/TranslationInterface.java
new file mode 100644
index 0000000..e4d6dd3
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/TranslationInterface.java
@@ -0,0 +1,20 @@
+package org.dromara.common.translation.core;
+
+import org.dromara.common.translation.annotation.TranslationType;
+
+/**
+ * 缈昏瘧鎺ュ彛 (瀹炵幇绫婚渶鏍囨敞 {@link TranslationType} 娉ㄨВ鏍囨槑缈昏瘧绫诲瀷)
+ *
+ * @author Lion Li
+ */
+public interface TranslationInterface<T> {
+
+    /**
+     * 缈昏瘧
+     *
+     * @param key   闇�瑕佽缈昏瘧鐨勯敭(涓嶄负绌�)
+     * @param other 鍏朵粬鍙傛暟
+     * @return 杩斿洖閿搴旂殑鍊�
+     */
+    T translation(Object key, String other);
+}
diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationBeanSerializerModifier.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationBeanSerializerModifier.java
new file mode 100644
index 0000000..727672f
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationBeanSerializerModifier.java
@@ -0,0 +1,29 @@
+package org.dromara.common.translation.core.handler;
+
+import com.fasterxml.jackson.databind.BeanDescription;
+import com.fasterxml.jackson.databind.SerializationConfig;
+import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
+import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
+
+import java.util.List;
+
+/**
+ * Bean 搴忓垪鍖栦慨鏀瑰櫒 瑙e喅 Null 琚崟鐙鐞嗛棶棰�
+ *
+ * @author Lion Li
+ */
+public class TranslationBeanSerializerModifier extends BeanSerializerModifier {
+
+    @Override
+    public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc,
+                                                     List<BeanPropertyWriter> beanProperties) {
+        for (BeanPropertyWriter writer : beanProperties) {
+            // 濡傛灉搴忓垪鍖栧櫒涓� TranslationHandler 鐨勮瘽 灏� Null 鍊间篃浜ょ粰浠栧鐞�
+            if (writer.getSerializer() instanceof TranslationHandler serializer) {
+                writer.assignNullSerializer(serializer);
+            }
+        }
+        return beanProperties;
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationHandler.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationHandler.java
new file mode 100644
index 0000000..bb9615b
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/handler/TranslationHandler.java
@@ -0,0 +1,65 @@
+package org.dromara.common.translation.core.handler;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.ContextualSerializer;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.core.utils.reflect.ReflectUtils;
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.common.translation.core.TranslationInterface;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 缈昏瘧澶勭悊鍣�
+ *
+ * @author Lion Li
+ */
+@Slf4j
+public class TranslationHandler extends JsonSerializer<Object> implements ContextualSerializer {
+
+    /**
+     * 鍏ㄥ眬缈昏瘧瀹炵幇绫绘槧灏勫櫒
+     */
+    public static final Map<String, TranslationInterface<?>> TRANSLATION_MAPPER = new ConcurrentHashMap<>();
+
+    private Translation translation;
+
+    @Override
+    public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+        TranslationInterface<?> trans = TRANSLATION_MAPPER.get(translation.type());
+        if (ObjectUtil.isNotNull(trans)) {
+            // 濡傛灉鏄犲皠瀛楁涓嶄负绌� 鍒欏彇鏄犲皠瀛楁鐨勫��
+            if (StringUtils.isNotBlank(translation.mapper())) {
+                value = ReflectUtils.invokeGetter(gen.getCurrentValue(), translation.mapper());
+            }
+            // 濡傛灉涓� null 鐩存帴鍐欏嚭
+            if (ObjectUtil.isNull(value)) {
+                gen.writeNull();
+                return;
+            }
+            Object result = trans.translation(value, translation.other());
+            gen.writeObject(result);
+        } else {
+            gen.writeObject(value);
+        }
+    }
+
+    @Override
+    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
+        Translation translation = property.getAnnotation(Translation.class);
+        if (Objects.nonNull(translation)) {
+            this.translation = translation;
+            return this;
+        }
+        return prov.findValueSerializer(property.getType(), property);
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DeptNameTranslationImpl.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DeptNameTranslationImpl.java
new file mode 100644
index 0000000..b88af57
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DeptNameTranslationImpl.java
@@ -0,0 +1,26 @@
+package org.dromara.common.translation.core.impl;
+
+import org.dromara.common.translation.annotation.TranslationType;
+import org.dromara.common.translation.constant.TransConstant;
+import org.dromara.common.translation.core.TranslationInterface;
+import org.dromara.system.api.RemoteDeptService;
+import lombok.AllArgsConstructor;
+import org.apache.dubbo.config.annotation.DubboReference;
+
+/**
+ * 閮ㄩ棬缈昏瘧瀹炵幇
+ *
+ * @author Lion Li
+ */
+@AllArgsConstructor
+@TranslationType(type = TransConstant.DEPT_ID_TO_NAME)
+public class DeptNameTranslationImpl implements TranslationInterface<String> {
+
+    @DubboReference
+    private RemoteDeptService remoteDeptService;
+
+    @Override
+    public String translation(Object key, String other) {
+        return remoteDeptService.selectDeptNameByIds(key.toString());
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DictTypeTranslationImpl.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DictTypeTranslationImpl.java
new file mode 100644
index 0000000..538d932
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/DictTypeTranslationImpl.java
@@ -0,0 +1,28 @@
+package org.dromara.common.translation.core.impl;
+
+import org.dromara.common.core.service.DictService;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.translation.annotation.TranslationType;
+import org.dromara.common.translation.constant.TransConstant;
+import org.dromara.common.translation.core.TranslationInterface;
+import lombok.AllArgsConstructor;
+
+/**
+ * 瀛楀吀缈昏瘧瀹炵幇
+ *
+ * @author Lion Li
+ */
+@AllArgsConstructor
+@TranslationType(type = TransConstant.DICT_TYPE_TO_LABEL)
+public class DictTypeTranslationImpl implements TranslationInterface<String> {
+
+    private final DictService dictService;
+
+    @Override
+    public String translation(Object key, String other) {
+        if (key instanceof String && StringUtils.isNotBlank(other)) {
+            return dictService.getDictLabel(other, key.toString());
+        }
+        return null;
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/NicknameTranslationImpl.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/NicknameTranslationImpl.java
new file mode 100644
index 0000000..b73478e
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/NicknameTranslationImpl.java
@@ -0,0 +1,26 @@
+package org.dromara.common.translation.core.impl;
+
+import lombok.AllArgsConstructor;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.common.translation.annotation.TranslationType;
+import org.dromara.common.translation.constant.TransConstant;
+import org.dromara.common.translation.core.TranslationInterface;
+import org.dromara.system.api.RemoteUserService;
+
+/**
+ * 鐢ㄦ埛鏄电О缈昏瘧瀹炵幇
+ *
+ * @author may
+ */
+@AllArgsConstructor
+@TranslationType(type = TransConstant.USER_ID_TO_NICKNAME)
+public class NicknameTranslationImpl implements TranslationInterface<String> {
+
+    @DubboReference
+    private RemoteUserService remoteUserService;
+
+    @Override
+    public String translation(Object key, String other) {
+        return remoteUserService.selectNicknameById((Long) key);
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/OssUrlTranslationImpl.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/OssUrlTranslationImpl.java
new file mode 100644
index 0000000..b9fe839
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/OssUrlTranslationImpl.java
@@ -0,0 +1,26 @@
+package org.dromara.common.translation.core.impl;
+
+import org.dromara.common.translation.annotation.TranslationType;
+import org.dromara.common.translation.constant.TransConstant;
+import org.dromara.common.translation.core.TranslationInterface;
+import org.dromara.resource.api.RemoteFileService;
+import lombok.AllArgsConstructor;
+import org.apache.dubbo.config.annotation.DubboReference;
+
+/**
+ * OSS缈昏瘧瀹炵幇
+ *
+ * @author Lion Li
+ */
+@AllArgsConstructor
+@TranslationType(type = TransConstant.OSS_ID_TO_URL)
+public class OssUrlTranslationImpl implements TranslationInterface<String> {
+
+    @DubboReference(mock = "true")
+    private RemoteFileService ossService;
+
+    @Override
+    public String translation(Object key, String other) {
+        return ossService.selectUrlByIds(key.toString());
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/UserNameTranslationImpl.java b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/UserNameTranslationImpl.java
new file mode 100644
index 0000000..c442302
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-translation/src/main/java/org/dromara/common/translation/core/impl/UserNameTranslationImpl.java
@@ -0,0 +1,26 @@
+package org.dromara.common.translation.core.impl;
+
+import org.dromara.common.translation.annotation.TranslationType;
+import org.dromara.common.translation.constant.TransConstant;
+import org.dromara.common.translation.core.TranslationInterface;
+import org.dromara.system.api.RemoteUserService;
+import lombok.AllArgsConstructor;
+import org.apache.dubbo.config.annotation.DubboReference;
+
+/**
+ * 鐢ㄦ埛鍚嶇炕璇戝疄鐜�
+ *
+ * @author Lion Li
+ */
+@AllArgsConstructor
+@TranslationType(type = TransConstant.USER_ID_TO_NAME)
+public class UserNameTranslationImpl implements TranslationInterface<String> {
+
+    @DubboReference
+    private RemoteUserService remoteUserService;
+
+    @Override
+    public String translation(Object key, String other) {
+        return remoteUserService.selectUserNameById((Long) key);
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-translation/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-translation/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..ad40205
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-translation/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1,6 @@
+org.dromara.common.translation.config.TranslationConfig
+org.dromara.common.translation.core.impl.DeptNameTranslationImpl
+org.dromara.common.translation.core.impl.DictTypeTranslationImpl
+org.dromara.common.translation.core.impl.OssUrlTranslationImpl
+org.dromara.common.translation.core.impl.UserNameTranslationImpl
+org.dromara.common.translation.core.impl.NicknameTranslationImpl
diff --git a/ruoyi-common/ruoyi-common-web/pom.xml b/ruoyi-common/ruoyi-common-web/pom.xml
new file mode 100644
index 0000000..5ef7e92
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-web/pom.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-web</artifactId>
+
+    <description>
+        ruoyi-common-web web鏈嶅姟
+    </description>
+
+	<dependencies>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-core</artifactId>
+        </dependency>
+
+        <!-- SpringBoot Web瀹瑰櫒 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+            <exclusions>
+                <exclusion>
+                    <artifactId>spring-boot-starter-tomcat</artifactId>
+                    <groupId>org.springframework.boot</groupId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <!-- web 瀹瑰櫒浣跨敤 undertow 鎬ц兘鏇村己 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-undertow</artifactId>
+        </dependency>
+
+        <!-- SpringBoot Actuator -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>net.dreamlu</groupId>
+            <artifactId>mica-metrics</artifactId>
+            <version>2.7.6</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>net.dreamlu</groupId>
+                    <artifactId>mica-core</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <dependency>
+            <groupId>net.dreamlu</groupId>
+            <artifactId>mica-core</artifactId>
+            <version>2.7.6</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/I18nConfig.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/I18nConfig.java
new file mode 100644
index 0000000..4e212cb
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/I18nConfig.java
@@ -0,0 +1,22 @@
+package org.dromara.common.web.config;
+
+import org.dromara.common.web.core.I18nLocaleResolver;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.web.servlet.LocaleResolver;
+
+/**
+ * 鍥介檯鍖栭厤缃�
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration(before = WebMvcAutoConfiguration.class)
+public class I18nConfig {
+
+    @Bean
+    public LocaleResolver localeResolver() {
+        return new I18nLocaleResolver();
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/UndertowConfig.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/UndertowConfig.java
new file mode 100644
index 0000000..421ce6d
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/config/UndertowConfig.java
@@ -0,0 +1,30 @@
+package org.dromara.common.web.config;
+
+import io.undertow.server.DefaultByteBufferPool;
+import io.undertow.websockets.jsr.WebSocketDeploymentInfo;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
+import org.springframework.boot.web.server.WebServerFactoryCustomizer;
+
+/**
+ * Undertow 鑷畾涔夐厤缃�
+ *
+ * @author Lion Li
+ */
+@AutoConfiguration
+public class UndertowConfig implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
+
+    /**
+     * 璁剧疆 Undertow 鐨� websocket 缂撳啿姹�
+     */
+    @Override
+    public void customize(UndertowServletWebServerFactory factory) {
+        // 榛樿涓嶇洿鎺ュ垎閰嶅唴瀛� 濡傛灉椤圭洰涓娇鐢ㄤ簡 websocket 寤鸿鐩存帴鍒嗛厤
+        factory.addDeploymentInfoCustomizers(deploymentInfo -> {
+            WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo();
+            webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(false, 512));
+            deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo);
+        });
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/core/BaseController.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/core/BaseController.java
new file mode 100644
index 0000000..2808fa8
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/core/BaseController.java
@@ -0,0 +1,32 @@
+package org.dromara.common.web.core;
+
+import org.dromara.common.core.domain.R;
+
+/**
+ * web灞傞�氱敤鏁版嵁澶勭悊
+ *
+ * @author Lion Li
+ */
+public class BaseController {
+
+    /**
+     * 鍝嶅簲杩斿洖缁撴灉
+     *
+     * @param rows 褰卞搷琛屾暟
+     * @return 鎿嶄綔缁撴灉
+     */
+    protected R<Void> toAjax(int rows) {
+        return rows > 0 ? R.ok() : R.fail();
+    }
+
+    /**
+     * 鍝嶅簲杩斿洖缁撴灉
+     *
+     * @param result 缁撴灉
+     * @return 鎿嶄綔缁撴灉
+     */
+    protected R<Void> toAjax(boolean result) {
+        return result ? R.ok() : R.fail();
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/core/I18nLocaleResolver.java b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/core/I18nLocaleResolver.java
new file mode 100644
index 0000000..98ddd06
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-web/src/main/java/org/dromara/common/web/core/I18nLocaleResolver.java
@@ -0,0 +1,31 @@
+package org.dromara.common.web.core;
+
+import org.springframework.web.servlet.LocaleResolver;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.util.Locale;
+
+/**
+ * 鑾峰彇璇锋眰澶村浗闄呭寲淇℃伅
+ *
+ * @author Lion Li
+ */
+public class I18nLocaleResolver implements LocaleResolver {
+
+    @Override
+    public Locale resolveLocale(HttpServletRequest httpServletRequest) {
+        String language = httpServletRequest.getHeader("content-language");
+        Locale locale = Locale.getDefault();
+        if (language != null && language.length() > 0) {
+            String[] split = language.split("_");
+            locale = new Locale(split[0], split[1]);
+        }
+        return locale;
+    }
+
+    @Override
+    public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
+
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..cdad79a
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1,2 @@
+org.dromara.common.web.config.I18nConfig
+org.dromara.common.web.config.UndertowConfig
diff --git a/ruoyi-common/ruoyi-common-web/src/main/resources/logback-common.xml b/ruoyi-common/ruoyi-common-web/src/main/resources/logback-common.xml
new file mode 100644
index 0000000..89eaa97
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-web/src/main/resources/logback-common.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<included>
+
+    <property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"/>
+
+    <!-- 鎺у埗鍙拌緭鍑� -->
+    <appender name="file_console" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${log.path}/console.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 鏃ュ織鏂囦欢鍚嶆牸寮� -->
+            <fileNamePattern>${log.path}/console.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <!-- 鏃ュ織鏈�澶� 1澶� -->
+            <maxHistory>1</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+            <charset>utf-8</charset>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <!-- 杩囨护鐨勭骇鍒� -->
+            <level>INFO</level>
+        </filter>
+    </appender>
+
+    <!-- 绯荤粺鏃ュ織杈撳嚭 -->
+    <appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${log.path}/info.log</file>
+        <!-- 寰幆鏀跨瓥锛氬熀浜庢椂闂村垱寤烘棩蹇楁枃浠� -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 鏃ュ織鏂囦欢鍚嶆牸寮� -->
+            <fileNamePattern>${log.path}/info.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <!-- 鏃ュ織鏈�澶х殑鍘嗗彶 60澶� -->
+            <maxHistory>60</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 杩囨护鐨勭骇鍒� -->
+            <level>INFO</level>
+            <!-- 鍖归厤鏃剁殑鎿嶄綔锛氭帴鏀讹紙璁板綍锛� -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 涓嶅尮閰嶆椂鐨勬搷浣滐細鎷掔粷锛堜笉璁板綍锛� -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${log.path}/error.log</file>
+        <!-- 寰幆鏀跨瓥锛氬熀浜庢椂闂村垱寤烘棩蹇楁枃浠� -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <!-- 鏃ュ織鏂囦欢鍚嶆牸寮� -->
+            <fileNamePattern>${log.path}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
+            <!-- 鏃ュ織鏈�澶х殑鍘嗗彶 60澶� -->
+            <maxHistory>60</maxHistory>
+        </rollingPolicy>
+        <encoder>
+            <pattern>${log.pattern}</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <!-- 杩囨护鐨勭骇鍒� -->
+            <level>ERROR</level>
+            <!-- 鍖归厤鏃剁殑鎿嶄綔锛氭帴鏀讹紙璁板綍锛� -->
+            <onMatch>ACCEPT</onMatch>
+            <!-- 涓嶅尮閰嶆椂鐨勬搷浣滐細鎷掔粷锛堜笉璁板綍锛� -->
+            <onMismatch>DENY</onMismatch>
+        </filter>
+    </appender>
+
+    <!-- info寮傛杈撳嚭 -->
+    <appender name="async_info" class="ch.qos.logback.classic.AsyncAppender">
+        <!-- 涓嶄涪澶辨棩蹇�.榛樿鐨�,濡傛灉闃熷垪鐨�80%宸叉弧,鍒欎細涓㈠純TRACT銆丏EBUG銆両NFO绾у埆鐨勬棩蹇� -->
+        <discardingThreshold>0</discardingThreshold>
+        <!-- 鏇存敼榛樿鐨勯槦鍒楃殑娣卞害,璇ュ�间細褰卞搷鎬ц兘.榛樿鍊间负256 -->
+        <queueSize>512</queueSize>
+        <!-- 娣诲姞闄勫姞鐨刟ppender,鏈�澶氬彧鑳芥坊鍔犱竴涓� -->
+        <appender-ref ref="file_info"/>
+    </appender>
+
+    <!-- error寮傛杈撳嚭 -->
+    <appender name="async_error" class="ch.qos.logback.classic.AsyncAppender">
+        <!-- 涓嶄涪澶辨棩蹇�.榛樿鐨�,濡傛灉闃熷垪鐨�80%宸叉弧,鍒欎細涓㈠純TRACT銆丏EBUG銆両NFO绾у埆鐨勬棩蹇� -->
+        <discardingThreshold>0</discardingThreshold>
+        <!-- 鏇存敼榛樿鐨勯槦鍒楃殑娣卞害,璇ュ�间細褰卞搷鎬ц兘.榛樿鍊间负256 -->
+        <queueSize>512</queueSize>
+        <!-- 娣诲姞闄勫姞鐨刟ppender,鏈�澶氬彧鑳芥坊鍔犱竴涓� -->
+        <appender-ref ref="file_error"/>
+    </appender>
+
+    <!--绯荤粺鎿嶄綔鏃ュ織-->
+    <root level="info">
+        <appender-ref ref="async_info"/>
+        <appender-ref ref="async_error"/>
+        <appender-ref ref="file_console"/>
+    </root>
+</included>
diff --git a/ruoyi-common/ruoyi-common-websocket/pom.xml b/ruoyi-common/ruoyi-common-websocket/pom.xml
new file mode 100644
index 0000000..db86dcb
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-websocket/pom.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>ruoyi-common-websocket</artifactId>
+
+    <description>
+        ruoyi-common-websocket 妯″潡
+    </description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-redis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-satoken</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-json</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/config/WebSocketConfig.java b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/config/WebSocketConfig.java
new file mode 100644
index 0000000..30d109e
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/config/WebSocketConfig.java
@@ -0,0 +1,60 @@
+package org.dromara.common.websocket.config;
+
+import cn.hutool.core.util.StrUtil;
+import org.dromara.common.websocket.config.properties.WebSocketProperties;
+import org.dromara.common.websocket.handler.PlusWebSocketHandler;
+import org.dromara.common.websocket.interceptor.PlusWebSocketInterceptor;
+import org.dromara.common.websocket.listener.WebSocketTopicListener;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+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.config.annotation.EnableWebSocket;
+import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
+import org.springframework.web.socket.server.HandshakeInterceptor;
+
+/**
+ * WebSocket 閰嶇疆
+ *
+ * @author zendwang
+ */
+@AutoConfiguration
+@ConditionalOnProperty(value = "websocket.enabled", havingValue = "true")
+@EnableConfigurationProperties(WebSocketProperties.class)
+@EnableWebSocket
+public class WebSocketConfig {
+
+    @Bean
+    public WebSocketConfigurer webSocketConfigurer(HandshakeInterceptor handshakeInterceptor,
+                                                   WebSocketHandler webSocketHandler,
+                                                   WebSocketProperties webSocketProperties) {
+        if (StrUtil.isBlank(webSocketProperties.getPath())) {
+            webSocketProperties.setPath("/websocket");
+        }
+
+        if (StrUtil.isBlank(webSocketProperties.getAllowedOrigins())) {
+            webSocketProperties.setAllowedOrigins("*");
+        }
+
+        return registry -> registry
+            .addHandler(webSocketHandler, webSocketProperties.getPath())
+            .addInterceptors(handshakeInterceptor)
+            .setAllowedOrigins(webSocketProperties.getAllowedOrigins());
+    }
+
+    @Bean
+    public HandshakeInterceptor handshakeInterceptor() {
+        return new PlusWebSocketInterceptor();
+    }
+
+    @Bean
+    public WebSocketHandler webSocketHandler() {
+        return new PlusWebSocketHandler();
+    }
+
+    @Bean
+    public WebSocketTopicListener topicListener() {
+        return new WebSocketTopicListener();
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/config/properties/WebSocketProperties.java b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/config/properties/WebSocketProperties.java
new file mode 100644
index 0000000..d629fe5
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/config/properties/WebSocketProperties.java
@@ -0,0 +1,26 @@
+package org.dromara.common.websocket.config.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * WebSocket 閰嶇疆椤�
+ *
+ * @author zendwang
+ */
+@ConfigurationProperties("websocket")
+@Data
+public class WebSocketProperties {
+
+    private Boolean enabled;
+
+    /**
+     * 璺緞
+     */
+    private String path;
+
+    /**
+     *  璁剧疆璁块棶婧愬湴鍧�
+     */
+    private String allowedOrigins;
+}
diff --git a/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/constant/WebSocketConstants.java b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/constant/WebSocketConstants.java
new file mode 100644
index 0000000..54eb447
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/constant/WebSocketConstants.java
@@ -0,0 +1,28 @@
+package org.dromara.common.websocket.constant;
+
+/**
+ * websocket鐨勫父閲忛厤缃�
+ *
+ * @author zendwang
+ */
+public interface WebSocketConstants {
+    /**
+     * websocketSession涓殑鍙傛暟鐨刱ey
+     */
+    String LOGIN_USER_KEY = "loginUser";
+
+    /**
+     * 璁㈤槄鐨勯閬�
+     */
+    String WEB_SOCKET_TOPIC = "global:websocket";
+
+    /**
+     * 鍓嶇蹇冭烦妫�鏌ョ殑鍛戒护
+     */
+    String PING = "ping";
+
+    /**
+     * 鏈嶅姟绔績璺虫仮澶嶇殑瀛楃涓�
+     */
+    String PONG = "pong";
+}
diff --git a/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/dto/WebSocketMessageDto.java b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/dto/WebSocketMessageDto.java
new file mode 100644
index 0000000..e2d4456
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/dto/WebSocketMessageDto.java
@@ -0,0 +1,29 @@
+package org.dromara.common.websocket.dto;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 娑堟伅鐨刣to
+ *
+ * @author zendwang
+ */
+@Data
+public class WebSocketMessageDto implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 闇�瑕佹帹閫佸埌鐨剆ession key 鍒楄〃
+     */
+    private List<Long> sessionKeys;
+
+    /**
+     * 闇�瑕佸彂閫佺殑娑堟伅
+     */
+    private String message;
+}
diff --git a/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/handler/PlusWebSocketHandler.java b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/handler/PlusWebSocketHandler.java
new file mode 100644
index 0000000..b92f0cf
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/handler/PlusWebSocketHandler.java
@@ -0,0 +1,102 @@
+package org.dromara.common.websocket.handler;
+
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.websocket.dto.WebSocketMessageDto;
+import org.dromara.common.websocket.holder.WebSocketSessionHolder;
+import org.dromara.common.websocket.utils.WebSocketUtils;
+import org.dromara.system.api.model.LoginUser;
+import org.springframework.web.socket.*;
+import org.springframework.web.socket.handler.AbstractWebSocketHandler;
+
+import java.util.List;
+
+import static org.dromara.common.websocket.constant.WebSocketConstants.LOGIN_USER_KEY;
+
+/**
+ * WebSocketHandler 瀹炵幇绫�
+ *
+ * @author zendwang
+ */
+@Slf4j
+public class PlusWebSocketHandler extends AbstractWebSocketHandler {
+
+    /**
+     * 杩炴帴鎴愬姛鍚�
+     */
+    @Override
+    public void afterConnectionEstablished(WebSocketSession session) {
+        LoginUser loginUser = (LoginUser) session.getAttributes().get(LOGIN_USER_KEY);
+        WebSocketSessionHolder.addSession(loginUser.getUserId(), session);
+        log.info("[connect] sessionId: {},userId:{},userType:{}", session.getId(), loginUser.getUserId(), loginUser.getUserType());
+    }
+
+    /**
+     * 澶勭悊鍙戦�佹潵鐨勬枃鏈秷鎭�
+     *
+     * @param session
+     * @param message
+     * @throws Exception
+     */
+    @Override
+    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
+        LoginUser loginUser = (LoginUser) session.getAttributes().get(LOGIN_USER_KEY);
+        List<Long> userIds = List.of(loginUser.getUserId());
+        WebSocketMessageDto webSocketMessageDto = new WebSocketMessageDto();
+        webSocketMessageDto.setSessionKeys(userIds);
+        webSocketMessageDto.setMessage(message.getPayload());
+        WebSocketUtils.publishMessage(webSocketMessageDto);
+    }
+
+    @Override
+    protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
+        super.handleBinaryMessage(session, message);
+    }
+
+    /**
+     * 蹇冭烦鐩戞祴鐨勫洖澶�
+     *
+     * @param session
+     * @param message
+     * @throws Exception
+     */
+    @Override
+    protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
+        WebSocketUtils.sendPongMessage(session);
+    }
+
+    /**
+     * 杩炴帴鍑洪敊鏃�
+     *
+     * @param session
+     * @param exception
+     * @throws Exception
+     */
+    @Override
+    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
+        log.error("[transport error] sessionId: {} , exception:{}", session.getId(), exception.getMessage());
+    }
+
+    /**
+     * 杩炴帴鍏抽棴鍚�
+     *
+     * @param session
+     * @param status
+     */
+    @Override
+    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
+        LoginUser loginUser = (LoginUser) session.getAttributes().get(LOGIN_USER_KEY);
+        WebSocketSessionHolder.removeSession(loginUser.getUserId());
+        log.info("[disconnect] sessionId: {},userId:{},userType:{}", session.getId(), loginUser.getUserId(), loginUser.getUserType());
+    }
+
+    /**
+     * 鏄惁鏀寔鍒嗙墖娑堟伅
+     *
+     * @return
+     */
+    @Override
+    public boolean supportsPartialMessages() {
+        return false;
+    }
+
+}
diff --git a/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/holder/WebSocketSessionHolder.java b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/holder/WebSocketSessionHolder.java
new file mode 100644
index 0000000..de8c5a7
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/holder/WebSocketSessionHolder.java
@@ -0,0 +1,42 @@
+package org.dromara.common.websocket.holder;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.springframework.web.socket.WebSocketSession;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * WebSocketSession 鐢ㄤ簬淇濆瓨褰撳墠鎵�鏈夊湪绾跨殑浼氳瘽淇℃伅
+ *
+ * @author zendwang
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class WebSocketSessionHolder {
+
+    private static final Map<Long, WebSocketSession> USER_SESSION_MAP = new ConcurrentHashMap<>();
+
+    public static void addSession(Long sessionKey, WebSocketSession session) {
+        USER_SESSION_MAP.put(sessionKey, session);
+    }
+
+    public static void removeSession(Long sessionKey) {
+        if (USER_SESSION_MAP.containsKey(sessionKey)) {
+            USER_SESSION_MAP.remove(sessionKey);
+        }
+    }
+
+    public static WebSocketSession getSessions(Long sessionKey) {
+        return USER_SESSION_MAP.get(sessionKey);
+    }
+
+    public static Set<Long> getSessionsAll() {
+        return USER_SESSION_MAP.keySet();
+    }
+
+    public static Boolean existSession(Long sessionKey) {
+        return USER_SESSION_MAP.containsKey(sessionKey);
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/interceptor/PlusWebSocketInterceptor.java b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/interceptor/PlusWebSocketInterceptor.java
new file mode 100644
index 0000000..fda4b59
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/interceptor/PlusWebSocketInterceptor.java
@@ -0,0 +1,51 @@
+package org.dromara.common.websocket.interceptor;
+
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.system.api.model.LoginUser;
+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;
+
+import static org.dromara.common.websocket.constant.WebSocketConstants.LOGIN_USER_KEY;
+
+/**
+ * WebSocket鎻℃墜璇锋眰鐨勬嫤鎴櫒
+ *
+ * @author zendwang
+ */
+@Slf4j
+public class PlusWebSocketInterceptor implements HandshakeInterceptor {
+
+    /**
+     * 鎻℃墜鍓�
+     *
+     * @param request    request
+     * @param response   response
+     * @param wsHandler  wsHandler
+     * @param attributes attributes
+     * @return 鏄惁鎻℃墜鎴愬姛
+     */
+    @Override
+    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) {
+        LoginUser loginUser = LoginHelper.getLoginUser();
+        attributes.put(LOGIN_USER_KEY, loginUser);
+        return true;
+    }
+
+    /**
+     * 鎻℃墜鍚�
+     *
+     * @param request   request
+     * @param response  response
+     * @param wsHandler wsHandler
+     * @param exception 寮傚父
+     */
+    @Override
+    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
+
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/listener/WebSocketTopicListener.java b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/listener/WebSocketTopicListener.java
new file mode 100644
index 0000000..01528d0
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/listener/WebSocketTopicListener.java
@@ -0,0 +1,43 @@
+package org.dromara.common.websocket.listener;
+
+import cn.hutool.core.collection.CollUtil;
+import org.dromara.common.websocket.holder.WebSocketSessionHolder;
+import org.dromara.common.websocket.utils.WebSocketUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.core.Ordered;
+
+/**
+ * WebSocket 涓婚璁㈤槄鐩戝惉鍣�
+ *
+ * @author zendwang
+ */
+@Slf4j
+public class WebSocketTopicListener implements ApplicationRunner, Ordered {
+
+    @Override
+    public void run(ApplicationArguments args) throws Exception {
+        WebSocketUtils.subscribeMessage((message) -> {
+            log.info("WebSocket涓婚璁㈤槄鏀跺埌娑堟伅session keys={} message={}", message.getSessionKeys(), message.getMessage());
+            // 濡傛灉key涓嶄负绌哄氨鎸夌収key鍙戞秷鎭� 濡傛灉涓虹┖灏辩兢鍙�
+            if (CollUtil.isNotEmpty(message.getSessionKeys())) {
+                message.getSessionKeys().forEach(key -> {
+                    if (WebSocketSessionHolder.existSession(key)) {
+                        WebSocketUtils.sendMessage(key, message.getMessage());
+                    }
+                });
+            } else {
+                WebSocketSessionHolder.getSessionsAll().forEach(key -> {
+                    WebSocketUtils.sendMessage(key, message.getMessage());
+                });
+            }
+        });
+        log.info("鍒濆鍖朩ebSocket涓婚璁㈤槄鐩戝惉鍣ㄦ垚鍔�");
+    }
+
+    @Override
+    public int getOrder() {
+        return -1;
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/utils/WebSocketUtils.java b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/utils/WebSocketUtils.java
new file mode 100644
index 0000000..99bb845
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-websocket/src/main/java/org/dromara/common/websocket/utils/WebSocketUtils.java
@@ -0,0 +1,110 @@
+package org.dromara.common.websocket.utils;
+
+import cn.hutool.core.collection.CollUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.common.websocket.dto.WebSocketMessageDto;
+import org.dromara.common.websocket.holder.WebSocketSessionHolder;
+import org.springframework.web.socket.PongMessage;
+import org.springframework.web.socket.TextMessage;
+import org.springframework.web.socket.WebSocketMessage;
+import org.springframework.web.socket.WebSocketSession;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+import static org.dromara.common.websocket.constant.WebSocketConstants.WEB_SOCKET_TOPIC;
+
+/**
+ * 宸ュ叿绫�
+ *
+ * @author zendwang
+ */
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class WebSocketUtils {
+
+    /**
+     * 鍙戦�佹秷鎭�
+     *
+     * @param sessionKey session涓婚敭 涓�鑸负鐢ㄦ埛id
+     * @param message    娑堟伅鏂囨湰
+     */
+    public static void sendMessage(Long sessionKey, String message) {
+        WebSocketSession session = WebSocketSessionHolder.getSessions(sessionKey);
+        sendMessage(session, message);
+    }
+
+    /**
+     * 璁㈤槄娑堟伅
+     *
+     * @param consumer 鑷畾涔夊鐞�
+     */
+    public static void subscribeMessage(Consumer<WebSocketMessageDto> consumer) {
+        RedisUtils.subscribe(WEB_SOCKET_TOPIC, WebSocketMessageDto.class, consumer);
+    }
+
+    /**
+     * 鍙戝竷璁㈤槄鐨勬秷鎭�
+     *
+     * @param webSocketMessage 娑堟伅瀵硅薄
+     */
+    public static void publishMessage(WebSocketMessageDto webSocketMessage) {
+        List<Long> unsentSessionKeys = new ArrayList<>();
+        // 褰撳墠鏈嶅姟鍐卻ession,鐩存帴鍙戦�佹秷鎭�
+        for (Long sessionKey : webSocketMessage.getSessionKeys()) {
+            if (WebSocketSessionHolder.existSession(sessionKey)) {
+                WebSocketUtils.sendMessage(sessionKey, webSocketMessage.getMessage());
+                continue;
+            }
+            unsentSessionKeys.add(sessionKey);
+        }
+        // 涓嶅湪褰撳墠鏈嶅姟鍐卻ession,鍙戝竷璁㈤槄娑堟伅
+        if (CollUtil.isNotEmpty(unsentSessionKeys)) {
+            WebSocketMessageDto broadcastMessage = new WebSocketMessageDto();
+            broadcastMessage.setMessage(webSocketMessage.getMessage());
+            broadcastMessage.setSessionKeys(unsentSessionKeys);
+            RedisUtils.publish(WEB_SOCKET_TOPIC, broadcastMessage, consumer -> {
+                log.info("WebSocket鍙戦�佷富棰樿闃呮秷鎭痶opic:{} session keys:{} message:{}",
+                    WEB_SOCKET_TOPIC, unsentSessionKeys, webSocketMessage.getMessage());
+            });
+        }
+    }
+
+    /**
+     * 鍙戝竷璁㈤槄鐨勬秷鎭�(缇ゅ彂)
+     *
+     * @param message 娑堟伅鍐呭
+     */
+    public static void publishAll(String message) {
+        WebSocketMessageDto broadcastMessage = new WebSocketMessageDto();
+        broadcastMessage.setMessage(message);
+        RedisUtils.publish(WEB_SOCKET_TOPIC, broadcastMessage, consumer -> {
+            log.info("WebSocket鍙戦�佷富棰樿闃呮秷鎭痶opic:{} message:{}", WEB_SOCKET_TOPIC, message);
+        });
+    }
+
+    public static void sendPongMessage(WebSocketSession session) {
+        sendMessage(session, new PongMessage());
+    }
+
+    public static void sendMessage(WebSocketSession session, String message) {
+        sendMessage(session, new TextMessage(message));
+    }
+
+    private static void sendMessage(WebSocketSession session, WebSocketMessage<?> message) {
+        if (session == null || !session.isOpen()) {
+            log.warn("[send] session浼氳瘽宸茬粡鍏抽棴");
+        } else {
+            try {
+                session.sendMessage(message);
+            } catch (IOException e) {
+                log.error("[send] session({}) 鍙戦�佹秷鎭�({}) 寮傚父", session, message, e);
+            }
+        }
+    }
+}
diff --git a/ruoyi-common/ruoyi-common-websocket/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-websocket/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..c3a7305
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-websocket/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.websocket.config.WebSocketConfig

--
Gitblit v1.9.1