lwh 3 settimane fa
parent
commit
aff9ff3c32
100 ha cambiato i file con 9709 aggiunte e 0 eliminazioni
  1. 6 0
      .editorconfig
  2. 61 0
      .gitignore
  3. 202 0
      LICENSE
  4. 13 0
      db/Dockerfile
  5. 1100 0
      db/pig.sql
  6. 325 0
      db/pig_config.sql
  7. 129 0
      docker-compose.yml
  8. 13 0
      pig-auth/Dockerfile
  9. 114 0
      pig-auth/pom.xml
  10. 39 0
      pig-auth/src/main/java/com/pig4cloud/pig/auth/PigAuthApplication.java
  11. 169 0
      pig-auth/src/main/java/com/pig4cloud/pig/auth/config/AuthorizationServerConfiguration.java
  12. 53 0
      pig-auth/src/main/java/com/pig4cloud/pig/auth/endpoint/ImageCodeEndpoint.java
  13. 273 0
      pig-auth/src/main/java/com/pig4cloud/pig/auth/endpoint/PigTokenEndpoint.java
  14. 152 0
      pig-auth/src/main/java/com/pig4cloud/pig/auth/support/CustomeOAuth2AccessTokenGenerator.java
  15. 104 0
      pig-auth/src/main/java/com/pig4cloud/pig/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationConverter.java
  16. 301 0
      pig-auth/src/main/java/com/pig4cloud/pig/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationProvider.java
  17. 65 0
      pig-auth/src/main/java/com/pig4cloud/pig/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationToken.java
  18. 4 0
      pig-auth/src/main/java/com/pig4cloud/pig/auth/support/base/package-info.java
  19. 38 0
      pig-auth/src/main/java/com/pig4cloud/pig/auth/support/core/CustomeOAuth2TokenCustomizer.java
  20. 33 0
      pig-auth/src/main/java/com/pig4cloud/pig/auth/support/core/FormIdentityLoginConfigurer.java
  21. 226 0
      pig-auth/src/main/java/com/pig4cloud/pig/auth/support/core/PigDaoAuthenticationProvider.java
  22. 42 0
      pig-auth/src/main/java/com/pig4cloud/pig/auth/support/filter/AuthSecurityConfigProperties.java
  23. 102 0
      pig-auth/src/main/java/com/pig4cloud/pig/auth/support/filter/PasswordDecoderFilter.java
  24. 125 0
      pig-auth/src/main/java/com/pig4cloud/pig/auth/support/filter/ValidateCodeFilter.java
  25. 68 0
      pig-auth/src/main/java/com/pig4cloud/pig/auth/support/handler/FormAuthenticationFailureHandler.java
  26. 109 0
      pig-auth/src/main/java/com/pig4cloud/pig/auth/support/handler/PigAuthenticationFailureEventHandler.java
  27. 129 0
      pig-auth/src/main/java/com/pig4cloud/pig/auth/support/handler/PigAuthenticationSuccessEventHandler.java
  28. 88 0
      pig-auth/src/main/java/com/pig4cloud/pig/auth/support/handler/PigLogoutSuccessEventHandler.java
  29. 48 0
      pig-auth/src/main/java/com/pig4cloud/pig/auth/support/handler/SsoLogoutSuccessHandler.java
  30. 71 0
      pig-auth/src/main/java/com/pig4cloud/pig/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationConverter.java
  31. 82 0
      pig-auth/src/main/java/com/pig4cloud/pig/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationProvider.java
  32. 35 0
      pig-auth/src/main/java/com/pig4cloud/pig/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationToken.java
  33. 4 0
      pig-auth/src/main/java/com/pig4cloud/pig/auth/support/password/package-info.java
  34. 57 0
      pig-auth/src/main/java/com/pig4cloud/pig/auth/support/sms/OAuth2ResourceOwnerSmsAuthenticationConverter.java
  35. 66 0
      pig-auth/src/main/java/com/pig4cloud/pig/auth/support/sms/OAuth2ResourceOwnerSmsAuthenticationProvider.java
  36. 21 0
      pig-auth/src/main/java/com/pig4cloud/pig/auth/support/sms/OAuth2ResourceOwnerSmsAuthenticationToken.java
  37. 4 0
      pig-auth/src/main/java/com/pig4cloud/pig/auth/support/sms/package-info.java
  38. 21 0
      pig-auth/src/main/resources/application.yml
  39. 78 0
      pig-auth/src/main/resources/logback-spring.xml
  40. 52 0
      pig-auth/src/main/resources/templates/ftl/confirm.ftl
  41. 117 0
      pig-auth/src/main/resources/templates/ftl/layout/base.ftl
  42. 41 0
      pig-auth/src/main/resources/templates/ftl/login.ftl
  43. 236 0
      pig-common/pig-common-bom/pom.xml
  44. 69 0
      pig-common/pig-common-core/pom.xml
  45. 65 0
      pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/config/JacksonConfiguration.java
  46. 107 0
      pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/config/RedisTemplateConfiguration.java
  47. 57 0
      pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/config/RestTemplateConfiguration.java
  48. 67 0
      pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/config/WebMvcConfiguration.java
  49. 67 0
      pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/CacheConstants.java
  50. 95 0
      pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/CommonConstants.java
  51. 135 0
      pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/SecurityConstants.java
  52. 35 0
      pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/ServiceNameConstants.java
  53. 52 0
      pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/enums/DictTypeEnum.java
  54. 50 0
      pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/enums/LoginTypeEnum.java
  55. 57 0
      pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/enums/MenuTypeEnum.java
  56. 48 0
      pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/exception/CheckedException.java
  57. 106 0
      pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/exception/ErrorCodes.java
  58. 48 0
      pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/exception/PigDeniedException.java
  59. 36 0
      pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/exception/ValidateCodeException.java
  60. 58 0
      pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/factory/YamlPropertySourceFactory.java
  61. 69 0
      pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/jackson/PigJavaTimeModule.java
  62. 149 0
      pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/servlet/RepeatBodyRequestWrapper.java
  63. 109 0
      pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/util/ClassUtils.java
  64. 49 0
      pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/util/MsgUtils.java
  65. 89 0
      pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/util/R.java
  66. 662 0
      pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/util/RedisUtils.java
  67. 289 0
      pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/util/RetOps.java
  68. 123 0
      pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/util/SpringContextHolder.java
  69. 168 0
      pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/util/WebUtils.java
  70. 5 0
      pig-common/pig-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  71. 17 0
      pig-common/pig-common-core/src/main/resources/banner.txt
  72. 26 0
      pig-common/pig-common-core/src/main/resources/i18n/messages_zh_CN.properties
  73. 73 0
      pig-common/pig-common-core/src/main/resources/logback-spring.xml
  74. 42 0
      pig-common/pig-common-datasource/pom.xml
  75. 107 0
      pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/DynamicDataSourceAutoConfiguration.java
  76. 37 0
      pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/annotation/EnableDynamicDataSource.java
  77. 42 0
      pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/config/ClearTtlDataSourceFilter.java
  78. 57 0
      pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/config/DataSourceProperties.java
  79. 85 0
      pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/config/JdbcDynamicDataSourceProvider.java
  80. 63 0
      pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/config/LastParamDsProcessor.java
  81. 30 0
      pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/enums/DsConfTypeEnum.java
  82. 72 0
      pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/enums/DsJdbcUrlEnum.java
  83. 57 0
      pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/support/DataSourceConstants.java
  84. 46 0
      pig-common/pig-common-excel/pom.xml
  85. 69 0
      pig-common/pig-common-excel/src/main/java/com/pig4cloud/pig/common/excel/ExcelAutoConfiguration.java
  86. 26 0
      pig-common/pig-common-excel/src/main/java/com/pig4cloud/pig/common/excel/provider/RemoteDictApiService.java
  87. 48 0
      pig-common/pig-common-excel/src/main/java/com/pig4cloud/pig/common/excel/provider/RemoteDictDataProvider.java
  88. 1 0
      pig-common/pig-common-excel/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  89. 72 0
      pig-common/pig-common-feign/pom.xml
  90. 77 0
      pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/PigFeignAutoConfiguration.java
  91. 53 0
      pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/annotation/EnablePigFeignClients.java
  92. 16 0
      pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/annotation/NoToken.java
  93. 37 0
      pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/core/PigFeignInnerRequestInterceptor.java
  94. 25 0
      pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/core/PigFeignRequestCloseInterceptor.java
  95. 82 0
      pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/sentinel/SentinelAutoConfiguration.java
  96. 139 0
      pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/sentinel/ext/PigSentinelFeign.java
  97. 191 0
      pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/sentinel/ext/PigSentinelInvocationHandler.java
  98. 140 0
      pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/sentinel/handle/GlobalBizExceptionHandler.java
  99. 54 0
      pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/sentinel/handle/PigUrlBlockHandler.java
  100. 45 0
      pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/sentinel/parser/PigHeaderRequestOriginParser.java

+ 6 - 0
.editorconfig

@@ -0,0 +1,6 @@
+root = true
+
+[*.{groovy,java,kt,xml}]
+indent_style = tab
+indent_size = 4
+continuation_indent_size = 8

+ 61 - 0
.gitignore

@@ -0,0 +1,61 @@
+### gradle ###
+.gradle
+/build/
+!gradle/wrapper/gradle-wrapper.jar
+
+### STS ###
+.settings/
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+bin/
+
+### IntelliJ IDEA ###
+!.idea/icon.png
+.idea
+*.iws
+*.iml
+*.ipr
+rebel.xml
+
+### NetBeans ###
+nbproject/private/
+build/
+nbbuild/
+nbdist/
+.nb-gradle/
+
+### maven ###
+target/
+*.war
+*.ear
+*.zip
+*.tar
+*.tar.gz
+*.versionsBackup
+
+### vscode ###
+.vscode
+
+### logs ###
+/logs/
+*.log
+
+### temp ignore ###
+*.cache
+*.diff
+*.patch
+*.tmp
+*.java~
+*.properties~
+*.xml~
+
+### system ignore ###
+.DS_Store
+Thumbs.db
+Servers
+.metadata
+.flattened-pom.xml

+ 202 - 0
LICENSE

@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 13 - 0
db/Dockerfile

@@ -0,0 +1,13 @@
+FROM registry.cn-hangzhou.aliyuncs.com/dockerhub_mirror/mysql-server:8.0.32
+
+MAINTAINER lengleng([email protected])
+
+ENV TZ=Asia/Shanghai
+
+RUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
+
+COPY ./pig.sql /docker-entrypoint-initdb.d
+
+COPY ./pig_config.sql /docker-entrypoint-initdb.d
+
+EXPOSE 3306

+ 1100 - 0
db/pig.sql

@@ -0,0 +1,1100 @@
+DROP DATABASE IF EXISTS `pig`;
+
+CREATE DATABASE  `pig` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
+
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+USE `pig`;
+
+
+-- ----------------------------
+-- Table structure for sys_dept
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_dept`;
+CREATE TABLE `sys_dept` (
+  `dept_id` bigint NOT NULL COMMENT '部门ID',
+  `name` varchar(50)  DEFAULT NULL COMMENT '部门名称',
+  `sort_order` int NOT NULL DEFAULT '0' COMMENT '排序',
+  `create_by` varchar(64)  NOT NULL DEFAULT ' ' COMMENT '创建人',
+  `update_by` varchar(64)  NOT NULL DEFAULT ' ' COMMENT '修改人',
+  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+  `update_time` datetime DEFAULT NULL COMMENT '修改时间',
+  `del_flag` char(1)  DEFAULT '0' COMMENT '删除标志',
+  `parent_id` bigint DEFAULT NULL COMMENT '父级部门ID',
+  PRIMARY KEY (`dept_id`) USING BTREE
+) ENGINE=InnoDB  COMMENT='部门管理';
+
+-- ----------------------------
+-- Records of sys_dept
+-- ----------------------------
+BEGIN;
+INSERT INTO `sys_dept` VALUES (1, '总裁办', 1, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:07:49', '0', 0);
+INSERT INTO `sys_dept` VALUES (2, '技术部', 2, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 1);
+INSERT INTO `sys_dept` VALUES (3, '市场部', 3, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 1);
+INSERT INTO `sys_dept` VALUES (4, '销售部', 4, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 1);
+INSERT INTO `sys_dept` VALUES (5, '财务部', 5, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 1);
+INSERT INTO `sys_dept` VALUES (6, '人事行政部', 6, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:53:36', '1', 1);
+INSERT INTO `sys_dept` VALUES (7, '研发部', 7, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 2);
+INSERT INTO `sys_dept` VALUES (8, 'UI设计部', 11, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 7);
+INSERT INTO `sys_dept` VALUES (9, '产品部', 12, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 2);
+INSERT INTO `sys_dept` VALUES (10, '渠道部', 13, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 3);
+INSERT INTO `sys_dept` VALUES (11, '推广部', 14, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 3);
+INSERT INTO `sys_dept` VALUES (12, '客服部', 15, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 4);
+INSERT INTO `sys_dept` VALUES (13, '财务会计部', 16, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 13:04:47', '0', 5);
+INSERT INTO `sys_dept` VALUES (14, '审计风控部', 17, 'admin', 'admin', '2023-04-03 13:04:47', '2023-04-03 14:06:57', '0', 5);
+COMMIT;
+
+-- ----------------------------
+-- Table structure for sys_dict
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_dict`;
+CREATE TABLE `sys_dict` (
+  `id` bigint NOT NULL COMMENT '编号',
+  `dict_type` varchar(100)  DEFAULT NULL COMMENT '字典类型',
+  `description` varchar(100)  DEFAULT NULL COMMENT '描述',
+  `create_by` varchar(64)  NOT NULL DEFAULT ' ' COMMENT '创建人',
+  `update_by` varchar(64)  NOT NULL DEFAULT ' ' COMMENT '修改人',
+  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
+  `remarks` varchar(255)  DEFAULT NULL COMMENT '备注信息',
+  `system_flag` char(1)  DEFAULT '0' COMMENT '系统标志',
+  `del_flag` char(1)  DEFAULT '0' COMMENT '删除标志',
+  PRIMARY KEY (`id`) USING BTREE,
+  KEY `sys_dict_del_flag` (`del_flag`) USING BTREE
+) ENGINE=InnoDB  COMMENT='字典表';
+
+-- ----------------------------
+-- Records of sys_dict
+-- ----------------------------
+BEGIN;
+INSERT INTO `sys_dict` VALUES (1, 'log_type', '日志类型', ' ', ' ', '2019-03-19 11:06:44', '2019-03-19 11:06:44', '异常、正常', '1', '0');
+INSERT INTO `sys_dict` VALUES (2, 'social_type', '社交登录', ' ', ' ', '2019-03-19 11:09:44', '2019-03-19 11:09:44', '微信、QQ', '1', '0');
+INSERT INTO `sys_dict` VALUES (3, 'job_type', '定时任务类型', ' ', ' ', '2019-03-19 11:22:21', '2019-03-19 11:22:21', 'quartz', '1', '0');
+INSERT INTO `sys_dict` VALUES (4, 'job_status', '定时任务状态', ' ', ' ', '2019-03-19 11:24:57', '2019-03-19 11:24:57', '发布状态、运行状态', '1', '0');
+INSERT INTO `sys_dict` VALUES (5, 'job_execute_status', '定时任务执行状态', ' ', ' ', '2019-03-19 11:26:15', '2019-03-19 11:26:15', '正常、异常', '1', '0');
+INSERT INTO `sys_dict` VALUES (6, 'misfire_policy', '定时任务错失执行策略', ' ', ' ', '2019-03-19 11:27:19', '2019-03-19 11:27:19', '周期', '1', '0');
+INSERT INTO `sys_dict` VALUES (7, 'gender', '性别', ' ', ' ', '2019-03-27 13:44:06', '2019-03-27 13:44:06', '微信用户性别', '1', '0');
+INSERT INTO `sys_dict` VALUES (8, 'subscribe', '订阅状态', ' ', ' ', '2019-03-27 13:48:33', '2019-03-27 13:48:33', '公众号订阅状态', '1', '0');
+INSERT INTO `sys_dict` VALUES (9, 'response_type', '回复', ' ', ' ', '2019-03-28 21:29:21', '2019-03-28 21:29:21', '微信消息是否已回复', '1', '0');
+INSERT INTO `sys_dict` VALUES (10, 'param_type', '参数配置', ' ', ' ', '2019-04-29 18:20:47', '2019-04-29 18:20:47', '检索、原文、报表、安全、文档、消息、其他', '1', '0');
+INSERT INTO `sys_dict` VALUES (11, 'status_type', '租户状态', ' ', ' ', '2019-05-15 16:31:08', '2019-05-15 16:31:08', '租户状态', '1', '0');
+INSERT INTO `sys_dict` VALUES (12, 'dict_type', '字典类型', ' ', ' ', '2019-05-16 14:16:20', '2019-05-16 14:20:16', '系统类不能修改', '1', '0');
+INSERT INTO `sys_dict` VALUES (13, 'channel_type', '支付类型', ' ', ' ', '2019-05-16 14:16:20', '2019-05-16 14:20:16', '系统类不能修改', '1', '0');
+INSERT INTO `sys_dict` VALUES (14, 'grant_types', '授权类型', ' ', ' ', '2019-08-13 07:34:10', '2019-08-13 07:34:10', NULL, '1', '0');
+INSERT INTO `sys_dict` VALUES (15, 'style_type', '前端风格', ' ', ' ', '2020-02-07 03:49:28', '2020-02-07 03:50:40', '0-Avue 1-element', '1', '0');
+INSERT INTO `sys_dict` VALUES (16, 'captcha_flag_types', '验证码开关', ' ', ' ', '2020-11-18 06:53:25', '2020-11-18 06:53:25', '是否校验验证码', '1', '0');
+INSERT INTO `sys_dict` VALUES (17, 'enc_flag_types', '前端密码加密', ' ', ' ', '2020-11-18 06:54:44', '2020-11-18 06:54:44', '前端密码是否加密传输', '1', '0');
+INSERT INTO `sys_dict` VALUES (18, 'lock_flag', '用户状态', 'admin', ' ', '2023-02-01 16:55:31', NULL, NULL, '1', '0');
+INSERT INTO `sys_dict` VALUES (19, 'ds_config_type', '数据连接类型', 'admin', ' ', '2023-02-06 18:36:59', NULL, NULL, '1', '0');
+INSERT INTO `sys_dict` VALUES (20, 'common_status', '通用状态', 'admin', ' ', '2023-02-09 11:02:08', NULL, NULL, '1', '0');
+INSERT INTO `sys_dict` VALUES (21, 'app_social_type', 'app社交登录', 'admin', ' ', '2023-02-10 11:11:06', NULL, 'app社交登录', '1', '0');
+INSERT INTO `sys_dict` VALUES (22, 'yes_no_type', '是否', 'admin', ' ', '2023-02-20 23:25:04', NULL, NULL, '1', '0');
+INSERT INTO `sys_dict` VALUES (23, 'repType', '微信消息类型', 'admin', ' ', '2023-02-24 15:08:25', NULL, NULL, '0', '0');
+INSERT INTO `sys_dict` VALUES (24, 'leave_status', '请假状态', 'admin', ' ', '2023-03-02 22:50:15', NULL, NULL, '0', '0');
+INSERT INTO `sys_dict` VALUES (25, 'schedule_type', '日程类型', 'admin', ' ', '2023-03-06 14:49:18', NULL, NULL, '0', '0');
+INSERT INTO `sys_dict` VALUES (26, 'schedule_status', '日程状态', 'admin', ' ', '2023-03-06 14:52:57', NULL, NULL, '0', '0');
+INSERT INTO `sys_dict` VALUES (27, 'ds_type', '代码生成器支持的数据库类型', 'admin', ' ', '2023-03-12 09:57:59', NULL, NULL, '1', '0');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for sys_dict_item
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_dict_item`;
+CREATE TABLE `sys_dict_item` (
+  `id` bigint NOT NULL COMMENT '编号',
+  `dict_id` bigint NOT NULL COMMENT '字典ID',
+  `item_value` varchar(100)  DEFAULT NULL COMMENT '字典项值',
+  `label` varchar(100)  DEFAULT NULL COMMENT '字典项名称',
+  `dict_type` varchar(100)  DEFAULT NULL COMMENT '字典类型',
+  `description` varchar(100)  DEFAULT NULL COMMENT '字典项描述',
+  `sort_order` int NOT NULL DEFAULT '0' COMMENT '排序(升序)',
+  `create_by` varchar(64)  NOT NULL DEFAULT ' ' COMMENT '创建人',
+  `update_by` varchar(64)  NOT NULL DEFAULT ' ' COMMENT '修改人',
+  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
+  `remarks` varchar(255)  DEFAULT NULL COMMENT '备注信息',
+  `del_flag` char(1)  DEFAULT '0' COMMENT '删除标志',
+  PRIMARY KEY (`id`) USING BTREE,
+  KEY `sys_dict_value` (`item_value`) USING BTREE,
+  KEY `sys_dict_label` (`label`) USING BTREE,
+  KEY `sys_dict_item_del_flag` (`del_flag`) USING BTREE
+) ENGINE=InnoDB  COMMENT='字典项';
+
+-- ----------------------------
+-- Records of sys_dict_item
+-- ----------------------------
+BEGIN;
+INSERT INTO `sys_dict_item` VALUES (1, 1, '9', '异常', 'log_type', '日志异常', 1, ' ', ' ', '2019-03-19 11:08:59', '2019-03-25 12:49:13', '', '0');
+INSERT INTO `sys_dict_item` VALUES (2, 1, '0', '正常', 'log_type', '日志正常', 0, ' ', ' ', '2019-03-19 11:09:17', '2019-03-25 12:49:18', '', '0');
+INSERT INTO `sys_dict_item` VALUES (3, 2, 'WX', '微信', 'social_type', '微信登录', 0, ' ', ' ', '2019-03-19 11:10:02', '2019-03-25 12:49:36', '', '0');
+INSERT INTO `sys_dict_item` VALUES (4, 2, 'QQ', 'QQ', 'social_type', 'QQ登录', 1, ' ', ' ', '2019-03-19 11:10:14', '2019-03-25 12:49:36', '', '0');
+INSERT INTO `sys_dict_item` VALUES (5, 3, '1', 'java类', 'job_type', 'java类', 1, ' ', ' ', '2019-03-19 11:22:37', '2019-03-25 12:49:36', '', '0');
+INSERT INTO `sys_dict_item` VALUES (6, 3, '2', 'spring bean', 'job_type', 'spring bean容器实例', 2, ' ', ' ', '2019-03-19 11:23:05', '2019-03-25 12:49:36', '', '0');
+INSERT INTO `sys_dict_item` VALUES (7, 3, '9', '其他', 'job_type', '其他类型', 9, ' ', ' ', '2019-03-19 11:23:31', '2019-03-25 12:49:36', '', '0');
+INSERT INTO `sys_dict_item` VALUES (8, 3, '3', 'Rest 调用', 'job_type', 'Rest 调用', 3, ' ', ' ', '2019-03-19 11:23:57', '2019-03-25 12:49:36', '', '0');
+INSERT INTO `sys_dict_item` VALUES (9, 3, '4', 'jar', 'job_type', 'jar类型', 4, ' ', ' ', '2019-03-19 11:24:20', '2019-03-25 12:49:36', '', '0');
+INSERT INTO `sys_dict_item` VALUES (10, 4, '1', '未发布', 'job_status', '未发布', 1, ' ', ' ', '2019-03-19 11:25:18', '2019-03-25 12:49:36', '', '0');
+INSERT INTO `sys_dict_item` VALUES (11, 4, '2', '运行中', 'job_status', '运行中', 2, ' ', ' ', '2019-03-19 11:25:31', '2019-03-25 12:49:36', '', '0');
+INSERT INTO `sys_dict_item` VALUES (12, 4, '3', '暂停', 'job_status', '暂停', 3, ' ', ' ', '2019-03-19 11:25:42', '2019-03-25 12:49:36', '', '0');
+INSERT INTO `sys_dict_item` VALUES (13, 5, '0', '正常', 'job_execute_status', '正常', 0, ' ', ' ', '2019-03-19 11:26:27', '2019-03-25 12:49:36', '', '0');
+INSERT INTO `sys_dict_item` VALUES (14, 5, '1', '异常', 'job_execute_status', '异常', 1, ' ', ' ', '2019-03-19 11:26:41', '2019-03-25 12:49:36', '', '0');
+INSERT INTO `sys_dict_item` VALUES (15, 6, '1', '错失周期立即执行', 'misfire_policy', '错失周期立即执行', 1, ' ', ' ', '2019-03-19 11:27:45', '2019-03-25 12:49:36', '', '0');
+INSERT INTO `sys_dict_item` VALUES (16, 6, '2', '错失周期执行一次', 'misfire_policy', '错失周期执行一次', 2, ' ', ' ', '2019-03-19 11:27:57', '2019-03-25 12:49:36', '', '0');
+INSERT INTO `sys_dict_item` VALUES (17, 6, '3', '下周期执行', 'misfire_policy', '下周期执行', 3, ' ', ' ', '2019-03-19 11:28:08', '2019-03-25 12:49:36', '', '0');
+INSERT INTO `sys_dict_item` VALUES (18, 7, '1', '男', 'gender', '微信-男', 0, ' ', ' ', '2019-03-27 13:45:13', '2019-03-27 13:45:13', '微信-男', '0');
+INSERT INTO `sys_dict_item` VALUES (19, 7, '2', '女', 'gender', '女-微信', 1, ' ', ' ', '2019-03-27 13:45:34', '2019-03-27 13:45:34', '女-微信', '0');
+INSERT INTO `sys_dict_item` VALUES (20, 7, '0', '未知', 'gender', 'x性别未知', 3, ' ', ' ', '2019-03-27 13:45:57', '2019-03-27 13:45:57', 'x性别未知', '0');
+INSERT INTO `sys_dict_item` VALUES (21, 8, '0', '未关注', 'subscribe', '公众号-未关注', 0, ' ', ' ', '2019-03-27 13:49:07', '2019-03-27 13:49:07', '公众号-未关注', '0');
+INSERT INTO `sys_dict_item` VALUES (22, 8, '1', '已关注', 'subscribe', '公众号-已关注', 1, ' ', ' ', '2019-03-27 13:49:26', '2019-03-27 13:49:26', '公众号-已关注', '0');
+INSERT INTO `sys_dict_item` VALUES (23, 9, '0', '未回复', 'response_type', '微信消息-未回复', 0, ' ', ' ', '2019-03-28 21:29:47', '2019-03-28 21:29:47', '微信消息-未回复', '0');
+INSERT INTO `sys_dict_item` VALUES (24, 9, '1', '已回复', 'response_type', '微信消息-已回复', 1, ' ', ' ', '2019-03-28 21:30:08', '2019-03-28 21:30:08', '微信消息-已回复', '0');
+INSERT INTO `sys_dict_item` VALUES (25, 10, '1', '检索', 'param_type', '检索', 0, ' ', ' ', '2019-04-29 18:22:17', '2019-04-29 18:22:17', '检索', '0');
+INSERT INTO `sys_dict_item` VALUES (26, 10, '2', '原文', 'param_type', '原文', 0, ' ', ' ', '2019-04-29 18:22:27', '2019-04-29 18:22:27', '原文', '0');
+INSERT INTO `sys_dict_item` VALUES (27, 10, '3', '报表', 'param_type', '报表', 0, ' ', ' ', '2019-04-29 18:22:36', '2019-04-29 18:22:36', '报表', '0');
+INSERT INTO `sys_dict_item` VALUES (28, 10, '4', '安全', 'param_type', '安全', 0, ' ', ' ', '2019-04-29 18:22:46', '2019-04-29 18:22:46', '安全', '0');
+INSERT INTO `sys_dict_item` VALUES (29, 10, '5', '文档', 'param_type', '文档', 0, ' ', ' ', '2019-04-29 18:22:56', '2019-04-29 18:22:56', '文档', '0');
+INSERT INTO `sys_dict_item` VALUES (30, 10, '6', '消息', 'param_type', '消息', 0, ' ', ' ', '2019-04-29 18:23:05', '2019-04-29 18:23:05', '消息', '0');
+INSERT INTO `sys_dict_item` VALUES (31, 10, '9', '其他', 'param_type', '其他', 0, ' ', ' ', '2019-04-29 18:23:16', '2019-04-29 18:23:16', '其他', '0');
+INSERT INTO `sys_dict_item` VALUES (32, 10, '0', '默认', 'param_type', '默认', 0, ' ', ' ', '2019-04-29 18:23:30', '2019-04-29 18:23:30', '默认', '0');
+INSERT INTO `sys_dict_item` VALUES (33, 11, '0', '正常', 'status_type', '状态正常', 0, ' ', ' ', '2019-05-15 16:31:34', '2019-05-16 22:30:46', '状态正常', '0');
+INSERT INTO `sys_dict_item` VALUES (34, 11, '9', '冻结', 'status_type', '状态冻结', 1, ' ', ' ', '2019-05-15 16:31:56', '2019-05-16 22:30:50', '状态冻结', '0');
+INSERT INTO `sys_dict_item` VALUES (35, 12, '1', '系统类', 'dict_type', '系统类字典', 0, ' ', ' ', '2019-05-16 14:20:40', '2019-05-16 14:20:40', '不能修改删除', '0');
+INSERT INTO `sys_dict_item` VALUES (36, 12, '0', '业务类', 'dict_type', '业务类字典', 0, ' ', ' ', '2019-05-16 14:20:59', '2019-05-16 14:20:59', '可以修改', '0');
+INSERT INTO `sys_dict_item` VALUES (37, 2, 'GITEE', '码云', 'social_type', '码云', 2, ' ', ' ', '2019-06-28 09:59:12', '2019-06-28 09:59:12', '码云', '0');
+INSERT INTO `sys_dict_item` VALUES (38, 2, 'OSC', '开源中国', 'social_type', '开源中国登录', 2, ' ', ' ', '2019-06-28 10:04:32', '2019-06-28 10:04:32', '', '0');
+INSERT INTO `sys_dict_item` VALUES (39, 14, 'password', '密码模式', 'grant_types', '支持oauth密码模式', 0, ' ', ' ', '2019-08-13 07:35:28', '2019-08-13 07:35:28', NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (40, 14, 'authorization_code', '授权码模式', 'grant_types', 'oauth2 授权码模式', 1, ' ', ' ', '2019-08-13 07:36:07', '2019-08-13 07:36:07', NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (41, 14, 'client_credentials', '客户端模式', 'grant_types', 'oauth2 客户端模式', 2, ' ', ' ', '2019-08-13 07:36:30', '2019-08-13 07:36:30', NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (42, 14, 'refresh_token', '刷新模式', 'grant_types', 'oauth2 刷新token', 3, ' ', ' ', '2019-08-13 07:36:54', '2019-08-13 07:36:54', NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (43, 14, 'implicit', '简化模式', 'grant_types', 'oauth2 简化模式', 4, ' ', ' ', '2019-08-13 07:39:32', '2019-08-13 07:39:32', NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (44, 15, '0', 'Avue', 'style_type', 'Avue风格', 0, ' ', ' ', '2020-02-07 03:52:52', '2020-02-07 03:52:52', '', '0');
+INSERT INTO `sys_dict_item` VALUES (45, 15, '1', 'element', 'style_type', 'element-ui', 1, ' ', ' ', '2020-02-07 03:53:12', '2020-02-07 03:53:12', '', '0');
+INSERT INTO `sys_dict_item` VALUES (46, 16, '0', '关', 'captcha_flag_types', '不校验验证码', 0, ' ', ' ', '2020-11-18 06:53:58', '2020-11-18 06:53:58', '不校验验证码 -0', '0');
+INSERT INTO `sys_dict_item` VALUES (47, 16, '1', '开', 'captcha_flag_types', '校验验证码', 1, ' ', ' ', '2020-11-18 06:54:15', '2020-11-18 06:54:15', '不校验验证码-1', '0');
+INSERT INTO `sys_dict_item` VALUES (48, 17, '0', '否', 'enc_flag_types', '不加密', 0, ' ', ' ', '2020-11-18 06:55:31', '2020-11-18 06:55:31', '不加密-0', '0');
+INSERT INTO `sys_dict_item` VALUES (49, 17, '1', '是', 'enc_flag_types', '加密', 1, ' ', ' ', '2020-11-18 06:55:51', '2020-11-18 06:55:51', '加密-1', '0');
+INSERT INTO `sys_dict_item` VALUES (50, 13, 'MERGE_PAY', '聚合支付', 'channel_type', '聚合支付', 1, ' ', ' ', '2019-05-30 19:08:08', '2019-06-18 13:51:53', '聚合支付', '0');
+INSERT INTO `sys_dict_item` VALUES (51, 2, 'CAS', 'CAS登录', 'social_type', 'CAS 单点登录系统', 3, ' ', ' ', '2022-02-18 13:56:25', '2022-02-18 13:56:28', NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (52, 2, 'DINGTALK', '钉钉', 'social_type', '钉钉', 3, ' ', ' ', '2022-02-18 13:56:25', '2022-02-18 13:56:28', NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (53, 2, 'WEIXIN_CP', '企业微信', 'social_type', '企业微信', 3, ' ', ' ', '2022-02-18 13:56:25', '2022-02-18 13:56:28', NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (54, 15, '2', 'APP', 'style_type', 'uview风格', 1, ' ', ' ', '2020-02-07 03:53:12', '2020-02-07 03:53:12', '', '0');
+INSERT INTO `sys_dict_item` VALUES (55, 13, 'ALIPAY_WAP', '支付宝支付', 'channel_type', '支付宝支付', 1, ' ', ' ', '2019-05-30 19:08:08', '2019-06-18 13:51:53', '聚合支付', '0');
+INSERT INTO `sys_dict_item` VALUES (56, 13, 'WEIXIN_MP', '微信支付', 'channel_type', '微信支付', 1, ' ', ' ', '2019-05-30 19:08:08', '2019-06-18 13:51:53', '聚合支付', '0');
+INSERT INTO `sys_dict_item` VALUES (57, 14, 'mobile', 'mobile', 'grant_types', '移动端登录', 5, 'admin', ' ', '2023-01-29 17:21:42', NULL, NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (58, 18, '0', '有效', 'lock_flag', '有效', 0, 'admin', ' ', '2023-02-01 16:56:00', NULL, NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (59, 18, '9', '禁用', 'lock_flag', '禁用', 1, 'admin', ' ', '2023-02-01 16:56:09', NULL, NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (60, 15, '4', 'vue3', 'style_type', 'element-plus', 4, 'admin', ' ', '2023-02-06 13:52:43', NULL, NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (61, 19, '0', '主机', 'ds_config_type', '主机', 0, 'admin', ' ', '2023-02-06 18:37:23', NULL, NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (62, 19, '1', 'JDBC', 'ds_config_type', 'jdbc', 2, 'admin', ' ', '2023-02-06 18:37:34', NULL, NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (63, 20, 'false', '否', 'common_status', '否', 1, 'admin', ' ', '2023-02-09 11:02:39', NULL, NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (64, 20, 'true', '是', 'common_status', '是', 2, 'admin', ' ', '2023-02-09 11:02:52', NULL, NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (65, 21, 'MINI', '小程序', 'app_social_type', '小程序登录', 0, 'admin', ' ', '2023-02-10 11:11:41', NULL, NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (66, 22, '0', '否', 'yes_no_type', '0', 0, 'admin', ' ', '2023-02-20 23:35:23', NULL, '0', '0');
+INSERT INTO `sys_dict_item` VALUES (67, 22, '1', '是', 'yes_no_type', '1', 0, 'admin', ' ', '2023-02-20 23:35:37', NULL, '1', '0');
+INSERT INTO `sys_dict_item` VALUES (69, 23, 'text', '文本', 'repType', '文本', 0, 'admin', ' ', '2023-02-24 15:08:45', NULL, NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (70, 23, 'image', '图片', 'repType', '图片', 0, 'admin', ' ', '2023-02-24 15:08:56', NULL, NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (71, 23, 'voice', '语音', 'repType', '语音', 0, 'admin', ' ', '2023-02-24 15:09:08', NULL, NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (72, 23, 'video', '视频', 'repType', '视频', 0, 'admin', ' ', '2023-02-24 15:09:18', NULL, NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (73, 23, 'shortvideo', '小视频', 'repType', '小视频', 0, 'admin', ' ', '2023-02-24 15:09:29', NULL, NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (74, 23, 'location', '地理位置', 'repType', '地理位置', 0, 'admin', ' ', '2023-02-24 15:09:41', NULL, NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (75, 23, 'link', '链接消息', 'repType', '链接消息', 0, 'admin', ' ', '2023-02-24 15:09:49', NULL, NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (76, 23, 'event', '事件推送', 'repType', '事件推送', 0, 'admin', ' ', '2023-02-24 15:09:57', NULL, NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (77, 24, '0', '未提交', 'leave_status', '未提交', 0, 'admin', ' ', '2023-03-02 22:50:45', NULL, '未提交', '0');
+INSERT INTO `sys_dict_item` VALUES (78, 24, '1', '审批中', 'leave_status', '审批中', 0, 'admin', ' ', '2023-03-02 22:50:57', NULL, '审批中', '0');
+INSERT INTO `sys_dict_item` VALUES (79, 24, '2', '完成', 'leave_status', '完成', 0, 'admin', ' ', '2023-03-02 22:51:06', NULL, '完成', '0');
+INSERT INTO `sys_dict_item` VALUES (80, 24, '9', '驳回', 'leave_status', '驳回', 0, 'admin', ' ', '2023-03-02 22:51:20', NULL, NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (81, 25, 'record', '日程记录', 'schedule_type', '日程记录', 0, 'admin', ' ', '2023-03-06 14:50:01', NULL, NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (82, 25, 'plan', '计划', 'schedule_type', '计划类型', 0, 'admin', ' ', '2023-03-06 14:50:29', NULL, NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (83, 26, '0', '计划中', 'schedule_status', '日程状态', 0, 'admin', ' ', '2023-03-06 14:53:18', NULL, NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (84, 26, '1', '已开始', 'schedule_status', '已开始', 0, 'admin', ' ', '2023-03-06 14:53:33', NULL, NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (85, 26, '3', '已结束', 'schedule_status', '已结束', 0, 'admin', ' ', '2023-03-06 14:53:41', NULL, NULL, '0');
+INSERT INTO `sys_dict_item` VALUES (86, 27, 'mysql', 'mysql', 'ds_type', 'mysql', 0, 'admin', ' ', '2023-03-12 09:58:11', NULL, NULL, '0');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for sys_file
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_file`;
+CREATE TABLE `sys_file` (
+  `id` bigint NOT NULL COMMENT '编号',
+  `file_name` varchar(100)  DEFAULT NULL COMMENT '文件名',
+  `bucket_name` varchar(200)  DEFAULT NULL COMMENT '文件存储桶名称',
+  `original` varchar(100)  DEFAULT NULL COMMENT '原始文件名',
+  `type` varchar(50)  DEFAULT NULL COMMENT '文件类型',
+  `file_size` bigint DEFAULT NULL COMMENT '文件大小',
+  `create_by` varchar(64)  NOT NULL DEFAULT ' ' COMMENT '创建人',
+  `update_by` varchar(64)  NOT NULL DEFAULT ' ' COMMENT '修改人',
+  `create_time` datetime DEFAULT NULL COMMENT '上传时间',
+  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
+  `del_flag` char(1)  DEFAULT '0' COMMENT '删除标志',
+  PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB  COMMENT='文件管理表';
+
+-- ----------------------------
+-- Records of sys_file
+-- ----------------------------
+BEGIN;
+COMMIT;
+
+-- ----------------------------
+-- Table structure for sys_log
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_log`;
+CREATE TABLE `sys_log` (
+  `id` bigint NOT NULL COMMENT '编号',
+  `log_type` char(1)  DEFAULT '0' COMMENT '日志类型',
+  `title` varchar(255)  DEFAULT NULL COMMENT '日志标题',
+  `service_id` varchar(32)  DEFAULT NULL COMMENT '服务ID',
+  `create_by` varchar(64)  DEFAULT ' ' COMMENT '创建人',
+  `update_by` varchar(64)  DEFAULT ' ' COMMENT '修改人',
+  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
+  `remote_addr` varchar(255)  DEFAULT NULL COMMENT '远程地址',
+  `user_agent` varchar(1000)  DEFAULT NULL COMMENT '用户代理',
+  `request_uri` varchar(255)  DEFAULT NULL COMMENT '请求URI',
+  `method` varchar(10)  DEFAULT NULL COMMENT '请求方法',
+  `params` text  COMMENT '请求参数',
+  `time` bigint DEFAULT NULL COMMENT '执行时间',
+  `del_flag` char(1)  DEFAULT '0' COMMENT '删除标志',
+  `exception` text  COMMENT '异常信息',
+  PRIMARY KEY (`id`) USING BTREE,
+  KEY `sys_log_request_uri` (`request_uri`) USING BTREE,
+  KEY `sys_log_type` (`log_type`) USING BTREE,
+  KEY `sys_log_create_date` (`create_time`) USING BTREE
+) ENGINE=InnoDB  COMMENT='日志表';
+
+
+-- ----------------------------
+-- Table structure for sys_menu
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_menu`;
+CREATE TABLE `sys_menu` (
+  `menu_id` bigint NOT NULL COMMENT '菜单ID',
+  `name` varchar(32)  DEFAULT NULL COMMENT '菜单名称',
+  `en_name` varchar(128)  DEFAULT NULL COMMENT '英文名称',
+  `permission` varchar(32)  DEFAULT NULL COMMENT '权限标识',
+  `path` varchar(128)  DEFAULT NULL COMMENT '路由路径',
+  `parent_id` bigint DEFAULT NULL COMMENT '父菜单ID',
+  `icon` varchar(64)  DEFAULT NULL COMMENT '菜单图标',
+  `visible` char(1)  DEFAULT '1' COMMENT '是否可见,0隐藏,1显示',
+  `sort_order` int DEFAULT '1' COMMENT '排序值,越小越靠前',
+  `keep_alive` char(1)  DEFAULT '0' COMMENT '是否缓存,0否,1是',
+  `embedded` char(1)  DEFAULT NULL COMMENT '是否内嵌,0否,1是',
+  `menu_type` char(1)  DEFAULT '0' COMMENT '菜单类型,0目录,1菜单,2按钮',
+  `create_by` varchar(64)  NOT NULL DEFAULT ' ' COMMENT '创建人',
+  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_by` varchar(64)  NOT NULL DEFAULT ' ' COMMENT '修改人',
+  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  `del_flag` char(1)  DEFAULT '0' COMMENT '删除标志,0未删除,1已删除',
+  PRIMARY KEY (`menu_id`) USING BTREE
+) ENGINE=InnoDB  COMMENT='菜单权限表';
+
+-- ----------------------------
+-- Records of sys_menu
+-- ----------------------------
+BEGIN;
+INSERT INTO `sys_menu` VALUES (1000, '权限管理', 'authorization', NULL, '/admin', -1, 'iconfont icon-icon-', '1', 0, '0', '0', '0', '', '2018-09-28 08:29:53', 'admin', '2023-03-12 22:32:52', '0');
+INSERT INTO `sys_menu` VALUES (1100, '用户管理', 'user', NULL, '/admin/user/index', 1000, 'ele-User', '1', 1, '0', '0', '0', '', '2017-11-02 22:24:37', 'admin', '2023-07-05 10:28:22', '0');
+INSERT INTO `sys_menu` VALUES (1101, '用户新增', NULL, 'sys_user_add', NULL, 1100, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 09:52:09', ' ', '2021-05-25 03:12:55', '0');
+INSERT INTO `sys_menu` VALUES (1102, '用户修改', NULL, 'sys_user_edit', NULL, 1100, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 09:52:48', ' ', '2021-05-25 03:12:55', '0');
+INSERT INTO `sys_menu` VALUES (1103, '用户删除', NULL, 'sys_user_del', NULL, 1100, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 09:54:01', ' ', '2021-05-25 03:12:55', '0');
+INSERT INTO `sys_menu` VALUES (1104, '导入导出', NULL, 'sys_user_export', NULL, 1100, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 09:54:01', ' ', '2021-05-25 03:12:55', '0');
+INSERT INTO `sys_menu` VALUES (1200, '菜单管理', 'menu', NULL, '/admin/menu/index', 1000, 'iconfont icon-caidan', '1', 2, '0', '0', '0', '', '2017-11-08 09:57:27', 'admin', '2023-07-05 10:28:17', '0');
+INSERT INTO `sys_menu` VALUES (1201, '菜单新增', NULL, 'sys_menu_add', NULL, 1200, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 10:15:53', ' ', '2021-05-25 03:12:55', '0');
+INSERT INTO `sys_menu` VALUES (1202, '菜单修改', NULL, 'sys_menu_edit', NULL, 1200, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 10:16:23', ' ', '2021-05-25 03:12:55', '0');
+INSERT INTO `sys_menu` VALUES (1203, '菜单删除', NULL, 'sys_menu_del', NULL, 1200, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 10:16:43', ' ', '2021-05-25 03:12:55', '0');
+INSERT INTO `sys_menu` VALUES (1300, '角色管理', 'role', NULL, '/admin/role/index', 1000, 'iconfont icon-gerenzhongxin', '1', 3, '0', NULL, '0', '', '2017-11-08 10:13:37', 'admin', '2023-07-05 10:28:13', '0');
+INSERT INTO `sys_menu` VALUES (1301, '角色新增', NULL, 'sys_role_add', NULL, 1300, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 10:14:18', ' ', '2021-05-25 03:12:55', '0');
+INSERT INTO `sys_menu` VALUES (1302, '角色修改', NULL, 'sys_role_edit', NULL, 1300, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 10:14:41', ' ', '2021-05-25 03:12:55', '0');
+INSERT INTO `sys_menu` VALUES (1303, '角色删除', NULL, 'sys_role_del', NULL, 1300, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 10:14:59', ' ', '2021-05-25 03:12:55', '0');
+INSERT INTO `sys_menu` VALUES (1304, '分配权限', NULL, 'sys_role_perm', NULL, 1300, NULL, '1', 1, '0', NULL, '1', ' ', '2018-04-20 07:22:55', ' ', '2021-05-25 03:12:55', '0');
+INSERT INTO `sys_menu` VALUES (1305, '角色导入导出', NULL, 'sys_role_export', NULL, 1300, NULL, '1', 4, '0', NULL, '1', ' ', '2022-03-26 15:54:34', ' ', NULL, '0');
+INSERT INTO `sys_menu` VALUES (1400, '部门管理', 'dept', NULL, '/admin/dept/index', 1000, 'iconfont icon-zidingyibuju', '1', 4, '0', NULL, '0', '', '2018-01-20 13:17:19', 'admin', '2023-07-05 10:28:07', '0');
+INSERT INTO `sys_menu` VALUES (1401, '部门新增', NULL, 'sys_dept_add', NULL, 1400, NULL, '1', 1, '0', NULL, '1', ' ', '2018-01-20 14:56:16', ' ', '2021-05-25 03:12:55', '0');
+INSERT INTO `sys_menu` VALUES (1402, '部门修改', NULL, 'sys_dept_edit', NULL, 1400, NULL, '1', 1, '0', NULL, '1', ' ', '2018-01-20 14:56:59', ' ', '2021-05-25 03:12:55', '0');
+INSERT INTO `sys_menu` VALUES (1403, '部门删除', NULL, 'sys_dept_del', NULL, 1400, NULL, '1', 1, '0', NULL, '1', ' ', '2018-01-20 14:57:28', ' ', '2021-05-25 03:12:55', '0');
+INSERT INTO `sys_menu` VALUES (1600, '岗位管理', 'post', NULL, '/admin/post/index', 1000, 'iconfont icon--chaifenhang', '1', 5, '1', '0', '0', '', '2022-03-26 13:04:14', 'admin', '2023-07-05 10:28:03', '0');
+INSERT INTO `sys_menu` VALUES (1601, '岗位信息查看', NULL, 'sys_post_view', NULL, 1600, NULL, '1', 0, '0', NULL, '1', ' ', '2022-03-26 13:05:34', ' ', NULL, '0');
+INSERT INTO `sys_menu` VALUES (1602, '岗位信息新增', NULL, 'sys_post_add', NULL, 1600, NULL, '1', 1, '0', NULL, '1', ' ', '2022-03-26 13:06:00', ' ', NULL, '0');
+INSERT INTO `sys_menu` VALUES (1603, '岗位信息修改', NULL, 'sys_post_edit', NULL, 1600, NULL, '1', 2, '0', NULL, '1', ' ', '2022-03-26 13:06:31', ' ', '2022-03-26 13:06:38', '0');
+INSERT INTO `sys_menu` VALUES (1604, '岗位信息删除', NULL, 'sys_post_del', NULL, 1600, NULL, '1', 3, '0', NULL, '1', ' ', '2022-03-26 13:06:31', ' ', NULL, '0');
+INSERT INTO `sys_menu` VALUES (1605, '岗位导入导出', NULL, 'sys_post_export', NULL, 1600, NULL, '1', 4, '0', NULL, '1', ' ', '2022-03-26 13:06:31', ' ', '2022-03-26 06:32:02', '0');
+INSERT INTO `sys_menu` VALUES (2000, '系统管理', 'system', NULL, '/system', -1, 'iconfont icon-quanjushezhi_o', '1', 1, '0', NULL, '0', '', '2017-11-07 20:56:00', 'admin', '2023-07-05 10:27:58', '0');
+INSERT INTO `sys_menu` VALUES (2001, '日志管理', 'log', NULL, '/admin/logs', 2000, 'ele-Cloudy', '1', 0, '0', '0', '0', 'admin', '2023-03-02 12:26:42', 'admin', '2023-07-05 10:27:53', '0');
+INSERT INTO `sys_menu` VALUES (2100, '操作日志', 'operation', NULL, '/admin/log/index', 2001, 'iconfont icon-jinridaiban', '1', 2, '0', '0', '0', '', '2017-11-20 14:06:22', 'admin', '2023-07-05 10:27:49', '0');
+INSERT INTO `sys_menu` VALUES (2101, '日志删除', NULL, 'sys_log_del', NULL, 2100, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-20 20:37:37', ' ', '2021-05-25 03:12:55', '0');
+INSERT INTO `sys_menu` VALUES (2102, '导入导出', NULL, 'sys_log_export', NULL, 2100, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-08 09:54:01', ' ', '2021-05-25 03:12:55', '0');
+INSERT INTO `sys_menu` VALUES (2200, '字典管理', 'dict', NULL, '/admin/dict/index', 2000, 'iconfont icon-zhongduancanshuchaxun', '1', 6, '0', NULL, '0', '', '2017-11-29 11:30:52', 'admin', '2023-07-05 10:27:37', '0');
+INSERT INTO `sys_menu` VALUES (2201, '字典删除', NULL, 'sys_dict_del', NULL, 2200, NULL, '1', 1, '0', NULL, '1', ' ', '2017-11-29 11:30:11', ' ', '2021-05-25 03:12:55', '0');
+INSERT INTO `sys_menu` VALUES (2202, '字典新增', NULL, 'sys_dict_add', NULL, 2200, NULL, '1', 1, '0', NULL, '1', ' ', '2018-05-11 22:34:55', ' ', '2021-05-25 03:12:55', '0');
+INSERT INTO `sys_menu` VALUES (2203, '字典修改', NULL, 'sys_dict_edit', NULL, 2200, NULL, '1', 1, '0', NULL, '1', ' ', '2018-05-11 22:36:03', ' ', '2021-05-25 03:12:55', '0');
+INSERT INTO `sys_menu` VALUES (2210, '参数管理', 'parameter', NULL, '/admin/param/index', 2000, 'iconfont icon-wenducanshu-05', '1', 7, '1', NULL, '0', '', '2019-04-29 22:16:50', 'admin', '2023-02-16 15:24:51', '0');
+INSERT INTO `sys_menu` VALUES (2211, '参数新增', NULL, 'sys_syspublicparam_add', NULL, 2210, NULL, '1', 1, '0', NULL, '1', ' ', '2019-04-29 22:17:36', ' ', '2020-03-24 08:57:11', '0');
+INSERT INTO `sys_menu` VALUES (2212, '参数删除', NULL, 'sys_syspublicparam_del', NULL, 2210, NULL, '1', 1, '0', NULL, '1', ' ', '2019-04-29 22:17:55', ' ', '2020-03-24 08:57:12', '0');
+INSERT INTO `sys_menu` VALUES (2213, '参数编辑', NULL, 'sys_syspublicparam_edit', NULL, 2210, NULL, '1', 1, '0', NULL, '1', ' ', '2019-04-29 22:18:14', ' ', '2020-03-24 08:57:13', '0');
+INSERT INTO `sys_menu` VALUES (2300, '代码生成', 'code', NULL, '/gen/table/index', 9000, 'iconfont icon-zhongduancanshu', '1', 1, '0', '0', '0', '', '2018-01-20 13:17:19', 'admin', '2023-02-20 13:54:35', '0');
+INSERT INTO `sys_menu` VALUES (2400, '终端管理', 'client', NULL, '/admin/client/index', 2000, 'iconfont icon-gongju', '1', 9, '1', NULL, '0', '', '2018-01-20 13:17:19', 'admin', '2023-02-16 15:25:28', '0');
+INSERT INTO `sys_menu` VALUES (2401, '客户端新增', NULL, 'sys_client_add', NULL, 2400, '1', '1', 1, '0', NULL, '1', ' ', '2018-05-15 21:35:18', ' ', '2021-05-25 03:12:55', '0');
+INSERT INTO `sys_menu` VALUES (2402, '客户端修改', NULL, 'sys_client_edit', NULL, 2400, NULL, '1', 1, '0', NULL, '1', ' ', '2018-05-15 21:37:06', ' ', '2021-05-25 03:12:55', '0');
+INSERT INTO `sys_menu` VALUES (2403, '客户端删除', NULL, 'sys_client_del', NULL, 2400, NULL, '1', 1, '0', NULL, '1', ' ', '2018-05-15 21:39:16', ' ', '2021-05-25 03:12:55', '0');
+INSERT INTO `sys_menu` VALUES (2600, '令牌管理', 'token', NULL, '/admin/token/index', 2000, 'ele-Key', '1', 11, '0', NULL, '0', '', '2018-09-04 05:58:41', 'admin', '2023-02-16 15:28:28', '0');
+INSERT INTO `sys_menu` VALUES (2601, '令牌删除', NULL, 'sys_token_del', NULL, 2600, NULL, '1', 1, '0', NULL, '1', ' ', '2018-09-04 05:59:50', ' ', '2020-03-24 08:57:24', '0');
+INSERT INTO `sys_menu` VALUES (2800, 'Quartz管理', 'quartz', NULL, '/daemon/job-manage/index', 2000, 'ele-AlarmClock', '1', 8, '0', NULL, '0', '', '2018-01-20 13:17:19', 'admin', '2023-02-16 15:25:06', '0');
+INSERT INTO `sys_menu` VALUES (2810, '任务新增', NULL, 'job_sys_job_add', NULL, 2800, '1', '1', 0, '0', NULL, '1', ' ', '2018-05-15 21:35:18', ' ', '2020-03-24 08:57:26', '0');
+INSERT INTO `sys_menu` VALUES (2820, '任务修改', NULL, 'job_sys_job_edit', NULL, 2800, '1', '1', 0, '0', NULL, '1', ' ', '2018-05-15 21:35:18', ' ', '2020-03-24 08:57:27', '0');
+INSERT INTO `sys_menu` VALUES (2830, '任务删除', NULL, 'job_sys_job_del', NULL, 2800, '1', '1', 0, '0', NULL, '1', ' ', '2018-05-15 21:35:18', ' ', '2020-03-24 08:57:28', '0');
+INSERT INTO `sys_menu` VALUES (2840, '任务暂停', NULL, 'job_sys_job_shutdown_job', NULL, 2800, '1', '1', 0, '0', NULL, '1', ' ', '2018-05-15 21:35:18', ' ', '2020-03-24 08:57:28', '0');
+INSERT INTO `sys_menu` VALUES (2850, '任务开始', NULL, 'job_sys_job_start_job', NULL, 2800, '1', '1', 0, '0', NULL, '1', ' ', '2018-05-15 21:35:18', ' ', '2020-03-24 08:57:29', '0');
+INSERT INTO `sys_menu` VALUES (2860, '任务刷新', NULL, 'job_sys_job_refresh_job', NULL, 2800, '1', '1', 0, '0', NULL, '1', ' ', '2018-05-15 21:35:18', ' ', '2020-03-24 08:57:30', '0');
+INSERT INTO `sys_menu` VALUES (2870, '执行任务', NULL, 'job_sys_job_run_job', NULL, 2800, '1', '1', 0, '0', NULL, '1', ' ', '2019-08-08 15:35:18', ' ', '2020-03-24 08:57:31', '0');
+INSERT INTO `sys_menu` VALUES (2871, '导出', NULL, 'job_sys_job_export', NULL, 2800, NULL, '1', 0, '0', '0', '1', 'admin', '2023-03-06 15:26:13', ' ', NULL, '0');
+INSERT INTO `sys_menu` VALUES (2906, '文件管理', 'file', NULL, '/admin/file/index', 2000, 'ele-Files', '1', 6, '0', NULL, '0', '', '2019-06-25 12:44:46', 'admin', '2023-02-16 15:24:42', '0');
+INSERT INTO `sys_menu` VALUES (2907, '删除文件', NULL, 'sys_file_del', NULL, 2906, NULL, '1', 1, '0', NULL, '1', ' ', '2019-06-25 13:41:41', ' ', '2020-03-24 08:58:42', '0');
+INSERT INTO `sys_menu` VALUES (4000, '系统监控', 'monitor', NULL, '/daemon', -1, 'iconfont icon-shuju', '1', 3, '0', '0', '0', 'admin', '2023-02-06 20:20:47', 'admin', '2023-02-23 20:01:07', '0');
+INSERT INTO `sys_menu` VALUES (4001, '文档扩展', 'doc', NULL, 'http://pig-gateway:9999/swagger-ui.html', 4000, 'iconfont icon-biaodan', '1', 2, '0', '1', '0', '', '2018-06-26 10:50:32', 'admin', '2023-02-23 20:01:29', '0');
+INSERT INTO `sys_menu` VALUES (4002, '缓存监控', 'cache', NULL, '/ext/cache', 4000, 'iconfont icon-shuju', '1', 1, '0', '0', '0', 'admin', '2023-05-29 15:12:59', 'admin', '2023-06-06 11:58:41', '0');
+INSERT INTO `sys_menu` VALUES (9000, '开发平台', 'develop', NULL, '/gen', -1, 'iconfont icon-shuxingtu', '1', 9, '0', '0', '0', '', '2019-08-12 09:35:16', 'admin', '2023-07-05 10:25:27', '0');
+INSERT INTO `sys_menu` VALUES (9005, '数据源管理', 'datasource', NULL, '/gen/datasource/index', 9000, 'ele-Coin', '1', 0, '0', NULL, '0', '', '2019-08-12 09:42:11', 'admin', '2023-07-05 10:26:56', '0');
+INSERT INTO `sys_menu` VALUES (9006, '表单设计', 'Form Design', NULL, '/gen/design/index', 9000, 'iconfont icon-AIshiyanshi', '0', 2, '0', '0', '0', '', '2019-08-16 10:08:56', 'admin', '2023-02-23 14:06:50', '0');
+INSERT INTO `sys_menu` VALUES (9007, '生成页面', 'generation', NULL, '/gen/gener/index', 9000, 'iconfont icon-tongzhi4', '0', 0, '0', '0', '0', 'admin', '2023-02-20 09:58:23', 'admin', '2023-07-05 10:27:06', '0');
+INSERT INTO `sys_menu` VALUES (9050, '元数据管理', 'metadata', NULL, '/gen/metadata', 9000, 'iconfont icon--chaifenhang', '1', 9, '0', '0', '0', '', '2018-07-27 01:13:21', 'admin', '2023-07-05 10:27:13', '0');
+INSERT INTO `sys_menu` VALUES (9051, '模板管理', 'template', NULL, '/gen/template/index', 9050, 'iconfont icon--chaifenhang', '1', 5, '0', '0', '0', 'admin', '2023-02-21 11:22:54', 'admin', '2023-07-05 10:27:18', '0');
+INSERT INTO `sys_menu` VALUES (9052, '查询', NULL, 'codegen_template_view', NULL, 9051, NULL, '0', 0, '0', '0', '1', 'admin', '2023-02-21 12:33:03', 'admin', '2023-02-21 13:50:54', '0');
+INSERT INTO `sys_menu` VALUES (9053, '增加', NULL, 'codegen_template_add', NULL, 9051, NULL, '1', 0, '0', '0', '1', 'admin', '2023-02-21 13:34:10', 'admin', '2023-02-21 13:39:49', '0');
+INSERT INTO `sys_menu` VALUES (9054, '新增', NULL, 'codegen_template_add', NULL, 9051, NULL, '0', 1, '0', '0', '1', 'admin', '2023-02-21 13:51:32', ' ', NULL, '0');
+INSERT INTO `sys_menu` VALUES (9055, '导出', NULL, 'codegen_template_export', NULL, 9051, NULL, '0', 2, '0', '0', '1', 'admin', '2023-02-21 13:51:58', ' ', NULL, '0');
+INSERT INTO `sys_menu` VALUES (9056, '删除', NULL, 'codegen_template_del', NULL, 9051, NULL, '0', 3, '0', '0', '1', 'admin', '2023-02-21 13:52:16', ' ', NULL, '0');
+INSERT INTO `sys_menu` VALUES (9057, '编辑', NULL, 'codegen_template_edit', NULL, 9051, NULL, '0', 4, '0', '0', '1', 'admin', '2023-02-21 13:52:58', ' ', NULL, '0');
+INSERT INTO `sys_menu` VALUES (9059, '模板分组', 'group', NULL, '/gen/group/index', 9050, 'iconfont icon-shuxingtu', '1', 6, '0', '0', '0', 'admin', '2023-02-21 15:06:50', 'admin', '2023-07-05 10:27:22', '0');
+INSERT INTO `sys_menu` VALUES (9060, '查询', NULL, 'codegen_group_view', NULL, 9059, NULL, '0', 0, '0', '0', '1', 'admin', '2023-02-21 15:08:07', ' ', NULL, '0');
+INSERT INTO `sys_menu` VALUES (9061, '新增', NULL, 'codegen_group_add', NULL, 9059, NULL, '0', 0, '0', '0', '1', 'admin', '2023-02-21 15:08:28', ' ', NULL, '0');
+INSERT INTO `sys_menu` VALUES (9062, '修改', NULL, 'codegen_group_edit', NULL, 9059, NULL, '0', 0, '0', '0', '1', 'admin', '2023-02-21 15:08:43', ' ', NULL, '0');
+INSERT INTO `sys_menu` VALUES (9063, '删除', NULL, 'codegen_group_del', NULL, 9059, NULL, '0', 0, '0', '0', '1', 'admin', '2023-02-21 15:09:02', ' ', NULL, '0');
+INSERT INTO `sys_menu` VALUES (9064, '导出', NULL, 'codegen_group_export', NULL, 9059, NULL, '0', 0, '0', '0', '1', 'admin', '2023-02-21 15:09:22', ' ', NULL, '0');
+INSERT INTO `sys_menu` VALUES (9065, '字段管理', 'field', NULL, '/gen/field-type/index', 9050, 'iconfont icon-fuwenben', '1', 0, '0', '0', '0', 'admin', '2023-02-23 20:05:09', 'admin', '2023-07-05 10:27:31', '0');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for sys_oauth_client_details
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_oauth_client_details`;
+CREATE TABLE `sys_oauth_client_details` (
+  `id` bigint NOT NULL COMMENT 'ID',
+  `client_id` varchar(32)  NOT NULL COMMENT '客户端ID',
+  `resource_ids` varchar(256)  DEFAULT NULL COMMENT '资源ID集合',
+  `client_secret` varchar(256)  DEFAULT NULL COMMENT '客户端秘钥',
+  `scope` varchar(256)  DEFAULT NULL COMMENT '授权范围',
+  `authorized_grant_types` varchar(256)  DEFAULT NULL COMMENT '授权类型',
+  `web_server_redirect_uri` varchar(256)  DEFAULT NULL COMMENT '回调地址',
+  `authorities` varchar(256)  DEFAULT NULL COMMENT '权限集合',
+  `access_token_validity` int DEFAULT NULL COMMENT '访问令牌有效期(秒)',
+  `refresh_token_validity` int DEFAULT NULL COMMENT '刷新令牌有效期(秒)',
+  `additional_information` varchar(4096)  DEFAULT NULL COMMENT '附加信息',
+  `autoapprove` varchar(256)  DEFAULT NULL COMMENT '自动授权',
+  `del_flag` char(1)  DEFAULT '0' COMMENT '删除标记,0未删除,1已删除',
+  `create_by` varchar(64)  NOT NULL DEFAULT ' ' COMMENT '创建人',
+  `update_by` varchar(64)  NOT NULL DEFAULT ' ' COMMENT '修改人',
+  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
+  PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB  COMMENT='终端信息表';
+
+-- ----------------------------
+-- Records of sys_oauth_client_details
+-- ----------------------------
+BEGIN;
+INSERT INTO `sys_oauth_client_details` VALUES (1, 'app', NULL, 'app', 'server', 'password,refresh_token,authorization_code,client_credentials,mobile', 'http://localhost:4040/sso1/login,http://localhost:4041/sso1/login,http://localhost:8080/renren-admin/sys/oauth2-sso,http://localhost:8090/sys/oauth2-sso', NULL, 43200, 2592001, '{\"enc_flag\":\"1\",\"captcha_flag\":\"1\",\"online_quantity\":\"1\"}', 'true', '0', '', 'admin', NULL, '2023-02-09 13:54:54');
+INSERT INTO `sys_oauth_client_details` VALUES (2, 'daemon', NULL, 'daemon', 'server', 'password,refresh_token', NULL, NULL, 43200, 2592001, '{\"enc_flag\":\"1\",\"captcha_flag\":\"1\"}', 'true', '0', ' ', ' ', NULL, NULL);
+INSERT INTO `sys_oauth_client_details` VALUES (3, 'gen', NULL, 'gen', 'server', 'password,refresh_token', NULL, NULL, 43200, 2592001, '{\"enc_flag\":\"1\",\"captcha_flag\":\"1\"}', 'true', '0', ' ', ' ', NULL, NULL);
+INSERT INTO `sys_oauth_client_details` VALUES (4, 'mp', NULL, 'mp', 'server', 'password,refresh_token', NULL, NULL, 43200, 2592001, '{\"enc_flag\":\"1\",\"captcha_flag\":\"1\"}', 'true', '0', ' ', ' ', NULL, NULL);
+INSERT INTO `sys_oauth_client_details` VALUES (5, 'pig', NULL, 'pig', 'server', 'password,refresh_token,authorization_code,client_credentials,mobile', 'http://localhost:4040/sso1/login,http://localhost:4041/sso1/login,http://localhost:8080/renren-admin/sys/oauth2-sso,http://localhost:8090/sys/oauth2-sso', NULL, 43200, 2592001, '{\"enc_flag\":\"1\",\"captcha_flag\":\"1\",\"online_quantity\":\"1\"}', 'false', '0', '', 'admin', NULL, '2023-03-08 11:32:41');
+INSERT INTO `sys_oauth_client_details` VALUES (6, 'test', NULL, 'test', 'server', 'password,refresh_token', NULL, NULL, 43200, 2592001, '{ \"enc_flag\":\"1\",\"captcha_flag\":\"0\"}', 'true', '0', ' ', ' ', NULL, NULL);
+INSERT INTO `sys_oauth_client_details` VALUES (7, 'social', NULL, 'social', 'server', 'password,refresh_token,mobile', NULL, NULL, 43200, 2592001, '{ \"enc_flag\":\"0\",\"captcha_flag\":\"0\"}', 'true', '0', ' ', ' ', NULL, NULL);
+COMMIT;
+
+-- ----------------------------
+-- Table structure for sys_post
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_post`;
+CREATE TABLE `sys_post` (
+  `post_id` bigint NOT NULL COMMENT '岗位ID',
+  `post_code` varchar(64)  NOT NULL COMMENT '岗位编码',
+  `post_name` varchar(50)  NOT NULL COMMENT '岗位名称',
+  `post_sort` int NOT NULL COMMENT '岗位排序',
+  `remark` varchar(500)  DEFAULT NULL COMMENT '岗位描述',
+  `del_flag` char(1)  NOT NULL DEFAULT '0' COMMENT '是否删除  -1:已删除  0:正常',
+  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+  `create_by` varchar(64)  NOT NULL DEFAULT '' COMMENT '创建人',
+  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
+  `update_by` varchar(64)  NOT NULL DEFAULT '' COMMENT '更新人',
+  PRIMARY KEY (`post_id`) USING BTREE
+) ENGINE=InnoDB  COMMENT='岗位信息表';
+
+-- ----------------------------
+-- Records of sys_post
+-- ----------------------------
+BEGIN;
+INSERT INTO `sys_post` VALUES (1, 'CTO', 'CTO', 0, 'CTOOO', '0', '2022-03-26 13:48:17', '', '2023-03-08 16:03:35', 'admin');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for sys_public_param
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_public_param`;
+CREATE TABLE `sys_public_param` (
+  `public_id` bigint NOT NULL COMMENT '编号',
+  `public_name` varchar(128)  DEFAULT NULL COMMENT '名称',
+  `public_key` varchar(128)  DEFAULT NULL COMMENT '键',
+  `public_value` varchar(128)  DEFAULT NULL COMMENT '值',
+  `status` char(1)  DEFAULT '0' COMMENT '状态,0禁用,1启用',
+  `validate_code` varchar(64)  DEFAULT NULL COMMENT '校验码',
+  `create_by` varchar(64)  NOT NULL DEFAULT ' ' COMMENT '创建人',
+  `update_by` varchar(64)  NOT NULL DEFAULT ' ' COMMENT '修改人',
+  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  `public_type` char(1)  DEFAULT '0' COMMENT '类型,0未知,1系统,2业务',
+  `system_flag` char(1)  DEFAULT '0' COMMENT '系统标识,0非系统,1系统',
+  `del_flag` char(1)  DEFAULT '0' COMMENT '删除标记,0未删除,1已删除',
+  PRIMARY KEY (`public_id`) USING BTREE
+) ENGINE=InnoDB  COMMENT='公共参数配置表';
+
+-- ----------------------------
+-- Records of sys_public_param
+-- ----------------------------
+BEGIN;
+INSERT INTO `sys_public_param` VALUES (1, '租户默认来源', 'TENANT_DEFAULT_ID', '1', '0', '', ' ', ' ', '2020-05-12 04:03:46', '2020-06-20 08:56:30', '2', '0', '1');
+INSERT INTO `sys_public_param` VALUES (2, '租户默认部门名称', 'TENANT_DEFAULT_DEPTNAME', '租户默认部门', '0', '', ' ', ' ', '2020-05-12 03:36:32', NULL, '2', '1', '0');
+INSERT INTO `sys_public_param` VALUES (3, '租户默认账户', 'TENANT_DEFAULT_USERNAME', 'admin', '0', '', ' ', ' ', '2020-05-12 04:05:04', NULL, '2', '1', '0');
+INSERT INTO `sys_public_param` VALUES (4, '租户默认密码', 'TENANT_DEFAULT_PASSWORD', '123456', '0', '', ' ', ' ', '2020-05-12 04:05:24', NULL, '2', '1', '0');
+INSERT INTO `sys_public_param` VALUES (5, '租户默认角色编码', 'TENANT_DEFAULT_ROLECODE', 'ROLE_ADMIN', '0', '', ' ', ' ', '2020-05-12 04:05:57', NULL, '2', '1', '0');
+INSERT INTO `sys_public_param` VALUES (6, '租户默认角色名称', 'TENANT_DEFAULT_ROLENAME', '租户默认角色', '0', '', ' ', ' ', '2020-05-12 04:06:19', NULL, '2', '1', '0');
+INSERT INTO `sys_public_param` VALUES (7, '表前缀', 'GEN_TABLE_PREFIX', 'tb_', '0', '', ' ', ' ', '2020-05-12 04:23:04', NULL, '9', '1', '0');
+INSERT INTO `sys_public_param` VALUES (8, '接口文档不显示的字段', 'GEN_HIDDEN_COLUMNS', 'tenant_id', '0', '', ' ', ' ', '2020-05-12 04:25:19', NULL, '9', '1', '0');
+INSERT INTO `sys_public_param` VALUES (9, '注册用户默认角色', 'USER_DEFAULT_ROLE', 'GENERAL_USER', '0', NULL, ' ', ' ', '2022-03-31 16:52:24', NULL, '2', '1', '0');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for sys_role
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_role`;
+CREATE TABLE `sys_role` (
+  `role_id` bigint NOT NULL COMMENT '角色ID',
+  `role_name` varchar(64)  DEFAULT NULL COMMENT '角色名称',
+  `role_code` varchar(64)  DEFAULT NULL COMMENT '角色编码',
+  `role_desc` varchar(255)  DEFAULT NULL COMMENT '角色描述',
+  `create_by` varchar(64)  NOT NULL DEFAULT ' ' COMMENT '创建人',
+  `update_by` varchar(64)  NOT NULL DEFAULT ' ' COMMENT '修改人',
+  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  `del_flag` char(1)  DEFAULT '0' COMMENT '删除标记,0未删除,1已删除',
+  PRIMARY KEY (`role_id`) USING BTREE,
+  KEY `role_idx1_role_code` (`role_code`) USING BTREE
+) ENGINE=InnoDB  COMMENT='系统角色表';
+
+-- ----------------------------
+-- Records of sys_role
+-- ----------------------------
+BEGIN;
+INSERT INTO `sys_role` VALUES (1, '管理员', 'ROLE_ADMIN', '管理员', '', 'admin', '2017-10-29 15:45:51', '2023-07-07 14:55:07', '0');
+INSERT INTO `sys_role` VALUES (2, '普通用户', 'GENERAL_USER', '普通用户', '', 'admin', '2022-03-31 17:03:15', '2023-04-03 02:28:51', '0');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for sys_role_menu
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_role_menu`;
+CREATE TABLE `sys_role_menu` (
+  `role_id` bigint NOT NULL COMMENT '角色ID',
+  `menu_id` bigint NOT NULL COMMENT '菜单ID',
+  PRIMARY KEY (`role_id`,`menu_id`) USING BTREE
+) ENGINE=InnoDB  COMMENT='角色菜单表';
+
+-- ----------------------------
+-- Records of sys_role_menu
+-- ----------------------------
+BEGIN;
+INSERT INTO `sys_role_menu` VALUES (1, 1000);
+INSERT INTO `sys_role_menu` VALUES (1, 1100);
+INSERT INTO `sys_role_menu` VALUES (1, 1101);
+INSERT INTO `sys_role_menu` VALUES (1, 1102);
+INSERT INTO `sys_role_menu` VALUES (1, 1103);
+INSERT INTO `sys_role_menu` VALUES (1, 1104);
+INSERT INTO `sys_role_menu` VALUES (1, 1200);
+INSERT INTO `sys_role_menu` VALUES (1, 1201);
+INSERT INTO `sys_role_menu` VALUES (1, 1202);
+INSERT INTO `sys_role_menu` VALUES (1, 1203);
+INSERT INTO `sys_role_menu` VALUES (1, 1300);
+INSERT INTO `sys_role_menu` VALUES (1, 1301);
+INSERT INTO `sys_role_menu` VALUES (1, 1302);
+INSERT INTO `sys_role_menu` VALUES (1, 1303);
+INSERT INTO `sys_role_menu` VALUES (1, 1304);
+INSERT INTO `sys_role_menu` VALUES (1, 1305);
+INSERT INTO `sys_role_menu` VALUES (1, 1400);
+INSERT INTO `sys_role_menu` VALUES (1, 1401);
+INSERT INTO `sys_role_menu` VALUES (1, 1402);
+INSERT INTO `sys_role_menu` VALUES (1, 1403);
+INSERT INTO `sys_role_menu` VALUES (1, 1600);
+INSERT INTO `sys_role_menu` VALUES (1, 1601);
+INSERT INTO `sys_role_menu` VALUES (1, 1602);
+INSERT INTO `sys_role_menu` VALUES (1, 1603);
+INSERT INTO `sys_role_menu` VALUES (1, 1604);
+INSERT INTO `sys_role_menu` VALUES (1, 1605);
+INSERT INTO `sys_role_menu` VALUES (1, 2000);
+INSERT INTO `sys_role_menu` VALUES (1, 2001);
+INSERT INTO `sys_role_menu` VALUES (1, 2100);
+INSERT INTO `sys_role_menu` VALUES (1, 2101);
+INSERT INTO `sys_role_menu` VALUES (1, 2102);
+INSERT INTO `sys_role_menu` VALUES (1, 2200);
+INSERT INTO `sys_role_menu` VALUES (1, 2201);
+INSERT INTO `sys_role_menu` VALUES (1, 2202);
+INSERT INTO `sys_role_menu` VALUES (1, 2203);
+INSERT INTO `sys_role_menu` VALUES (1, 2210);
+INSERT INTO `sys_role_menu` VALUES (1, 2211);
+INSERT INTO `sys_role_menu` VALUES (1, 2212);
+INSERT INTO `sys_role_menu` VALUES (1, 2213);
+INSERT INTO `sys_role_menu` VALUES (1, 2300);
+INSERT INTO `sys_role_menu` VALUES (1, 2400);
+INSERT INTO `sys_role_menu` VALUES (1, 2401);
+INSERT INTO `sys_role_menu` VALUES (1, 2402);
+INSERT INTO `sys_role_menu` VALUES (1, 2403);
+INSERT INTO `sys_role_menu` VALUES (1, 2600);
+INSERT INTO `sys_role_menu` VALUES (1, 2601);
+INSERT INTO `sys_role_menu` VALUES (1, 2800);
+INSERT INTO `sys_role_menu` VALUES (1, 2810);
+INSERT INTO `sys_role_menu` VALUES (1, 2820);
+INSERT INTO `sys_role_menu` VALUES (1, 2830);
+INSERT INTO `sys_role_menu` VALUES (1, 2840);
+INSERT INTO `sys_role_menu` VALUES (1, 2850);
+INSERT INTO `sys_role_menu` VALUES (1, 2860);
+INSERT INTO `sys_role_menu` VALUES (1, 2870);
+INSERT INTO `sys_role_menu` VALUES (1, 2871);
+INSERT INTO `sys_role_menu` VALUES (1, 2906);
+INSERT INTO `sys_role_menu` VALUES (1, 2907);
+INSERT INTO `sys_role_menu` VALUES (1, 4000);
+INSERT INTO `sys_role_menu` VALUES (1, 4001);
+INSERT INTO `sys_role_menu` VALUES (1, 4002);
+INSERT INTO `sys_role_menu` VALUES (1, 9000);
+INSERT INTO `sys_role_menu` VALUES (1, 9005);
+INSERT INTO `sys_role_menu` VALUES (1, 9006);
+INSERT INTO `sys_role_menu` VALUES (1, 9007);
+INSERT INTO `sys_role_menu` VALUES (1, 9050);
+INSERT INTO `sys_role_menu` VALUES (1, 9051);
+INSERT INTO `sys_role_menu` VALUES (1, 9052);
+INSERT INTO `sys_role_menu` VALUES (1, 9053);
+INSERT INTO `sys_role_menu` VALUES (1, 9054);
+INSERT INTO `sys_role_menu` VALUES (1, 9055);
+INSERT INTO `sys_role_menu` VALUES (1, 9056);
+INSERT INTO `sys_role_menu` VALUES (1, 9057);
+INSERT INTO `sys_role_menu` VALUES (1, 9059);
+INSERT INTO `sys_role_menu` VALUES (1, 9060);
+INSERT INTO `sys_role_menu` VALUES (1, 9061);
+INSERT INTO `sys_role_menu` VALUES (1, 9062);
+INSERT INTO `sys_role_menu` VALUES (1, 9063);
+INSERT INTO `sys_role_menu` VALUES (1, 9064);
+INSERT INTO `sys_role_menu` VALUES (1, 9065);
+INSERT INTO `sys_role_menu` VALUES (2, 4000);
+INSERT INTO `sys_role_menu` VALUES (2, 4001);
+INSERT INTO `sys_role_menu` VALUES (2, 4002);
+COMMIT;
+
+-- ----------------------------
+-- Table structure for sys_user
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_user`;
+CREATE TABLE `sys_user` (
+  `user_id` bigint NOT NULL COMMENT '用户ID',
+  `username` varchar(64)  DEFAULT NULL COMMENT '用户名',
+  `password` varchar(255)  DEFAULT NULL COMMENT '密码',
+  `salt` varchar(255)  DEFAULT NULL COMMENT '盐值',
+  `phone` varchar(20)  DEFAULT NULL COMMENT '电话号码',
+  `avatar` varchar(255)  DEFAULT NULL COMMENT '头像',
+  `nickname` varchar(64)  DEFAULT NULL COMMENT '昵称',
+  `name` varchar(64)  DEFAULT NULL COMMENT '姓名',
+  `email` varchar(128)  DEFAULT NULL COMMENT '邮箱地址',
+  `dept_id` bigint DEFAULT NULL COMMENT '所属部门ID',
+  `create_by` varchar(64)  NOT NULL DEFAULT ' ' COMMENT '创建人',
+  `update_by` varchar(64)  NOT NULL DEFAULT ' ' COMMENT '修改人',
+  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
+  `lock_flag` char(1)  DEFAULT '0' COMMENT '锁定标记,0未锁定,9已锁定',
+  `del_flag` char(1)  DEFAULT '0' COMMENT '删除标记,0未删除,1已删除',
+  `wx_openid` varchar(32)  DEFAULT NULL COMMENT '微信登录openId',
+  `mini_openid` varchar(32)  DEFAULT NULL COMMENT '小程序openId',
+  `qq_openid` varchar(32)  DEFAULT NULL COMMENT 'QQ openId',
+  `gitee_login` varchar(100)  DEFAULT NULL COMMENT '码云标识',
+  `osc_id` varchar(100)  DEFAULT NULL COMMENT '开源中国标识',
+  PRIMARY KEY (`user_id`) USING BTREE,
+  KEY `user_wx_openid` (`wx_openid`) USING BTREE,
+  KEY `user_qq_openid` (`qq_openid`) USING BTREE,
+  KEY `user_idx1_username` (`username`) USING BTREE
+) ENGINE=InnoDB  COMMENT='用户表';
+
+-- ----------------------------
+-- Records of sys_user
+-- ----------------------------
+BEGIN;
+INSERT INTO `sys_user` VALUES (1, 'admin', '$2a$10$c/Ae0pRjJtMZg3BnvVpO.eIK6WYWVbKTzqgdy3afR7w.vd.xi3Mgy', '', '17034642999', '/admin/sys-file/s3demo/7ff4ca6b7bf446f3a5a13ac016dc21af.png', '管理员', '管理员', '[email protected]', 4, ' ', 'admin', '2018-04-20 07:15:18', '2023-07-07 14:55:40', '0', '0', NULL, 'oBxPy5E-v82xWGsfzZVzkD3wEX64', NULL, 'log4j', NULL);
+COMMIT;
+
+-- ----------------------------
+-- Table structure for sys_user_post
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_user_post`;
+CREATE TABLE `sys_user_post` (
+  `user_id` bigint NOT NULL COMMENT '用户ID',
+  `post_id` bigint NOT NULL COMMENT '岗位ID',
+  PRIMARY KEY (`user_id`,`post_id`) USING BTREE
+) ENGINE=InnoDB  ROW_FORMAT=DYNAMIC COMMENT='用户与岗位关联表';
+
+-- ----------------------------
+-- Records of sys_user_post
+-- ----------------------------
+BEGIN;
+INSERT INTO `sys_user_post` VALUES (1, 1);
+COMMIT;
+
+-- ----------------------------
+-- Table structure for sys_user_role
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_user_role`;
+CREATE TABLE `sys_user_role` (
+  `user_id` bigint NOT NULL COMMENT '用户ID',
+  `role_id` bigint NOT NULL COMMENT '角色ID',
+  PRIMARY KEY (`user_id`,`role_id`) USING BTREE
+) ENGINE=InnoDB  COMMENT='用户角色表';
+
+-- ----------------------------
+-- Records of sys_user_role
+-- ----------------------------
+BEGIN;
+INSERT INTO `sys_user_role` VALUES (1, 1);
+INSERT INTO `sys_user_role` VALUES (1676492190299299842, 2);
+COMMIT;
+
+-- ----------------------------
+-- Table structure for sys_job
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_job`;
+CREATE TABLE `sys_job` (
+                           `job_id` bigint NOT NULL COMMENT '任务id',
+                           `job_name` varchar(64) NOT NULL COMMENT '任务名称',
+                           `job_group` varchar(64) NOT NULL COMMENT '任务组名',
+                           `job_order` char(1) DEFAULT '1' COMMENT '组内执行顺利,值越大执行优先级越高,最大值9,最小值1',
+                           `job_type` char(1) NOT NULL DEFAULT '1' COMMENT '1、java类;2、spring bean名称;3、rest调用;4、jar调用;9其他',
+                           `execute_path` varchar(500) DEFAULT NULL COMMENT 'job_type=3时,rest调用地址,仅支持rest get协议,需要增加String返回值,0成功,1失败;job_type=4时,jar路径;其它值为空',
+                           `class_name` varchar(500) DEFAULT NULL COMMENT 'job_type=1时,类完整路径;job_type=2时,spring bean名称;其它值为空',
+                           `method_name` varchar(500) DEFAULT NULL COMMENT '任务方法',
+                           `method_params_value` varchar(2000) DEFAULT NULL COMMENT '参数值',
+                           `cron_expression` varchar(255) DEFAULT NULL COMMENT 'cron执行表达式',
+                           `misfire_policy` varchar(20) DEFAULT '3' COMMENT '错失执行策略(1错失周期立即执行 2错失周期执行一次 3下周期执行)',
+                           `job_tenant_type` char(1) DEFAULT '1' COMMENT '1、多租户任务;2、非多租户任务',
+                           `job_status` char(1) DEFAULT '0' COMMENT '状态(1、未发布;2、运行中;3、暂停;4、删除;)',
+                           `job_execute_status` char(1) DEFAULT '0' COMMENT '状态(0正常 1异常)',
+                           `create_by` varchar(64) DEFAULT NULL COMMENT '创建者',
+                           `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+                           `update_by` varchar(64) DEFAULT '' COMMENT '更新者',
+                           `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
+                           `start_time` timestamp NULL DEFAULT NULL COMMENT '初次执行时间',
+                           `previous_time` timestamp NULL DEFAULT NULL COMMENT '上次执行时间',
+                           `next_time` timestamp NULL DEFAULT NULL COMMENT '下次执行时间',
+                           `remark` varchar(500) DEFAULT '' COMMENT '备注信息',
+                           PRIMARY KEY (`job_id`) USING BTREE,
+                           UNIQUE KEY `job_name_group_idx` (`job_name`,`job_group`) USING BTREE
+) ENGINE=InnoDB  COMMENT='定时任务调度表';
+
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_job_log`;
+CREATE TABLE `sys_job_log` (
+                               `job_log_id` bigint NOT NULL COMMENT '任务日志ID',
+                               `job_id` bigint NOT NULL COMMENT '任务id',
+                               `job_name` varchar(64)  DEFAULT NULL COMMENT '任务名称',
+                               `job_group` varchar(64)  DEFAULT NULL COMMENT '任务组名',
+                               `job_order` char(1)  DEFAULT NULL COMMENT '组内执行顺利,值越大执行优先级越高,最大值9,最小值1',
+                               `job_type` char(1)  NOT NULL DEFAULT '1' COMMENT '1、java类;2、spring bean名称;3、rest调用;4、jar调用;9其他',
+                               `execute_path` varchar(500)  DEFAULT NULL COMMENT 'job_type=3时,rest调用地址,仅支持post协议;job_type=4时,jar路径;其它值为空',
+                               `class_name` varchar(500)  DEFAULT NULL COMMENT 'job_type=1时,类完整路径;job_type=2时,spring bean名称;其它值为空',
+                               `method_name` varchar(500)  DEFAULT NULL COMMENT '任务方法',
+                               `method_params_value` varchar(2000)  DEFAULT NULL COMMENT '参数值',
+                               `cron_expression` varchar(255)  DEFAULT NULL COMMENT 'cron执行表达式',
+                               `job_message` varchar(500)  DEFAULT NULL COMMENT '日志信息',
+                               `job_log_status` char(1)  DEFAULT '0' COMMENT '执行状态(0正常 1失败)',
+                               `execute_time` varchar(30)  DEFAULT NULL COMMENT '执行时间',
+                               `exception_info` varchar(2000)  DEFAULT '' COMMENT '异常信息',
+                               `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+                               PRIMARY KEY (`job_log_id`) USING BTREE
+) ENGINE=InnoDB  COMMENT='定时任务执行日志表';
+
+
+#
+# Quartz seems to work best with the driver mm.mysql-2.0.7-bin.jar
+#
+# PLEASE consider using mysql with innodb tables to avoid locking issues
+#
+# In your Quartz properties file, you'll need to set
+# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
+#
+
+DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
+DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
+DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
+DROP TABLE IF EXISTS QRTZ_LOCKS;
+DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
+DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
+DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
+DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
+DROP TABLE IF EXISTS QRTZ_TRIGGERS;
+DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
+DROP TABLE IF EXISTS QRTZ_CALENDARS;
+
+
+CREATE TABLE QRTZ_JOB_DETAILS
+  (
+    SCHED_NAME VARCHAR(120) NOT NULL,
+    JOB_NAME  VARCHAR(200) NOT NULL,
+    JOB_GROUP VARCHAR(200) NOT NULL,
+    DESCRIPTION VARCHAR(250) NULL,
+    JOB_CLASS_NAME   VARCHAR(250) NOT NULL,
+    IS_DURABLE VARCHAR(1) NOT NULL,
+    IS_NONCONCURRENT VARCHAR(1) NOT NULL,
+    IS_UPDATE_DATA VARCHAR(1) NOT NULL,
+    REQUESTS_RECOVERY VARCHAR(1) NOT NULL,
+    JOB_DATA BLOB NULL,
+    PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
+);
+
+CREATE TABLE QRTZ_TRIGGERS
+  (
+    SCHED_NAME VARCHAR(120) NOT NULL,
+    TRIGGER_NAME VARCHAR(200) NOT NULL,
+    TRIGGER_GROUP VARCHAR(200) NOT NULL,
+    JOB_NAME  VARCHAR(200) NOT NULL,
+    JOB_GROUP VARCHAR(200) NOT NULL,
+    DESCRIPTION VARCHAR(250) NULL,
+    NEXT_FIRE_TIME BIGINT(13) NULL,
+    PREV_FIRE_TIME BIGINT(13) NULL,
+    PRIORITY INTEGER NULL,
+    TRIGGER_STATE VARCHAR(16) NOT NULL,
+    TRIGGER_TYPE VARCHAR(8) NOT NULL,
+    START_TIME BIGINT(13) NOT NULL,
+    END_TIME BIGINT(13) NULL,
+    CALENDAR_NAME VARCHAR(200) NULL,
+    MISFIRE_INSTR SMALLINT(2) NULL,
+    JOB_DATA BLOB NULL,
+    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
+    FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
+        REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
+);
+
+CREATE TABLE QRTZ_SIMPLE_TRIGGERS
+  (
+    SCHED_NAME VARCHAR(120) NOT NULL,
+    TRIGGER_NAME VARCHAR(200) NOT NULL,
+    TRIGGER_GROUP VARCHAR(200) NOT NULL,
+    REPEAT_COUNT BIGINT(7) NOT NULL,
+    REPEAT_INTERVAL BIGINT(12) NOT NULL,
+    TIMES_TRIGGERED BIGINT(10) NOT NULL,
+    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
+    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
+        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
+);
+
+CREATE TABLE QRTZ_CRON_TRIGGERS
+  (
+    SCHED_NAME VARCHAR(120) NOT NULL,
+    TRIGGER_NAME VARCHAR(200) NOT NULL,
+    TRIGGER_GROUP VARCHAR(200) NOT NULL,
+    CRON_EXPRESSION VARCHAR(200) NOT NULL,
+    TIME_ZONE_ID VARCHAR(80),
+    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
+    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
+        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
+);
+
+CREATE TABLE QRTZ_SIMPROP_TRIGGERS
+  (
+    SCHED_NAME VARCHAR(120) NOT NULL,
+    TRIGGER_NAME VARCHAR(200) NOT NULL,
+    TRIGGER_GROUP VARCHAR(200) NOT NULL,
+    STR_PROP_1 VARCHAR(512) NULL,
+    STR_PROP_2 VARCHAR(512) NULL,
+    STR_PROP_3 VARCHAR(512) NULL,
+    INT_PROP_1 INT NULL,
+    INT_PROP_2 INT NULL,
+    LONG_PROP_1 BIGINT NULL,
+    LONG_PROP_2 BIGINT NULL,
+    DEC_PROP_1 NUMERIC(13,4) NULL,
+    DEC_PROP_2 NUMERIC(13,4) NULL,
+    BOOL_PROP_1 VARCHAR(1) NULL,
+    BOOL_PROP_2 VARCHAR(1) NULL,
+    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
+    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
+    REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
+);
+
+CREATE TABLE QRTZ_BLOB_TRIGGERS
+  (
+    SCHED_NAME VARCHAR(120) NOT NULL,
+    TRIGGER_NAME VARCHAR(200) NOT NULL,
+    TRIGGER_GROUP VARCHAR(200) NOT NULL,
+    BLOB_DATA BLOB NULL,
+    PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
+    FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
+        REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
+);
+
+CREATE TABLE QRTZ_CALENDARS
+  (
+    SCHED_NAME VARCHAR(120) NOT NULL,
+    CALENDAR_NAME  VARCHAR(200) NOT NULL,
+    CALENDAR BLOB NOT NULL,
+    PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
+);
+
+CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
+  (
+    SCHED_NAME VARCHAR(120) NOT NULL,
+    TRIGGER_GROUP  VARCHAR(200) NOT NULL,
+    PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
+);
+
+CREATE TABLE QRTZ_FIRED_TRIGGERS
+  (
+    SCHED_NAME VARCHAR(120) NOT NULL,
+    ENTRY_ID VARCHAR(95) NOT NULL,
+    TRIGGER_NAME VARCHAR(200) NOT NULL,
+    TRIGGER_GROUP VARCHAR(200) NOT NULL,
+    INSTANCE_NAME VARCHAR(200) NOT NULL,
+    FIRED_TIME BIGINT(13) NOT NULL,
+    SCHED_TIME BIGINT(13) NOT NULL,
+    PRIORITY INTEGER NOT NULL,
+    STATE VARCHAR(16) NOT NULL,
+    JOB_NAME VARCHAR(200) NULL,
+    JOB_GROUP VARCHAR(200) NULL,
+    IS_NONCONCURRENT VARCHAR(1) NULL,
+    REQUESTS_RECOVERY VARCHAR(1) NULL,
+    PRIMARY KEY (SCHED_NAME,ENTRY_ID)
+);
+
+CREATE TABLE QRTZ_SCHEDULER_STATE
+  (
+    SCHED_NAME VARCHAR(120) NOT NULL,
+    INSTANCE_NAME VARCHAR(200) NOT NULL,
+    LAST_CHECKIN_TIME BIGINT(13) NOT NULL,
+    CHECKIN_INTERVAL BIGINT(13) NOT NULL,
+    PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
+);
+
+CREATE TABLE QRTZ_LOCKS
+  (
+    SCHED_NAME VARCHAR(120) NOT NULL,
+    LOCK_NAME  VARCHAR(40) NOT NULL,
+    PRIMARY KEY (SCHED_NAME,LOCK_NAME)
+);
+
+-- ----------------------------
+-- Table structure for gen_datasource_conf
+-- ----------------------------
+DROP TABLE IF EXISTS `gen_datasource_conf`;
+CREATE TABLE `gen_datasource_conf` (
+  `id` bigint NOT NULL COMMENT '主键',
+  `name` varchar(64)  DEFAULT NULL COMMENT '别名',
+  `url` varchar(255)  DEFAULT NULL COMMENT 'jdbcurl',
+  `username` varchar(64)  DEFAULT NULL COMMENT '用户名',
+  `password` varchar(64)  DEFAULT NULL COMMENT '密码',
+  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新',
+  `del_flag` char(1)  DEFAULT '0' COMMENT '删除标记',
+  `ds_type` varchar(64)  DEFAULT NULL COMMENT '数据库类型',
+  `conf_type` char(1)  DEFAULT NULL COMMENT '配置类型',
+  `ds_name` varchar(64)  DEFAULT NULL COMMENT '数据库名称',
+  `instance` varchar(64)  DEFAULT NULL COMMENT '实例',
+  `port` int DEFAULT NULL COMMENT '端口',
+  `host` varchar(128)  DEFAULT NULL COMMENT '主机',
+  PRIMARY KEY (`id`) USING BTREE
+) ENGINE=InnoDB  COMMENT='数据源表';
+
+-- ----------------------------
+-- Records of gen_datasource_conf
+-- ----------------------------
+BEGIN;
+COMMIT;
+
+-- ----------------------------
+-- Table structure for gen_field_type
+-- ----------------------------
+DROP TABLE IF EXISTS `gen_field_type`;
+CREATE TABLE `gen_field_type` (
+  `id` bigint NOT NULL COMMENT '主键',
+  `column_type` varchar(200)  DEFAULT NULL COMMENT '字段类型',
+  `attr_type` varchar(200)  DEFAULT NULL COMMENT '属性类型',
+  `package_name` varchar(200)  DEFAULT NULL COMMENT '属性包名',
+  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+  `create_by` varchar(64)  DEFAULT NULL COMMENT '创建人',
+  `update_time` datetime DEFAULT NULL COMMENT '修改时间',
+  `update_by` varchar(64)  DEFAULT NULL COMMENT '修改人',
+  `del_flag` char(1)  DEFAULT '0' COMMENT '删除标记',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `column_type` (`column_type`)
+) ENGINE=InnoDB AUTO_INCREMENT=1634915190321451010  COMMENT='字段类型管理';
+
+-- ----------------------------
+-- Records of gen_field_type
+-- ----------------------------
+BEGIN;
+INSERT INTO `gen_field_type` VALUES (1, 'datetime', 'LocalDateTime', 'java.time.LocalDateTime', '2023-02-06 08:45:10', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (2, 'date', 'LocalDate', 'java.time.LocalDate', '2023-02-06 08:45:10', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (3, 'tinyint', 'Integer', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (4, 'smallint', 'Integer', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (5, 'mediumint', 'Integer', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (6, 'int', 'Integer', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (7, 'integer', 'Integer', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (8, 'bigint', 'Long', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (9, 'float', 'Float', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (10, 'double', 'Double', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (11, 'decimal', 'BigDecimal', 'java.math.BigDecimal', '2023-02-06 08:45:11', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (12, 'bit', 'Boolean', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (13, 'char', 'String', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (14, 'varchar', 'String', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (15, 'tinytext', 'String', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (16, 'text', 'String', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (17, 'mediumtext', 'String', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (18, 'longtext', 'String', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (19, 'timestamp', 'LocalDateTime', 'java.time.LocalDateTime', '2023-02-06 08:45:11', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (20, 'NUMBER', 'Integer', NULL, '2023-02-06 08:45:11', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (21, 'BINARY_INTEGER', 'Integer', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (22, 'BINARY_FLOAT', 'Float', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (23, 'BINARY_DOUBLE', 'Double', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (24, 'VARCHAR2', 'String', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (25, 'NVARCHAR', 'String', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (26, 'NVARCHAR2', 'String', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (27, 'CLOB', 'String', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (28, 'int8', 'Long', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (29, 'int4', 'Integer', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (30, 'int2', 'Integer', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (31, 'numeric', 'BigDecimal', 'java.math.BigDecimal', '2023-02-06 08:45:12', NULL, NULL, NULL, '0');
+INSERT INTO `gen_field_type` VALUES (32, 'json', 'String', NULL, '2023-02-06 08:45:12', NULL, NULL, NULL, '0');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for gen_group
+-- ----------------------------
+DROP TABLE IF EXISTS `gen_group`;
+CREATE TABLE `gen_group` (
+  `id` bigint NOT NULL,
+  `group_name` varchar(255)  DEFAULT NULL COMMENT '分组名称',
+  `group_desc` varchar(255)  DEFAULT NULL COMMENT '分组描述',
+  `create_by` varchar(64)  NOT NULL DEFAULT ' ' COMMENT '创建人',
+  `update_by` varchar(64)  NOT NULL DEFAULT ' ' COMMENT '修改人',
+  `create_time` datetime DEFAULT NULL COMMENT '创建人',
+  `update_time` datetime DEFAULT NULL COMMENT '修改人',
+  `del_flag` char(1)  DEFAULT '0' COMMENT '删除标记',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB  COMMENT='模板分组';
+
+
+-- ----------------------------
+-- Table structure for gen_table
+-- ----------------------------
+DROP TABLE IF EXISTS `gen_table`;
+CREATE TABLE `gen_table` (
+  `id` bigint NOT NULL,
+  `table_name` varchar(200)  DEFAULT NULL COMMENT '表名',
+  `class_name` varchar(200)  DEFAULT NULL COMMENT '类名',
+  `db_type` varchar(200)  DEFAULT NULL COMMENT '数据库类型',
+  `table_comment` varchar(200)  DEFAULT NULL COMMENT '说明',
+  `author` varchar(200)  DEFAULT NULL COMMENT '作者',
+  `email` varchar(200)  DEFAULT NULL COMMENT '邮箱',
+  `package_name` varchar(200)  DEFAULT NULL COMMENT '项目包名',
+  `version` varchar(200)  DEFAULT NULL COMMENT '项目版本号',
+  `i18n` char(1)  DEFAULT '0' COMMENT '是否生成带有i18n 0 不带有 1带有',
+  `style`  bigint DEFAULT NULL COMMENT '代码风格',
+  `child_table_name` varchar(200)  DEFAULT NULL COMMENT '子表名称',
+  `main_field` varchar(200)  DEFAULT NULL COMMENT '主表关联键',
+  `child_field` varchar(200)  DEFAULT NULL COMMENT '子表关联键',
+  `generator_type` char(1)  DEFAULT '0' COMMENT '生成方式  0:zip压缩包   1:自定义目录',
+  `backend_path` varchar(500)  DEFAULT NULL COMMENT '后端生成路径',
+  `frontend_path` varchar(500)  DEFAULT NULL COMMENT '前端生成路径',
+  `module_name` varchar(200)  DEFAULT NULL COMMENT '模块名',
+  `function_name` varchar(200)  DEFAULT NULL COMMENT '功能名',
+  `form_layout` tinyint DEFAULT NULL COMMENT '表单布局  1:一列   2:两列',
+  `ds_name` varchar(200)  DEFAULT NULL COMMENT '数据源ID',
+  `baseclass_id` bigint DEFAULT NULL COMMENT '基类ID',
+  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `table_name` (`table_name`,`ds_name`) USING BTREE
+) ENGINE=InnoDB  COMMENT='代码生成表';
+
+-- ----------------------------
+-- Records of gen_table
+-- ----------------------------
+BEGIN;
+COMMIT;
+
+-- ----------------------------
+-- Table structure for gen_table_column
+-- ----------------------------
+DROP TABLE IF EXISTS `gen_table_column`;
+CREATE TABLE `gen_table_column` (
+  `id` bigint NOT NULL,
+  `ds_name` varchar(200)  DEFAULT NULL COMMENT '数据源名称',
+  `table_name` varchar(200)  DEFAULT NULL COMMENT '表名称',
+  `field_name` varchar(200)  DEFAULT NULL COMMENT '字段名称',
+  `field_type` varchar(200)  DEFAULT NULL COMMENT '字段类型',
+  `field_comment` varchar(200)  DEFAULT NULL COMMENT '字段说明',
+  `attr_name` varchar(200)  DEFAULT NULL COMMENT '属性名',
+  `attr_type` varchar(200)  DEFAULT NULL COMMENT '属性类型',
+  `package_name` varchar(200)  DEFAULT NULL COMMENT '属性包名',
+  `sort` int DEFAULT NULL COMMENT '排序',
+  `auto_fill` varchar(20)  DEFAULT NULL COMMENT '自动填充  DEFAULT、INSERT、UPDATE、INSERT_UPDATE',
+  `primary_pk` char(1)  DEFAULT '0' COMMENT '主键 0:否  1:是',
+  `base_field` char(1)  DEFAULT '0' COMMENT '基类字段 0:否  1:是',
+  `form_item` char(1)  DEFAULT '0' COMMENT '表单项 0:否  1:是',
+  `form_required` char(1)  DEFAULT '0' COMMENT '表单必填 0:否  1:是',
+  `form_type` varchar(200)  DEFAULT NULL COMMENT '表单类型',
+  `form_validator` varchar(200)  DEFAULT NULL COMMENT '表单效验',
+  `grid_item` char(1)  DEFAULT '0' COMMENT '列表项 0:否  1:是',
+  `grid_sort` char(1)  DEFAULT '0' COMMENT '列表排序 0:否  1:是',
+  `query_item` char(1)  DEFAULT '0' COMMENT '查询项 0:否  1:是',
+  `query_type` varchar(200)  DEFAULT NULL COMMENT '查询方式',
+  `query_form_type` varchar(200)  DEFAULT NULL COMMENT '查询表单类型',
+  `field_dict` varchar(200)  DEFAULT NULL COMMENT '字典类型',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB  COMMENT='代码生成表字段';
+
+-- ----------------------------
+-- Records of gen_table_column
+-- ----------------------------
+BEGIN;
+COMMIT;
+
+-- ----------------------------
+-- Table structure for gen_template
+-- ----------------------------
+DROP TABLE IF EXISTS `gen_template`;
+CREATE TABLE `gen_template` (
+  `id` bigint NOT NULL COMMENT '主键',
+  `template_name` varchar(255)  NOT NULL COMMENT '模板名称',
+  `generator_path` varchar(255)  NOT NULL COMMENT '模板路径',
+  `template_desc` varchar(255)  NOT NULL COMMENT '模板描述',
+  `template_code` text  NOT NULL COMMENT '模板代码',
+  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新',
+  `del_flag` char(1)  NOT NULL DEFAULT '0' COMMENT '删除标记',
+  `create_by` varchar(64)  NOT NULL DEFAULT ' ' COMMENT '创建人',
+  `update_by` varchar(64)  NOT NULL DEFAULT ' ' COMMENT '修改人',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB  COMMENT='模板';
+
+
+-- ----------------------------
+-- Table structure for gen_template_group
+-- ----------------------------
+DROP TABLE IF EXISTS `gen_template_group`;
+CREATE TABLE `gen_template_group` (
+  `group_id` bigint NOT NULL COMMENT '分组id',
+  `template_id` bigint NOT NULL COMMENT '模板id',
+  PRIMARY KEY (`group_id`,`template_id`)
+) ENGINE=InnoDB  COMMENT='模板分组关联表';
+
+SET FOREIGN_KEY_CHECKS = 1;

+ 325 - 0
db/pig_config.sql

@@ -0,0 +1,325 @@
+DROP DATABASE IF EXISTS `pig_config`;
+
+CREATE DATABASE  `pig_config` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
+
+USE pig_config;
+
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+-- ----------------------------
+-- Table structure for config_info
+-- ----------------------------
+DROP TABLE IF EXISTS `config_info`;
+CREATE TABLE `config_info` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
+  `data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id',
+  `group_id` varchar(128) COLLATE utf8_bin DEFAULT NULL COMMENT 'group_id',
+  `content` longtext COLLATE utf8_bin NOT NULL COMMENT 'content',
+  `md5` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'md5',
+  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
+  `src_user` text COLLATE utf8_bin COMMENT 'source user',
+  `src_ip` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT 'source ip',
+  `app_name` varchar(128) COLLATE utf8_bin DEFAULT NULL COMMENT 'app_name',
+  `tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT '租户字段',
+  `c_desc` varchar(256) COLLATE utf8_bin DEFAULT NULL COMMENT 'configuration description',
+  `c_use` varchar(64) COLLATE utf8_bin DEFAULT NULL COMMENT 'configuration usage',
+  `effect` varchar(64) COLLATE utf8_bin DEFAULT NULL COMMENT '配置生效的描述',
+  `type` varchar(64) COLLATE utf8_bin DEFAULT NULL COMMENT '配置的类型',
+  `c_schema` text COLLATE utf8_bin COMMENT '配置的模式',
+  `encrypted_data_key` varchar(1024) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT '密钥',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';
+
+-- ----------------------------
+-- Records of config_info
+-- ----------------------------
+BEGIN;
+INSERT INTO `config_info` (`id`, `data_id`, `group_id`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `app_name`, `tenant_id`, `c_desc`, `c_use`, `effect`, `type`, `c_schema`, `encrypted_data_key`) VALUES (8, 'application-dev.yml', 'DEFAULT_GROUP', '# 配置文件加密根密码\njasypt:\n  encryptor:\n    password: pig\n    algorithm: PBEWithMD5AndDES\n    iv-generator-classname: org.jasypt.iv.NoIvGenerator\n    \n# Spring 相关\nspring:\n  cache:\n    type: redis\n  data:\n    redis:\n      host: ${REDIS_HOST:127.0.0.1}\n  cloud:\n    sentinel:\n      eager: true\n      transport:\n        dashboard: pig-sentinel:5003\n    openfeign:\n      sentinel:\n        enabled: true\n      okhttp:\n        enabled: true\n      httpclient:\n        enabled: false\n      compression:\n        request:\n          enabled: true\n        response:\n          enabled: true\n\n# 暴露监控端点\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"  \n  endpoint:\n    health:\n      show-details: ALWAYS\n\n# mybaits-plus配置\nmybatis-plus:\n  mapper-locations: classpath:/mapper/*Mapper.xml\n  global-config:\n    banner: false\n    db-config:\n      id-type: auto\n      table-underline: true\n      logic-delete-value: 1\n      logic-not-delete-value: 0\n  type-handlers-package: com.pig4cloud.pig.common.mybatis.handler\n  configuration:\n    map-underscore-to-camel-case: true\n    shrink-whitespaces-in-sql: true\n\n# 短信插件配置:https://www.yuque.com/vxixfq/pig/zw8udk\nsms:\n  is-print: false # 是否打印日志\n  config-type: yaml # 配置类型,yaml', '3210108767554f4f3c492d0f63d0148d', '2025-05-16 12:48:39', '2025-06-29 10:17:45', 'nacos', '0:0:0:0:0:0:0:1', '', 'public', '', NULL, NULL, 'yaml', NULL, '');
+INSERT INTO `config_info` (`id`, `data_id`, `group_id`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `app_name`, `tenant_id`, `c_desc`, `c_use`, `effect`, `type`, `c_schema`, `encrypted_data_key`) VALUES (2, 'pig-auth-dev.yml', 'DEFAULT_GROUP', '# 数据源\nspring:\n  freemarker:\n    allow-request-override: false\n    allow-session-override: false\n    cache: true\n    charset: UTF-8\n    check-template-location: true\n    content-type: text/html\n    enabled: true\n    request-context-attribute: request\n    expose-request-attributes: false\n    expose-session-attributes: false\n    expose-spring-macro-helpers: true\n    prefer-file-system-access: true\n    suffix: .ftl\n    template-loader-path: classpath:/templates/\n\n\nsecurity:\n  encode-key: \'thanks,pig4cloud\'\n  ignore-clients:\n    - test\n    - client\n    - open\n    - app', 'b4a660ece61e8180b4940a0770eddfee', '2025-01-30 16:50:04', '2025-01-30 16:50:04', 'nacos', '127.0.0.1', '', '', NULL, NULL, NULL, 'yaml', NULL, '');
+INSERT INTO `config_info` (`id`, `data_id`, `group_id`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `app_name`, `tenant_id`, `c_desc`, `c_use`, `effect`, `type`, `c_schema`, `encrypted_data_key`) VALUES (3, 'pig-codegen-dev.yml', 'DEFAULT_GROUP', '# 数据源配置\nspring:\n  datasource:\n    type: com.zaxxer.hikari.HikariDataSource\n    driver-class-name: com.mysql.cj.jdbc.Driver\n    username: root\n    password: root\n    url: jdbc:mysql://${MYSQL_HOST:127.0.0.1}:${MYSQL_PORT:3306}/${MYSQL_DB:pig}?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true\n  resources:\n    static-locations: classpath:/static/,classpath:/views/\n', '859849b2ec6377d635b2fc30250607e3', '2025-01-30 16:50:04', '2025-01-30 16:50:04', 'nacos', '127.0.0.1', '', '', NULL, NULL, NULL, 'yaml', NULL, '');
+INSERT INTO `config_info` (`id`, `data_id`, `group_id`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `app_name`, `tenant_id`, `c_desc`, `c_use`, `effect`, `type`, `c_schema`, `encrypted_data_key`) VALUES (4, 'pig-gateway-dev.yml', 'DEFAULT_GROUP', 'spring:\n  cloud:\n    gateway:\n      server:\n        webflux:\n          routes:\n            # 认证中心\n            - id: pig-auth\n              uri: lb://pig-auth\n              predicates:\n                - Path=/auth/**\n            #UPMS 模块\n            - id: pig-upms-biz\n              uri: lb://pig-upms-biz\n              predicates:\n                - Path=/admin/**\n              filters:\n                # 限流配置\n                - name: RequestRateLimiter\n                  args:\n                    key-resolver: \'#{@remoteAddrKeyResolver}\'\n                    redis-rate-limiter.replenishRate: 100\n                    redis-rate-limiter.burstCapacity: 200\n            # 代码生成模块\n            - id: pig-codegen\n              uri: lb://pig-codegen\n              predicates:\n                - Path=/gen/**\n            # 代码生成模块\n            - id: pig-quartz\n              uri: lb://pig-quartz\n              predicates:\n                - Path=/job/**\n            # 固定路由转发配置 无修改\n            - id: openapi\n              uri: lb://pig-gateway\n              predicates:\n                - Path=/v3/api-docs/**\n              filters:\n                - RewritePath=/v3/api-docs/(?<path>.*), /$\\{path}/$\\{path}/v3/api-docs', '53ace4035d810f07e3767d94e1e68379', '2025-01-30 16:50:04', '2025-05-30 08:36:27', 'nacos_namespace_migrate', '0:0:0:0:0:0:0:1', '', '', '', NULL, NULL, 'yaml', NULL, '');
+INSERT INTO `config_info` (`id`, `data_id`, `group_id`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `app_name`, `tenant_id`, `c_desc`, `c_use`, `effect`, `type`, `c_schema`, `encrypted_data_key`) VALUES (5, 'pig-monitor-dev.yml', 'DEFAULT_GROUP', 'spring:\n  autoconfigure:\n    exclude: com.pig4cloud.pig.common.core.config.JacksonConfiguration\n  # 安全配置\n  security:\n    user:\n      name: ENC(8Hk2ILNJM8UTOuW/Xi75qg==)     # pig\n      password: ENC(o6cuPFfUevmTbkmBnE67Ow====) # pig\n', '650bdfa15f60f3faa84dfe6e6878b8cf', '2025-01-30 16:50:04', '2025-01-30 16:50:04', 'nacos', '127.0.0.1', '', '', NULL, NULL, NULL, 'yaml', NULL, '');
+INSERT INTO `config_info` (`id`, `data_id`, `group_id`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `app_name`, `tenant_id`, `c_desc`, `c_use`, `effect`, `type`, `c_schema`, `encrypted_data_key`) VALUES (6, 'pig-upms-biz-dev.yml', 'DEFAULT_GROUP', '# 数据源\nspring:\n  datasource:\n    type: com.zaxxer.hikari.HikariDataSource\n    driver-class-name: com.mysql.cj.jdbc.Driver\n    username: root\n    password: root\n    url: jdbc:mysql://${MYSQL_HOST:127.0.0.1}:${MYSQL_PORT:3306}/${MYSQL_DB:pig}?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true\n\n# 文件上传相关 支持阿里云、华为云、腾讯、minio\nfile:\n  bucketName: s3demo \n  local:\n    enable: true\n    base-path: /Users/lengleng/Downloads/img', '1fbd7a706766e450b16a802c574b52dd', '2025-01-30 16:50:04', '2025-01-30 16:50:04', 'nacos', '127.0.0.1', '', '', NULL, NULL, NULL, 'yaml', NULL, '');
+INSERT INTO `config_info` (`id`, `data_id`, `group_id`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `app_name`, `tenant_id`, `c_desc`, `c_use`, `effect`, `type`, `c_schema`, `encrypted_data_key`) VALUES (7, 'pig-quartz-dev.yml', 'DEFAULT_GROUP', 'spring:\n  datasource:\n    type: com.zaxxer.hikari.HikariDataSource\n    driver-class-name: com.mysql.cj.jdbc.Driver\n    username: root\n    password: root\n    url: jdbc:mysql://${MYSQL_HOST:127.0.0.1}:${MYSQL_PORT:3306}/${MYSQL_DB:pig}?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true\n', 'c8a5c23b676483d758afc80d7b601ca3', '2025-01-30 16:50:04', '2025-01-30 16:50:04', 'nacos', '127.0.0.1', '', '', NULL, NULL, NULL, 'yaml', NULL, '');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for config_info_beta
+-- ----------------------------
+DROP TABLE IF EXISTS `config_info_beta`;
+CREATE TABLE `config_info_beta` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
+  `data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id',
+  `group_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'group_id',
+  `app_name` varchar(128) COLLATE utf8_bin DEFAULT NULL COMMENT 'app_name',
+  `content` longtext COLLATE utf8_bin NOT NULL COMMENT 'content',
+  `beta_ips` varchar(1024) COLLATE utf8_bin DEFAULT NULL COMMENT 'betaIps',
+  `md5` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'md5',
+  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
+  `src_user` text COLLATE utf8_bin COMMENT 'source user',
+  `src_ip` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT 'source ip',
+  `tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT '租户字段',
+  `encrypted_data_key` varchar(1024) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT '密钥',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta';
+
+-- ----------------------------
+-- Records of config_info_beta
+-- ----------------------------
+BEGIN;
+COMMIT;
+
+-- ----------------------------
+-- Table structure for config_info_gray
+-- ----------------------------
+DROP TABLE IF EXISTS `config_info_gray`;
+CREATE TABLE `config_info_gray` (
+  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
+  `data_id` varchar(255) NOT NULL COMMENT 'data_id',
+  `group_id` varchar(128) NOT NULL COMMENT 'group_id',
+  `content` longtext NOT NULL COMMENT 'content',
+  `md5` varchar(32) DEFAULT NULL COMMENT 'md5',
+  `src_user` text COMMENT 'src_user',
+  `src_ip` varchar(100) DEFAULT NULL COMMENT 'src_ip',
+  `gmt_create` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'gmt_create',
+  `gmt_modified` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'gmt_modified',
+  `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
+  `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
+  `gray_name` varchar(128) NOT NULL COMMENT 'gray_name',
+  `gray_rule` text NOT NULL COMMENT 'gray_rule',
+  `encrypted_data_key` varchar(256) NOT NULL DEFAULT '' COMMENT 'encrypted_data_key',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_configinfogray_datagrouptenantgray` (`data_id`,`group_id`,`tenant_id`,`gray_name`),
+  KEY `idx_dataid_gmt_modified` (`data_id`,`gmt_modified`),
+  KEY `idx_gmt_modified` (`gmt_modified`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='config_info_gray';
+
+-- ----------------------------
+-- Records of config_info_gray
+-- ----------------------------
+BEGIN;
+COMMIT;
+
+-- ----------------------------
+-- Table structure for config_info_tag
+-- ----------------------------
+DROP TABLE IF EXISTS `config_info_tag`;
+CREATE TABLE `config_info_tag` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
+  `data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id',
+  `group_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'group_id',
+  `tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT 'tenant_id',
+  `tag_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'tag_id',
+  `app_name` varchar(128) COLLATE utf8_bin DEFAULT NULL COMMENT 'app_name',
+  `content` longtext COLLATE utf8_bin NOT NULL COMMENT 'content',
+  `md5` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'md5',
+  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
+  `src_user` text COLLATE utf8_bin COMMENT 'source user',
+  `src_ip` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT 'source ip',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag';
+
+-- ----------------------------
+-- Records of config_info_tag
+-- ----------------------------
+BEGIN;
+COMMIT;
+
+-- ----------------------------
+-- Table structure for config_tags_relation
+-- ----------------------------
+DROP TABLE IF EXISTS `config_tags_relation`;
+CREATE TABLE `config_tags_relation` (
+  `id` bigint NOT NULL COMMENT 'id',
+  `tag_name` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'tag_name',
+  `tag_type` varchar(64) COLLATE utf8_bin DEFAULT NULL COMMENT 'tag_type',
+  `data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id',
+  `group_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'group_id',
+  `tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT 'tenant_id',
+  `nid` bigint NOT NULL AUTO_INCREMENT COMMENT 'nid, 自增长标识',
+  PRIMARY KEY (`nid`),
+  UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),
+  KEY `idx_tenant_id` (`tenant_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation';
+
+-- ----------------------------
+-- Records of config_tags_relation
+-- ----------------------------
+BEGIN;
+COMMIT;
+
+-- ----------------------------
+-- Table structure for group_capacity
+-- ----------------------------
+DROP TABLE IF EXISTS `group_capacity`;
+CREATE TABLE `group_capacity` (
+  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `group_id` varchar(128) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群',
+  `quota` int unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
+  `usage` int unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
+  `max_size` int unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
+  `max_aggr_count` int unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值',
+  `max_aggr_size` int unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
+  `max_history_count` int unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
+  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_group_id` (`group_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';
+
+-- ----------------------------
+-- Records of group_capacity
+-- ----------------------------
+BEGIN;
+COMMIT;
+
+-- ----------------------------
+-- Table structure for his_config_info
+-- ----------------------------
+DROP TABLE IF EXISTS `his_config_info`;
+CREATE TABLE `his_config_info` (
+  `id` bigint unsigned NOT NULL COMMENT 'id',
+  `nid` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'nid, 自增标识',
+  `data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id',
+  `group_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'group_id',
+  `app_name` varchar(128) COLLATE utf8_bin DEFAULT NULL COMMENT 'app_name',
+  `content` longtext COLLATE utf8_bin NOT NULL COMMENT 'content',
+  `md5` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'md5',
+  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
+  `src_user` text COLLATE utf8_bin COMMENT 'source user',
+  `src_ip` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT 'source ip',
+  `op_type` char(10) COLLATE utf8_bin DEFAULT NULL COMMENT 'operation type',
+  `tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT '租户字段',
+  `encrypted_data_key` varchar(1024) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT '密钥',
+  `publish_type` varchar(50) COLLATE utf8_bin DEFAULT 'formal' COMMENT 'publish type gray or formal',
+  `gray_name` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT 'gray name',
+  `ext_info` longtext COLLATE utf8_bin COMMENT 'ext info',
+  PRIMARY KEY (`nid`),
+  KEY `idx_gmt_create` (`gmt_create`),
+  KEY `idx_gmt_modified` (`gmt_modified`),
+  KEY `idx_did` (`data_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造';
+
+-- ----------------------------
+-- Records of his_config_info
+-- ----------------------------
+BEGIN;
+INSERT INTO `his_config_info` (`id`, `nid`, `data_id`, `group_id`, `app_name`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `op_type`, `tenant_id`, `encrypted_data_key`, `publish_type`, `gray_name`, `ext_info`) VALUES (0, 1, 'application-dev.yml', 'DEFAULT_GROUP', '', '# 配置文件加密根密码\njasypt:\n  encryptor:\n    password: pig\n    algorithm: PBEWithMD5AndDES\n    iv-generator-classname: org.jasypt.iv.NoIvGenerator\n    \n# Spring 相关\nspring:\n  cache:\n    type: redis\n  data:\n    redis:\n      host: ${REDIS_HOST:127.0.0.1}\n  cloud:\n    sentinel:\n      eager: true\n      transport:\n        dashboard: pig-sentinel:5003\n    openfeign:\n      sentinel:\n        enabled: true\n      okhttp:\n        enabled: true\n      httpclient:\n        enabled: false\n      compression:\n        request:\n          enabled: true\n        response:\n          enabled: true\n\n# 暴露监控端点\nmanagement:\n  endpoints:\n    web:\n      exposure:\n        include: \"*\"  \n  endpoint:\n    health:\n      show-details: ALWAYS\n\n# mybaits-plus配置\nmybatis-plus:\n  mapper-locations: classpath:/mapper/*Mapper.xml\n  global-config:\n    banner: false\n    db-config:\n      id-type: auto\n      table-underline: true\n      logic-delete-value: 1\n      logic-not-delete-value: 0\n  type-handlers-package: com.pig4cloud.pig.common.mybatis.handler\n  configuration:\n    map-underscore-to-camel-case: true\n    shrink-whitespaces-in-sql: true\n\n# swagger 配置\nswagger:\n  enabled: true\n  title: Pig Swagger API\n  gateway: http://${GATEWAY_HOST:pig-gateway}:${GATEWAY-PORT:9999}\n  token-url: ${swagger.gateway}/auth/oauth2/token\n  scope: server\n  services:\n    pig-upms-biz: admin\n    pig-codegen: gen', '83cbbb61027b81b533bfde8b789e91e0', '2025-01-30 16:50:03', '2025-01-30 16:50:04', 'nacos', '127.0.0.1', 'I', '', '', 'formal', '', '{\"src_user\":\"nacos\",\"type\":\"yaml\"}');
+INSERT INTO `his_config_info` (`id`, `nid`, `data_id`, `group_id`, `app_name`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `op_type`, `tenant_id`, `encrypted_data_key`, `publish_type`, `gray_name`, `ext_info`) VALUES (0, 2, 'pig-auth-dev.yml', 'DEFAULT_GROUP', '', '# 数据源\nspring:\n  freemarker:\n    allow-request-override: false\n    allow-session-override: false\n    cache: true\n    charset: UTF-8\n    check-template-location: true\n    content-type: text/html\n    enabled: true\n    request-context-attribute: request\n    expose-request-attributes: false\n    expose-session-attributes: false\n    expose-spring-macro-helpers: true\n    prefer-file-system-access: true\n    suffix: .ftl\n    template-loader-path: classpath:/templates/\n\n\nsecurity:\n  encode-key: \'thanks,pig4cloud\'\n  ignore-clients:\n    - test\n    - client\n    - open\n    - app', 'b4a660ece61e8180b4940a0770eddfee', '2025-01-30 16:50:03', '2025-01-30 16:50:04', 'nacos', '127.0.0.1', 'I', '', '', 'formal', '', '{\"src_user\":\"nacos\",\"type\":\"yaml\"}');
+INSERT INTO `his_config_info` (`id`, `nid`, `data_id`, `group_id`, `app_name`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `op_type`, `tenant_id`, `encrypted_data_key`, `publish_type`, `gray_name`, `ext_info`) VALUES (0, 3, 'pig-codegen-dev.yml', 'DEFAULT_GROUP', '', '# 数据源配置\nspring:\n  datasource:\n    type: com.zaxxer.hikari.HikariDataSource\n    driver-class-name: com.mysql.cj.jdbc.Driver\n    username: root\n    password: root\n    url: jdbc:mysql://${MYSQL_HOST:127.0.0.1}:${MYSQL_PORT:3306}/${MYSQL_DB:pig}?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true\n  resources:\n    static-locations: classpath:/static/,classpath:/views/\n', '859849b2ec6377d635b2fc30250607e3', '2025-01-30 16:50:03', '2025-01-30 16:50:04', 'nacos', '127.0.0.1', 'I', '', '', 'formal', '', '{\"src_user\":\"nacos\",\"type\":\"yaml\"}');
+INSERT INTO `his_config_info` (`id`, `nid`, `data_id`, `group_id`, `app_name`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `op_type`, `tenant_id`, `encrypted_data_key`, `publish_type`, `gray_name`, `ext_info`) VALUES (0, 4, 'pig-gateway-dev.yml', 'DEFAULT_GROUP', '', 'spring:\n  cloud:\n    gateway:\n      locator:\n        enabled: true\n      routes:\n        # 认证中心\n        - id: pig-auth\n          uri: lb://pig-auth\n          predicates:\n            - Path=/auth/**\n        #UPMS 模块\n        - id: pig-upms-biz\n          uri: lb://pig-upms-biz\n          predicates:\n            - Path=/admin/**\n          filters:\n            # 限流配置\n            - name: RequestRateLimiter\n              args:\n                key-resolver: \'#{@remoteAddrKeyResolver}\'\n                redis-rate-limiter.replenishRate: 100\n                redis-rate-limiter.burstCapacity: 200\n        # 代码生成模块\n        - id: pig-codegen\n          uri: lb://pig-codegen\n          predicates:\n            - Path=/gen/**\n        # 代码生成模块\n        - id: pig-quartz\n          uri: lb://pig-quartz\n          predicates:\n            - Path=/job/**\n        # 固定路由转发配置 无修改\n        - id: openapi\n          uri: lb://pig-gateway\n          predicates:\n            - Path=/v3/api-docs/**\n          filters:\n            - RewritePath=/v3/api-docs/(?<path>.*), /$\\{path}/$\\{path}/v3/api-docs', '7940b3e89a9489e0844af6b1dc91d65b', '2025-01-30 16:50:03', '2025-01-30 16:50:04', 'nacos', '127.0.0.1', 'I', '', '', 'formal', '', '{\"src_user\":\"nacos\",\"type\":\"yaml\"}');
+INSERT INTO `his_config_info` (`id`, `nid`, `data_id`, `group_id`, `app_name`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `op_type`, `tenant_id`, `encrypted_data_key`, `publish_type`, `gray_name`, `ext_info`) VALUES (0, 5, 'pig-monitor-dev.yml', 'DEFAULT_GROUP', '', 'spring:\n  autoconfigure:\n    exclude: com.pig4cloud.pig.common.core.config.JacksonConfiguration\n  # 安全配置\n  security:\n    user:\n      name: ENC(8Hk2ILNJM8UTOuW/Xi75qg==)     # pig\n      password: ENC(o6cuPFfUevmTbkmBnE67Ow====) # pig\n', '650bdfa15f60f3faa84dfe6e6878b8cf', '2025-01-30 16:50:03', '2025-01-30 16:50:04', 'nacos', '127.0.0.1', 'I', '', '', 'formal', '', '{\"src_user\":\"nacos\",\"type\":\"yaml\"}');
+INSERT INTO `his_config_info` (`id`, `nid`, `data_id`, `group_id`, `app_name`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `op_type`, `tenant_id`, `encrypted_data_key`, `publish_type`, `gray_name`, `ext_info`) VALUES (0, 6, 'pig-upms-biz-dev.yml', 'DEFAULT_GROUP', '', '# 数据源\nspring:\n  datasource:\n    type: com.zaxxer.hikari.HikariDataSource\n    driver-class-name: com.mysql.cj.jdbc.Driver\n    username: root\n    password: root\n    url: jdbc:mysql://${MYSQL_HOST:127.0.0.1}:${MYSQL_PORT:3306}/${MYSQL_DB:pig}?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true\n\n# 文件上传相关 支持阿里云、华为云、腾讯、minio\nfile:\n  bucketName: s3demo \n  local:\n    enable: true\n    base-path: /Users/lengleng/Downloads/img', '1fbd7a706766e450b16a802c574b52dd', '2025-01-30 16:50:03', '2025-01-30 16:50:04', 'nacos', '127.0.0.1', 'I', '', '', 'formal', '', '{\"src_user\":\"nacos\",\"type\":\"yaml\"}');
+INSERT INTO `his_config_info` (`id`, `nid`, `data_id`, `group_id`, `app_name`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `op_type`, `tenant_id`, `encrypted_data_key`, `publish_type`, `gray_name`, `ext_info`) VALUES (0, 7, 'pig-quartz-dev.yml', 'DEFAULT_GROUP', '', 'spring:\n  datasource:\n    type: com.zaxxer.hikari.HikariDataSource\n    driver-class-name: com.mysql.cj.jdbc.Driver\n    username: root\n    password: root\n    url: jdbc:mysql://${MYSQL_HOST:127.0.0.1}:${MYSQL_PORT:3306}/${MYSQL_DB:pig}?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true\n', 'c8a5c23b676483d758afc80d7b601ca3', '2025-01-30 16:50:03', '2025-01-30 16:50:04', 'nacos', '127.0.0.1', 'I', '', '', 'formal', '', '{\"src_user\":\"nacos\",\"type\":\"yaml\"}');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for permissions
+-- ----------------------------
+DROP TABLE IF EXISTS `permissions`;
+CREATE TABLE `permissions` (
+  `role` varchar(50) NOT NULL COMMENT 'role',
+  `resource` varchar(128) NOT NULL COMMENT 'resource',
+  `action` varchar(8) NOT NULL COMMENT 'action',
+  UNIQUE KEY `uk_role_permission` (`role`,`resource`,`action`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- ----------------------------
+-- Records of permissions
+-- ----------------------------
+BEGIN;
+COMMIT;
+
+-- ----------------------------
+-- Table structure for roles
+-- ----------------------------
+DROP TABLE IF EXISTS `roles`;
+CREATE TABLE `roles` (
+  `username` varchar(50) NOT NULL COMMENT 'username',
+  `role` varchar(50) NOT NULL COMMENT 'role',
+  UNIQUE KEY `idx_user_role` (`username`,`role`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- ----------------------------
+-- Records of roles
+-- ----------------------------
+BEGIN;
+INSERT INTO `roles` (`username`, `role`) VALUES ('nacos', 'ROLE_ADMIN');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for tenant_capacity
+-- ----------------------------
+DROP TABLE IF EXISTS `tenant_capacity`;
+CREATE TABLE `tenant_capacity` (
+  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `tenant_id` varchar(128) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT 'Tenant ID',
+  `quota` int unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
+  `usage` int unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
+  `max_size` int unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
+  `max_aggr_count` int unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数',
+  `max_aggr_size` int unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
+  `max_history_count` int unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
+  `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_tenant_id` (`tenant_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表';
+
+-- ----------------------------
+-- Records of tenant_capacity
+-- ----------------------------
+BEGIN;
+COMMIT;
+
+-- ----------------------------
+-- Table structure for tenant_info
+-- ----------------------------
+DROP TABLE IF EXISTS `tenant_info`;
+CREATE TABLE `tenant_info` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
+  `kp` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'kp',
+  `tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT 'tenant_id',
+  `tenant_name` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT 'tenant_name',
+  `tenant_desc` varchar(256) COLLATE utf8_bin DEFAULT NULL COMMENT 'tenant_desc',
+  `create_source` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'create_source',
+  `gmt_create` bigint NOT NULL COMMENT '创建时间',
+  `gmt_modified` bigint NOT NULL COMMENT '修改时间',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),
+  KEY `idx_tenant_id` (`tenant_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info';
+
+-- ----------------------------
+-- Records of tenant_info
+-- ----------------------------
+BEGIN;
+COMMIT;
+
+-- ----------------------------
+-- Table structure for users
+-- ----------------------------
+DROP TABLE IF EXISTS `users`;
+CREATE TABLE `users` (
+  `username` varchar(50) NOT NULL COMMENT 'username',
+  `password` varchar(500) NOT NULL COMMENT 'password',
+  `enabled` tinyint(1) NOT NULL COMMENT 'enabled',
+  PRIMARY KEY (`username`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- ----------------------------
+-- Records of users
+-- ----------------------------
+BEGIN;
+INSERT INTO `users` (`username`, `password`, `enabled`) VALUES ('nacos', '$2a$10$W6PKgRTzXUp6R/NY853Kn.nRaIcX3whIMTZ/WWkNqo2MTOeSBjKJq', 1);
+COMMIT;
+
+SET FOREIGN_KEY_CHECKS = 1;

+ 129 - 0
docker-compose.yml

@@ -0,0 +1,129 @@
+services:
+  pig-mysql:
+    build:
+      context: ./db
+    environment:
+      MYSQL_ROOT_HOST: "%"
+      MYSQL_ROOT_PASSWORD: root
+    restart: always
+    container_name: pig-mysql
+    image: pig-mysql
+    ports:
+      - 33306:3306
+    networks:
+      - spring_cloud_default
+
+  pig-redis:
+    image: registry.cn-hangzhou.aliyuncs.com/dockerhub_mirror/redis
+    ports:
+      - 36379:6379
+    restart: always
+    container_name: pig-redis
+    hostname: pig-redis
+    networks:
+      - spring_cloud_default
+
+  pig-register:
+    build:
+      context: ./pig-register
+    restart: always
+    ports:
+      - 8848:8848
+      - 9848:9848
+      - 8080:8080
+    environment:
+      MYSQL_HOST: pig-mysql
+      REDIS_HOST: pig-redis
+    container_name: pig-register
+    hostname: pig-register
+    image: pig-register
+    networks:
+      - spring_cloud_default
+
+  pig-gateway:
+    build:
+      context: ./pig-gateway
+    restart: always
+    ports:
+      - 9999:9999
+    container_name: pig-gateway
+    hostname: pig-gateway
+    image: pig-gateway
+    environment:
+      REDIS_HOST: pig-redis
+      NACOS_HOST: pig-register
+    networks:
+      - spring_cloud_default
+
+  pig-auth:
+    build:
+      context: ./pig-auth
+    restart: always
+    container_name: pig-auth
+    hostname: pig-auth
+    image: pig-auth
+    environment:
+      REDIS_HOST: pig-redis
+      NACOS_HOST: pig-register
+    networks:
+      - spring_cloud_default
+
+  pig-upms:
+    build:
+      context: ./pig-upms/pig-upms-biz
+    restart: always
+    container_name: pig-upms
+    hostname: pig-upms
+    image: pig-upms
+    environment:
+      MYSQL_HOST: pig-mysql
+      REDIS_HOST: pig-redis
+      NACOS_HOST: pig-register
+    networks:
+      - spring_cloud_default
+
+  pig-monitor:
+    build:
+      context: ./pig-visual/pig-monitor
+    restart: always
+    ports:
+      - 5001:5001
+    container_name: pig-monitor
+    hostname: pig-monitor
+    image: pig-monitor
+    environment:
+      NACOS_HOST: pig-register
+    networks:
+      - spring_cloud_default
+
+  pig-codegen:
+    build:
+      context: ./pig-visual/pig-codegen
+    restart: always
+    container_name: pig-codegen
+    hostname: pig-codegen
+    image: pig-codegen
+    environment:
+      MYSQL_HOST: pig-mysql
+      REDIS_HOST: pig-redis
+      NACOS_HOST: pig-register
+    networks:
+      - spring_cloud_default
+
+  pig-quartz:
+    build:
+      context: ./pig-visual/pig-quartz
+    restart: always
+    image: pig-quartz
+    container_name: pig-quartz
+    environment:
+      MYSQL_HOST: pig-mysql
+      REDIS_HOST: pig-redis
+      NACOS_HOST: pig-register
+    networks:
+      - spring_cloud_default
+
+networks:
+  spring_cloud_default:
+    name:  spring_cloud_default
+    driver: bridge

+ 13 - 0
pig-auth/Dockerfile

@@ -0,0 +1,13 @@
+FROM registry.cn-hangzhou.aliyuncs.com/dockerhub_mirror/java:21-anolis
+
+WORKDIR /pig-auth
+
+ARG JAR_FILE=target/pig-auth.jar
+
+COPY ${JAR_FILE} app.jar
+
+EXPOSE 3000
+
+ENV TZ=Asia/Shanghai JAVA_OPTS="-Xms128m -Xmx256m -Djava.security.egd=file:/dev/./urandom"
+
+CMD sleep 60; java $JAVA_OPTS -jar app.jar

+ 114 - 0
pig-auth/pom.xml

@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<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>
+    <parent>
+        <groupId>com.pig4cloud</groupId>
+        <artifactId>pig</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <artifactId>pig-auth</artifactId>
+    <packaging>jar</packaging>
+
+    <description>pig 认证授权中心,基于 spring security oAuth2</description>
+
+    <dependencies>
+        <!--注册中心客户端-->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+        </dependency>
+        <!--配置中心客户端-->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
+        </dependency>
+        <!--断路器依赖-->
+        <dependency>
+            <groupId>com.pig4cloud</groupId>
+            <artifactId>pig-common-feign</artifactId>
+        </dependency>
+        <!--upms api、model 模块-->
+        <dependency>
+            <groupId>com.pig4cloud</groupId>
+            <artifactId>pig-upms-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.pig4cloud</groupId>
+            <artifactId>pig-common-security</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+        <!--freemarker 授权码模式渲染-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-freemarker</artifactId>
+        </dependency>
+        <!--undertow容器-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-undertow</artifactId>
+        </dependency>
+        <!-- log -->
+        <dependency>
+            <groupId>com.pig4cloud</groupId>
+            <artifactId>pig-common-log</artifactId>
+        </dependency>
+        <!-- 调用验证码核心模块 -->
+        <dependency>
+            <groupId>com.pig4cloud.plugin</groupId>
+            <artifactId>captcha-core</artifactId>
+            <version>${captcha.version}</version>
+        </dependency>
+        <!-- 加解密依赖 -->
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-crypto</artifactId>
+        </dependency>
+    </dependencies>
+
+    <profiles>
+        <profile>
+            <id>boot</id>
+        </profile>
+        <profile>
+            <id>cloud</id>
+            <activation>
+                <!-- 默认环境 -->
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.springframework.boot</groupId>
+                        <artifactId>spring-boot-maven-plugin</artifactId>
+                    </plugin>
+                    <plugin>
+                        <groupId>io.fabric8</groupId>
+                        <artifactId>docker-maven-plugin</artifactId>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+
+</project>

+ 39 - 0
pig-auth/src/main/java/com/pig4cloud/pig/auth/PigAuthApplication.java

@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.auth;
+
+import com.pig4cloud.pig.common.feign.annotation.EnablePigFeignClients;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+
+/**
+ * 认证授权中心应用启动类
+ *
+ * @author lengleng
+ * @date 2025/05/30
+ */
+@EnablePigFeignClients
+@EnableDiscoveryClient
+@SpringBootApplication
+public class PigAuthApplication {
+
+	public static void main(String[] args) {
+		SpringApplication.run(PigAuthApplication.class, args);
+	}
+
+}

+ 169 - 0
pig-auth/src/main/java/com/pig4cloud/pig/auth/config/AuthorizationServerConfiguration.java

@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.auth.config;
+
+import com.pig4cloud.pig.auth.support.CustomeOAuth2AccessTokenGenerator;
+import com.pig4cloud.pig.auth.support.core.CustomeOAuth2TokenCustomizer;
+import com.pig4cloud.pig.auth.support.core.FormIdentityLoginConfigurer;
+import com.pig4cloud.pig.auth.support.core.PigDaoAuthenticationProvider;
+import com.pig4cloud.pig.auth.support.filter.PasswordDecoderFilter;
+import com.pig4cloud.pig.auth.support.filter.ValidateCodeFilter;
+import com.pig4cloud.pig.auth.support.handler.PigAuthenticationFailureEventHandler;
+import com.pig4cloud.pig.auth.support.handler.PigAuthenticationSuccessEventHandler;
+import com.pig4cloud.pig.auth.support.password.OAuth2ResourceOwnerPasswordAuthenticationConverter;
+import com.pig4cloud.pig.auth.support.password.OAuth2ResourceOwnerPasswordAuthenticationProvider;
+import com.pig4cloud.pig.auth.support.sms.OAuth2ResourceOwnerSmsAuthenticationConverter;
+import com.pig4cloud.pig.auth.support.sms.OAuth2ResourceOwnerSmsAuthenticationProvider;
+import com.pig4cloud.pig.common.core.constant.SecurityConstants;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
+import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
+import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
+import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
+import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter;
+import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter;
+import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ClientCredentialsAuthenticationConverter;
+import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2RefreshTokenAuthenticationConverter;
+import org.springframework.security.web.DefaultSecurityFilterChain;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.AuthenticationConverter;
+import org.springframework.security.web.authentication.DelegatingAuthenticationConverter;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+import java.util.Arrays;
+
+/**
+ * 认证服务器配置类
+ *
+ * @author lengleng
+ * @date 2025/05/30
+ */
+@Configuration
+@RequiredArgsConstructor
+public class AuthorizationServerConfiguration {
+
+	private final OAuth2AuthorizationService authorizationService;
+
+	private final PasswordDecoderFilter passwordDecoderFilter;
+
+	private final ValidateCodeFilter validateCodeFilter;
+
+	/**
+	 * Authorization Server 配置,仅对 /oauth2/** 的请求有效
+	 * @param http http
+	 * @return {@link SecurityFilterChain }
+	 * @throws Exception 异常
+	 */
+	@Bean
+	@Order(Ordered.HIGHEST_PRECEDENCE)
+	public SecurityFilterChain authorizationServer(HttpSecurity http) throws Exception {
+		// 配置授权服务器的安全策略,只有/oauth2/**的请求才会走如下的配置
+		http.securityMatcher("/oauth2/**");
+		OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer();
+
+		// 增加验证码过滤器
+		http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);
+		// 增加密码解密过滤器
+		http.addFilterBefore(passwordDecoderFilter, UsernamePasswordAuthenticationFilter.class);
+
+		http.with(authorizationServerConfigurer.tokenEndpoint((tokenEndpoint) -> {// 个性化认证授权端点
+			tokenEndpoint.accessTokenRequestConverter(accessTokenRequestConverter()) // 注入自定义的授权认证Converter
+				.accessTokenResponseHandler(new PigAuthenticationSuccessEventHandler()) // 登录成功处理器
+				.errorResponseHandler(new PigAuthenticationFailureEventHandler());// 登录失败处理器
+		}).clientAuthentication(oAuth2ClientAuthenticationConfigurer -> // 个性化客户端认证
+		oAuth2ClientAuthenticationConfigurer.errorResponseHandler(new PigAuthenticationFailureEventHandler()))// 处理客户端认证异常
+			.authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint// 授权码端点个性化confirm页面
+				.consentPage(SecurityConstants.CUSTOM_CONSENT_PAGE_URI)), Customizer.withDefaults())
+			.authorizeHttpRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated());
+
+		// 设置 Token 存储的策略
+		http.with(authorizationServerConfigurer.authorizationService(authorizationService)// redis存储token的实现
+			.authorizationServerSettings(
+					AuthorizationServerSettings.builder().issuer(SecurityConstants.PROJECT_LICENSE).build()),
+				Customizer.withDefaults());
+
+		// 设置授权码模式登录页面
+		http.with(new FormIdentityLoginConfigurer(), Customizer.withDefaults());
+		DefaultSecurityFilterChain securityFilterChain = http.build();
+
+		// 注入自定义授权模式实现
+		addCustomOAuth2GrantAuthenticationProvider(http);
+
+		return securityFilterChain;
+	}
+
+	/**
+	 * 令牌生成规则实现 </br>
+	 * client:username:uuid
+	 * @return OAuth2TokenGenerator
+	 */
+	@Bean
+	public OAuth2TokenGenerator oAuth2TokenGenerator() {
+		CustomeOAuth2AccessTokenGenerator accessTokenGenerator = new CustomeOAuth2AccessTokenGenerator();
+		// 注入Token 增加关联用户信息
+		accessTokenGenerator.setAccessTokenCustomizer(new CustomeOAuth2TokenCustomizer());
+		return new DelegatingOAuth2TokenGenerator(accessTokenGenerator, new OAuth2RefreshTokenGenerator());
+	}
+
+	/**
+	 * request -> xToken 注入请求转换器
+	 * @return DelegatingAuthenticationConverter
+	 */
+	@Bean
+	public AuthenticationConverter accessTokenRequestConverter() {
+		return new DelegatingAuthenticationConverter(Arrays.asList(
+				new OAuth2ResourceOwnerPasswordAuthenticationConverter(),
+				new OAuth2ResourceOwnerSmsAuthenticationConverter(), new OAuth2RefreshTokenAuthenticationConverter(),
+				new OAuth2ClientCredentialsAuthenticationConverter(),
+				new OAuth2AuthorizationCodeAuthenticationConverter(),
+				new OAuth2AuthorizationCodeRequestAuthenticationConverter()));
+	}
+
+	/**
+	 * 注入授权模式实现提供方
+	 * <p>
+	 * 1. 密码模式 </br>
+	 * 2. 短信登录 </br>
+	 */
+	private void addCustomOAuth2GrantAuthenticationProvider(HttpSecurity http) {
+		AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
+		OAuth2AuthorizationService authorizationService = http.getSharedObject(OAuth2AuthorizationService.class);
+
+		OAuth2ResourceOwnerPasswordAuthenticationProvider resourceOwnerPasswordAuthenticationProvider = new OAuth2ResourceOwnerPasswordAuthenticationProvider(
+				authenticationManager, authorizationService, oAuth2TokenGenerator());
+
+		OAuth2ResourceOwnerSmsAuthenticationProvider resourceOwnerSmsAuthenticationProvider = new OAuth2ResourceOwnerSmsAuthenticationProvider(
+				authenticationManager, authorizationService, oAuth2TokenGenerator());
+
+		// 处理 UsernamePasswordAuthenticationToken
+		http.authenticationProvider(new PigDaoAuthenticationProvider());
+		// 处理 OAuth2ResourceOwnerPasswordAuthenticationToken
+		http.authenticationProvider(resourceOwnerPasswordAuthenticationProvider);
+		// 处理 OAuth2ResourceOwnerSmsAuthenticationToken
+		http.authenticationProvider(resourceOwnerSmsAuthenticationProvider);
+	}
+
+}

+ 53 - 0
pig-auth/src/main/java/com/pig4cloud/pig/auth/endpoint/ImageCodeEndpoint.java

@@ -0,0 +1,53 @@
+package com.pig4cloud.pig.auth.endpoint;
+
+import cn.hutool.core.lang.Validator;
+import com.pig4cloud.captcha.ArithmeticCaptcha;
+import com.pig4cloud.pig.common.core.constant.CacheConstants;
+import com.pig4cloud.pig.common.core.constant.SecurityConstants;
+import com.pig4cloud.pig.common.core.util.RedisUtils;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 验证码相关的接口
+ *
+ * @author lengleng
+ * @date 2022/6/27
+ */
+@RestController
+@RequestMapping("/code")
+@RequiredArgsConstructor
+public class ImageCodeEndpoint {
+
+	private static final Integer DEFAULT_IMAGE_WIDTH = 100;
+
+	private static final Integer DEFAULT_IMAGE_HEIGHT = 40;
+
+	/**
+	 * 创建图形验证码并输出到响应流
+	 * @param randomStr 随机字符串,用于缓存验证码
+	 * @param response HTTP响应对象,用于输出验证码图片
+	 */
+	@SneakyThrows
+	@GetMapping("/image")
+	public void image(String randomStr, HttpServletResponse response) {
+		ArithmeticCaptcha captcha = new ArithmeticCaptcha(DEFAULT_IMAGE_WIDTH, DEFAULT_IMAGE_HEIGHT);
+
+		if (Validator.isMobile(randomStr)) {
+			return;
+		}
+
+		String result = captcha.text();
+		RedisUtils.set(CacheConstants.DEFAULT_CODE_KEY + randomStr, result, SecurityConstants.CODE_TIME,
+				TimeUnit.SECONDS);
+		// 转换流信息写出
+		captcha.out(response.getOutputStream());
+	}
+
+}

+ 273 - 0
pig-auth/src/main/java/com/pig4cloud/pig/auth/endpoint/PigTokenEndpoint.java

@@ -0,0 +1,273 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.auth.endpoint;
+
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.date.TemporalAccessorUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.pig4cloud.pig.admin.api.entity.SysOauthClientDetails;
+import com.pig4cloud.pig.admin.api.feign.RemoteClientDetailsService;
+import com.pig4cloud.pig.admin.api.vo.TokenVo;
+import com.pig4cloud.pig.auth.support.handler.PigAuthenticationFailureEventHandler;
+import com.pig4cloud.pig.common.core.constant.CacheConstants;
+import com.pig4cloud.pig.common.core.constant.CommonConstants;
+import com.pig4cloud.pig.common.core.constant.SecurityConstants;
+import com.pig4cloud.pig.common.core.util.R;
+import com.pig4cloud.pig.common.core.util.RedisUtils;
+import com.pig4cloud.pig.common.core.util.RetOps;
+import com.pig4cloud.pig.common.core.util.SpringContextHolder;
+import com.pig4cloud.pig.common.security.annotation.Inner;
+import com.pig4cloud.pig.common.security.util.OAuth2EndpointUtils;
+import com.pig4cloud.pig.common.security.util.OAuth2ErrorCodesExpand;
+import com.pig4cloud.pig.common.security.util.OAuthClientException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cache.CacheManager;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.server.ServletServerHttpResponse;
+import org.springframework.security.authentication.event.LogoutSuccessEvent;
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
+import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
+import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
+import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
+import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
+import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.ModelAndView;
+
+import java.security.Principal;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * OAuth2 令牌端点控制器,提供令牌相关操作
+ *
+ * @author lengleng
+ * @date 2025/05/30
+ */
+@Slf4j
+@RestController
+@RequestMapping
+@RequiredArgsConstructor
+public class PigTokenEndpoint {
+
+	private final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter();
+
+	private final AuthenticationFailureHandler authenticationFailureHandler = new PigAuthenticationFailureEventHandler();
+
+	private final OAuth2AuthorizationService authorizationService;
+
+	private final RemoteClientDetailsService clientDetailsService;
+
+	private final CacheManager cacheManager;
+
+	/**
+	 * 授权码模式:认证页面
+	 * @param modelAndView 视图模型对象
+	 * @param error 表单登录失败处理回调的错误信息
+	 * @return 包含登录页面视图和错误信息的ModelAndView对象
+	 */
+	@GetMapping("/token/login")
+	public ModelAndView require(ModelAndView modelAndView, @RequestParam(required = false) String error) {
+		modelAndView.setViewName("ftl/login");
+		modelAndView.addObject("error", error);
+		return modelAndView;
+	}
+
+	/**
+	 * 授权码模式:确认页面
+	 * @param principal 用户主体信息
+	 * @param modelAndView 模型和视图对象
+	 * @param clientId 客户端ID
+	 * @param scope 请求的权限范围
+	 * @param state 状态参数
+	 * @return 包含确认页面信息的ModelAndView对象
+	 */
+	@GetMapping("/oauth2/confirm_access")
+	public ModelAndView confirm(Principal principal, ModelAndView modelAndView,
+			@RequestParam(OAuth2ParameterNames.CLIENT_ID) String clientId,
+			@RequestParam(OAuth2ParameterNames.SCOPE) String scope,
+			@RequestParam(OAuth2ParameterNames.STATE) String state) {
+		SysOauthClientDetails clientDetails = RetOps.of(clientDetailsService.getClientDetailsById(clientId))
+			.getData()
+			.orElseThrow(() -> new OAuthClientException("clientId 不合法"));
+
+		Set<String> authorizedScopes = StringUtils.commaDelimitedListToSet(clientDetails.getScope());
+		modelAndView.addObject("clientId", clientId);
+		modelAndView.addObject("state", state);
+		modelAndView.addObject("scopeList", authorizedScopes);
+		modelAndView.addObject("principalName", principal.getName());
+		modelAndView.setViewName("ftl/confirm");
+		return modelAndView;
+	}
+
+	/**
+	 * 注销并删除令牌
+	 * @param authHeader 认证头信息,包含Bearer token
+	 * @return 返回操作结果,包含布尔值表示是否成功
+	 */
+	@DeleteMapping("/token/logout")
+	public R<Boolean> logout(@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authHeader) {
+		if (StrUtil.isBlank(authHeader)) {
+			return R.ok();
+		}
+
+		String tokenValue = authHeader.replace(OAuth2AccessToken.TokenType.BEARER.getValue(), StrUtil.EMPTY).trim();
+		return removeToken(tokenValue);
+	}
+
+	/**
+	 * 检查令牌有效性
+	 * @param token 待验证的令牌
+	 * @param response HTTP响应对象
+	 * @param request HTTP请求对象
+	 * @throws InvalidBearerTokenException 令牌无效或缺失时抛出异常
+	 */
+	@SneakyThrows
+	@GetMapping("/token/check_token")
+	public void checkToken(String token, HttpServletResponse response, HttpServletRequest request) {
+		ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
+
+		if (StrUtil.isBlank(token)) {
+			httpResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
+			this.authenticationFailureHandler.onAuthenticationFailure(request, response,
+					new InvalidBearerTokenException(OAuth2ErrorCodesExpand.TOKEN_MISSING));
+			return;
+		}
+		OAuth2Authorization authorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);
+
+		// 如果令牌不存在 返回401
+		if (authorization == null || authorization.getAccessToken() == null) {
+			this.authenticationFailureHandler.onAuthenticationFailure(request, response,
+					new InvalidBearerTokenException(OAuth2ErrorCodesExpand.INVALID_BEARER_TOKEN));
+			return;
+		}
+
+		Map<String, Object> claims = authorization.getAccessToken().getClaims();
+		OAuth2AccessTokenResponse sendAccessTokenResponse = OAuth2EndpointUtils.sendAccessTokenResponse(authorization,
+				claims);
+		this.accessTokenHttpResponseConverter.write(sendAccessTokenResponse, MediaType.APPLICATION_JSON, httpResponse);
+	}
+
+	/**
+	 * 删除令牌
+	 * @param token 令牌
+	 * @return 删除结果
+	 */
+	@Inner
+	@DeleteMapping("/token/remove/{token}")
+	public R<Boolean> removeToken(@PathVariable("token") String token) {
+		OAuth2Authorization authorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);
+		if (authorization == null) {
+			return R.ok();
+		}
+
+		OAuth2Authorization.Token<OAuth2AccessToken> accessToken = authorization.getAccessToken();
+		if (accessToken == null || StrUtil.isBlank(accessToken.getToken().getTokenValue())) {
+			return R.ok();
+		}
+		// 清空用户信息(立即删除)
+		cacheManager.getCache(CacheConstants.USER_DETAILS).evictIfPresent(authorization.getPrincipalName());
+		// 清空access token
+		authorizationService.remove(authorization);
+		// 处理自定义退出事件,保存相关日志
+		SpringContextHolder.publishEvent(new LogoutSuccessEvent(new PreAuthenticatedAuthenticationToken(
+				authorization.getPrincipalName(), authorization.getRegisteredClientId())));
+		return R.ok();
+	}
+
+	/**
+	 * 分页查询令牌列表
+	 * @param params 请求参数,包含分页参数current和size
+	 * @return 分页结果,包含令牌信息列表
+	 */
+	@Inner
+	@PostMapping("/token/page")
+	public R<Page> tokenList(@RequestBody Map<String, Object> params) {
+		// 根据分页参数获取对应数据
+		String username = MapUtil.getStr(params, SecurityConstants.USERNAME);
+		String pattern = String.format("%s::*", CacheConstants.PROJECT_OAUTH_ACCESS);
+		int current = MapUtil.getInt(params, CommonConstants.CURRENT);
+		int size = MapUtil.getInt(params, CommonConstants.SIZE);
+		Page result = new Page(current, size);
+
+		// 获取总数
+		List<String> allKeys = RedisUtils.scan(pattern);
+		result.setTotal(allKeys.size());
+
+		List<String> pageKeys = RedisUtils.findKeysForPage(pattern, current - 1, size);
+		List<OAuth2Authorization> pagedAuthorizations = RedisUtils.multiGet(pageKeys);
+
+		// 转换为TokenVo
+		List<TokenVo> tokenVoList = pagedAuthorizations.stream()
+			.filter(Objects::nonNull)
+			.map(this::convertToTokenVo)
+			.filter(tokenVo -> {
+				if (StrUtil.isBlank(username)) {
+					return true;
+				}
+				return StrUtil.startWithAnyIgnoreCase(tokenVo.getUsername(), username);
+			})
+			.toList();
+
+		if (StrUtil.isNotBlank(username)) {
+			result.setTotal(tokenVoList.size());
+		}
+
+		result.setRecords(tokenVoList);
+		return R.ok(result);
+	}
+
+	/**
+	 * 将OAuth2Authorization转换为TokenVo
+	 * @param authorization OAuth2授权对象
+	 * @return TokenVo对象
+	 */
+	private TokenVo convertToTokenVo(OAuth2Authorization authorization) {
+		TokenVo tokenVo = new TokenVo();
+		tokenVo.setClientId(authorization.getRegisteredClientId());
+		tokenVo.setId(authorization.getId());
+		tokenVo.setUsername(authorization.getPrincipalName());
+		OAuth2Authorization.Token<OAuth2AccessToken> accessToken = authorization.getAccessToken();
+		tokenVo.setAccessToken(accessToken.getToken().getTokenValue());
+
+		String expiresAt = TemporalAccessorUtil.format(accessToken.getToken().getExpiresAt(),
+				DatePattern.NORM_DATETIME_PATTERN);
+		tokenVo.setExpiresAt(expiresAt);
+
+		String issuedAt = TemporalAccessorUtil.format(accessToken.getToken().getIssuedAt(),
+				DatePattern.NORM_DATETIME_PATTERN);
+		tokenVo.setIssuedAt(issuedAt);
+		return tokenVo;
+	}
+
+}

+ 152 - 0
pig-auth/src/main/java/com/pig4cloud/pig/auth/support/CustomeOAuth2AccessTokenGenerator.java

@@ -0,0 +1,152 @@
+package com.pig4cloud.pig.auth.support;
+
+import org.springframework.lang.Nullable;
+import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
+import org.springframework.security.crypto.keygen.StringKeyGenerator;
+import org.springframework.security.oauth2.core.ClaimAccessor;
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
+import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
+import org.springframework.security.oauth2.server.authorization.token.*;
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.io.Serial;
+import java.time.Instant;
+import java.util.*;
+
+/**
+ * 自定义OAuth2访问令牌生成器
+ *
+ * @author lengleng
+ * @date 2025/05/30
+ */
+public class CustomeOAuth2AccessTokenGenerator implements OAuth2TokenGenerator<OAuth2AccessToken> {
+
+	private OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer;
+
+	private final StringKeyGenerator accessTokenGenerator = new Base64StringKeyGenerator(
+			Base64.getUrlEncoder().withoutPadding(), 96);
+
+	/**
+	 * 生成OAuth2访问令牌
+	 * @param context OAuth2令牌上下文
+	 * @return 生成的访问令牌,如果令牌类型不是ACCESS_TOKEN或格式不是REFERENCE则返回null
+	 * @see OAuth2TokenContext
+	 * @see OAuth2AccessToken
+	 */
+	@Nullable
+	@Override
+	public OAuth2AccessToken generate(OAuth2TokenContext context) {
+		if (!OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType()) || !OAuth2TokenFormat.REFERENCE
+			.equals(context.getRegisteredClient().getTokenSettings().getAccessTokenFormat())) {
+			return null;
+		}
+
+		String issuer = null;
+		if (context.getAuthorizationServerContext() != null) {
+			issuer = context.getAuthorizationServerContext().getIssuer();
+		}
+		RegisteredClient registeredClient = context.getRegisteredClient();
+
+		Instant issuedAt = Instant.now();
+		Instant expiresAt = issuedAt.plus(registeredClient.getTokenSettings().getAccessTokenTimeToLive());
+
+		// @formatter:off
+        OAuth2TokenClaimsSet.Builder claimsBuilder = OAuth2TokenClaimsSet.builder();
+        if (StringUtils.hasText(issuer)) {
+            claimsBuilder.issuer(issuer);
+        }
+        claimsBuilder
+                .subject(context.getPrincipal().getName())
+                .audience(Collections.singletonList(registeredClient.getClientId()))
+                .issuedAt(issuedAt)
+                .expiresAt(expiresAt)
+                .notBefore(issuedAt)
+                .id(UUID.randomUUID().toString());
+        if (!CollectionUtils.isEmpty(context.getAuthorizedScopes())) {
+            claimsBuilder.claim(OAuth2ParameterNames.SCOPE, context.getAuthorizedScopes());
+        }
+        // @formatter:on
+
+		if (this.accessTokenCustomizer != null) {
+			// @formatter:off
+            OAuth2TokenClaimsContext.Builder accessTokenContextBuilder = OAuth2TokenClaimsContext.with(claimsBuilder)
+                    .registeredClient(context.getRegisteredClient())
+                    .principal(context.getPrincipal())
+                    .authorizationServerContext(context.getAuthorizationServerContext())
+                    .authorizedScopes(context.getAuthorizedScopes())
+                    .tokenType(context.getTokenType())
+                    .authorizationGrantType(context.getAuthorizationGrantType());
+            if (context.getAuthorization() != null) {
+                accessTokenContextBuilder.authorization(context.getAuthorization());
+            }
+            if (context.getAuthorizationGrant() != null) {
+                accessTokenContextBuilder.authorizationGrant(context.getAuthorizationGrant());
+            }
+            // @formatter:on
+
+			OAuth2TokenClaimsContext accessTokenContext = accessTokenContextBuilder.build();
+			this.accessTokenCustomizer.customize(accessTokenContext);
+		}
+
+		OAuth2TokenClaimsSet accessTokenClaimsSet = claimsBuilder.build();
+		return new CustomeOAuth2AccessTokenGenerator.OAuth2AccessTokenClaims(OAuth2AccessToken.TokenType.BEARER,
+				this.accessTokenGenerator.generateKey(), accessTokenClaimsSet.getIssuedAt(),
+				accessTokenClaimsSet.getExpiresAt(), context.getAuthorizedScopes(), accessTokenClaimsSet.getClaims());
+	}
+
+	/**
+	 * 设置用于定制{@link OAuth2AccessToken}的{@link OAuth2TokenClaimsContext#getClaims()}的{@link OAuth2TokenCustomizer}
+	 * @param accessTokenCustomizer
+	 * 用于定制{@code OAuth2AccessToken}声明的{@link OAuth2TokenCustomizer}
+	 * @throws IllegalArgumentException 当accessTokenCustomizer为null时抛出
+	 */
+	public void setAccessTokenCustomizer(OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer) {
+		Assert.notNull(accessTokenCustomizer, "accessTokenCustomizer cannot be null");
+		this.accessTokenCustomizer = accessTokenCustomizer;
+	}
+
+	/**
+	 * OAuth2访问令牌声明类,继承自OAuth2AccessToken并实现ClaimAccessor接口
+	 *
+	 * @author lengleng
+	 * @date 2025/05/30
+	 */
+	private static final class OAuth2AccessTokenClaims extends OAuth2AccessToken implements ClaimAccessor {
+
+		@Serial
+		private static final long serialVersionUID = 1L;
+
+		private final Map<String, Object> claims;
+
+		/**
+		 * 构造OAuth2访问令牌声明
+		 * @param tokenType 令牌类型
+		 * @param tokenValue 令牌值
+		 * @param issuedAt 颁发时间
+		 * @param expiresAt 过期时间
+		 * @param scopes 权限范围集合
+		 * @param claims 声明信息映射
+		 */
+		private OAuth2AccessTokenClaims(TokenType tokenType, String tokenValue, Instant issuedAt, Instant expiresAt,
+				Set<String> scopes, Map<String, Object> claims) {
+			super(tokenType, tokenValue, issuedAt, expiresAt, scopes);
+			this.claims = claims;
+		}
+
+		/**
+		 * 获取claims集合
+		 * @return claims键值对集合
+		 */
+		@Override
+		public Map<String, Object> getClaims() {
+			return this.claims;
+		}
+
+	}
+
+}

+ 104 - 0
pig-auth/src/main/java/com/pig4cloud/pig/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationConverter.java

@@ -0,0 +1,104 @@
+package com.pig4cloud.pig.auth.support.base;
+
+import com.pig4cloud.pig.common.security.util.OAuth2EndpointUtils;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.web.authentication.AuthenticationConverter;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.StringUtils;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * OAuth2资源所有者基础认证转换器抽象类
+ *
+ * @param <T> 继承自OAuth2ResourceOwnerBaseAuthenticationToken的泛型类型
+ * @author lengleng
+ * @date 2025/05/30
+ */
+public abstract class OAuth2ResourceOwnerBaseAuthenticationConverter<T extends OAuth2ResourceOwnerBaseAuthenticationToken>
+		implements AuthenticationConverter {
+
+	/**
+	 * 是否支持此convert
+	 * @param grantType 授权类型
+	 * @return
+	 */
+	public abstract boolean support(String grantType);
+
+	/**
+	 * 校验参数
+	 * @param request 请求
+	 */
+	public void checkParams(HttpServletRequest request) {
+
+	}
+
+	/**
+	 * 构建具体类型的token
+	 * @param clientPrincipal 客户端认证信息
+	 * @param requestedScopes 请求的作用域集合
+	 * @param additionalParameters 附加参数映射
+	 * @return 构建完成的token对象
+	 */
+	public abstract T buildToken(Authentication clientPrincipal, Set<String> requestedScopes,
+			Map<String, Object> additionalParameters);
+
+	/**
+	 * 将HttpServletRequest转换为Authentication对象
+	 * @param request HTTP请求对象
+	 * @return 认证信息对象
+	 * @throws OAuth2AuthenticationException 当请求参数不合法或客户端未认证时抛出异常
+	 */
+	@Override
+	public Authentication convert(HttpServletRequest request) {
+
+		// grant_type (REQUIRED)
+		String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
+		if (!support(grantType)) {
+			return null;
+		}
+
+		MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
+		// scope (OPTIONAL)
+		String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
+		if (StringUtils.hasText(scope) && parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) {
+			OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE,
+					OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
+		}
+
+		Set<String> requestedScopes = null;
+		if (StringUtils.hasText(scope)) {
+			requestedScopes = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
+		}
+
+		// 校验个性化参数
+		checkParams(request);
+
+		// 获取当前已经认证的客户端信息
+		Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
+		if (clientPrincipal == null) {
+			OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ErrorCodes.INVALID_CLIENT,
+					OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
+		}
+
+		// 扩展信息
+		Map<String, Object> additionalParameters = parameters.entrySet()
+			.stream()
+			.filter(e -> !e.getKey().equals(OAuth2ParameterNames.GRANT_TYPE)
+					&& !e.getKey().equals(OAuth2ParameterNames.SCOPE))
+			.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
+
+		// 创建token
+		return buildToken(clientPrincipal, requestedScopes, additionalParameters);
+
+	}
+
+}

+ 301 - 0
pig-auth/src/main/java/com/pig4cloud/pig/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationProvider.java

@@ -0,0 +1,301 @@
+package com.pig4cloud.pig.auth.support.base;
+
+import cn.hutool.extra.spring.SpringUtil;
+import com.pig4cloud.pig.common.security.util.OAuth2ErrorCodesExpand;
+import com.pig4cloud.pig.common.security.util.ScopeException;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.context.support.MessageSourceAccessor;
+import org.springframework.security.authentication.*;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.oauth2.core.*;
+import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
+import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
+import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
+import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
+import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+
+import java.security.Principal;
+import java.time.Instant;
+import java.util.*;
+import java.util.function.Supplier;
+
+/**
+ * OAuth2资源所有者基础认证提供者抽象类,用于处理资源所有者密码凭证授权流程
+ *
+ * @param <T> OAuth2资源所有者基础认证令牌类型
+ * @author lengleng
+ * @date 2025/05/30
+ */
+public abstract class OAuth2ResourceOwnerBaseAuthenticationProvider<T extends OAuth2ResourceOwnerBaseAuthenticationToken>
+		implements AuthenticationProvider {
+
+	private static final Logger LOGGER = LogManager.getLogger(OAuth2ResourceOwnerBaseAuthenticationProvider.class);
+
+	private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1";
+
+	private final OAuth2AuthorizationService authorizationService;
+
+	private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
+
+	private final AuthenticationManager authenticationManager;
+
+	private final MessageSourceAccessor messages;
+
+	@Deprecated
+	private Supplier<String> refreshTokenGenerator;
+
+	/**
+	 * 构造一个基于资源所有者密码模式的OAuth2认证提供者
+	 * @param authenticationManager 认证管理器
+	 * @param authorizationService 授权服务
+	 * @param tokenGenerator token生成器
+	 * @throws IllegalArgumentException 当authorizationService或tokenGenerator为null时抛出
+	 * @since 0.2.3
+	 */
+	public OAuth2ResourceOwnerBaseAuthenticationProvider(AuthenticationManager authenticationManager,
+			OAuth2AuthorizationService authorizationService,
+			OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
+		Assert.notNull(authorizationService, "authorizationService cannot be null");
+		Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
+		this.authenticationManager = authenticationManager;
+		this.authorizationService = authorizationService;
+		this.tokenGenerator = tokenGenerator;
+
+		// 国际化配置
+		this.messages = new MessageSourceAccessor(SpringUtil.getBean("securityMessageSource"), Locale.CHINA);
+	}
+
+	/**
+	 * 设置刷新令牌生成器
+	 * @param refreshTokenGenerator 刷新令牌生成器,不能为null
+	 * @deprecated 该方法已废弃
+	 */
+	@Deprecated
+	public void setRefreshTokenGenerator(Supplier<String> refreshTokenGenerator) {
+		Assert.notNull(refreshTokenGenerator, "refreshTokenGenerator cannot be null");
+		this.refreshTokenGenerator = refreshTokenGenerator;
+	}
+
+	/**
+	 * 构建用户名密码认证令牌
+	 * @param reqParameters 请求参数映射
+	 * @return 用户名密码认证令牌
+	 */
+	public abstract UsernamePasswordAuthenticationToken buildToken(Map<String, Object> reqParameters);
+
+	/**
+	 * 当前provider是否支持此令牌类型
+	 * @param authentication
+	 * @return
+	 */
+	@Override
+	public abstract boolean supports(Class<?> authentication);
+
+	/**
+	 * 当前的请求客户端是否支持此模式
+	 * @param registeredClient
+	 */
+	public abstract void checkClient(RegisteredClient registeredClient);
+
+	/**
+	 * 执行认证操作,遵循与{@link AuthenticationManager#authenticate(Authentication)}相同的契约
+	 * @param authentication 认证请求对象
+	 * @return 包含凭证的完整认证对象,如果当前认证提供者无法处理传入的认证对象可能返回null
+	 * @throws AuthenticationException 认证失败时抛出
+	 * @throws OAuth2AuthenticationException 当scope无效或token生成失败时抛出
+	 */
+	@Override
+	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+
+		T resouceOwnerBaseAuthentication = (T) authentication;
+
+		OAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient(
+				resouceOwnerBaseAuthentication);
+
+		RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
+		checkClient(registeredClient);
+
+		Set<String> authorizedScopes;
+		// Default to configured scopes
+		if (!CollectionUtils.isEmpty(resouceOwnerBaseAuthentication.getScopes())) {
+			for (String requestedScope : resouceOwnerBaseAuthentication.getScopes()) {
+				if (!registeredClient.getScopes().contains(requestedScope)) {
+					throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_SCOPE);
+				}
+			}
+			authorizedScopes = new LinkedHashSet<>(resouceOwnerBaseAuthentication.getScopes());
+		}
+		else {
+			authorizedScopes = new LinkedHashSet<>();
+		}
+
+		Map<String, Object> reqParameters = resouceOwnerBaseAuthentication.getAdditionalParameters();
+		try {
+
+			UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = buildToken(reqParameters);
+
+			LOGGER.debug("got usernamePasswordAuthenticationToken=" + usernamePasswordAuthenticationToken);
+
+			Authentication usernamePasswordAuthentication = authenticationManager
+				.authenticate(usernamePasswordAuthenticationToken);
+
+			// @formatter:off
+			DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
+					.registeredClient(registeredClient)
+					.principal(usernamePasswordAuthentication)
+					.authorizationServerContext(AuthorizationServerContextHolder.getContext())
+					.authorizedScopes(authorizedScopes)
+					.authorizationGrantType(resouceOwnerBaseAuthentication.getAuthorizationGrantType())
+					.authorizationGrant(resouceOwnerBaseAuthentication);
+			// @formatter:on
+
+			OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization
+				.withRegisteredClient(registeredClient)
+				.principalName(usernamePasswordAuthentication.getName())
+				.authorizationGrantType(resouceOwnerBaseAuthentication.getAuthorizationGrantType())
+				// 0.4.0 新增的方法
+				.authorizedScopes(authorizedScopes);
+
+			// ----- Access token -----
+			OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build();
+			OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);
+			if (generatedAccessToken == null) {
+				OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
+						"The token generator failed to generate the access token.", ERROR_URI);
+				throw new OAuth2AuthenticationException(error);
+			}
+			OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
+					generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
+					generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
+			if (generatedAccessToken instanceof ClaimAccessor) {
+				authorizationBuilder.id(accessToken.getTokenValue())
+					.token(accessToken,
+							(metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME,
+									((ClaimAccessor) generatedAccessToken).getClaims()))
+					// 0.4.0 新增的方法
+					.authorizedScopes(authorizedScopes)
+					.attribute(Principal.class.getName(), usernamePasswordAuthentication);
+			}
+			else {
+				authorizationBuilder.id(accessToken.getTokenValue()).accessToken(accessToken);
+			}
+
+			// ----- Refresh token -----
+			OAuth2RefreshToken refreshToken = null;
+			if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) &&
+			// Do not issue refresh token to public client
+					!clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) {
+
+				if (this.refreshTokenGenerator != null) {
+					Instant issuedAt = Instant.now();
+					Instant expiresAt = issuedAt.plus(registeredClient.getTokenSettings().getRefreshTokenTimeToLive());
+					refreshToken = new OAuth2RefreshToken(this.refreshTokenGenerator.get(), issuedAt, expiresAt);
+				}
+				else {
+					tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build();
+					OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext);
+					if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) {
+						OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
+								"The token generator failed to generate the refresh token.", ERROR_URI);
+						throw new OAuth2AuthenticationException(error);
+					}
+					refreshToken = (OAuth2RefreshToken) generatedRefreshToken;
+				}
+				authorizationBuilder.refreshToken(refreshToken);
+			}
+
+			OAuth2Authorization authorization = authorizationBuilder.build();
+
+			this.authorizationService.save(authorization);
+
+			LOGGER.debug("returning OAuth2AccessTokenAuthenticationToken");
+
+			return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken,
+					refreshToken, Objects.requireNonNull(authorization.getAccessToken().getClaims()));
+
+		}
+		catch (Exception ex) {
+			LOGGER.error("problem in authenticate", ex);
+			throw oAuth2AuthenticationException(authentication, (AuthenticationException) ex);
+		}
+
+	}
+
+	/**
+	 * 登录异常转换为oauth2异常
+	 * @param authentication 身份验证
+	 * @param authenticationException 身份验证异常
+	 * @return {@link OAuth2AuthenticationException}
+	 */
+	private OAuth2AuthenticationException oAuth2AuthenticationException(Authentication authentication,
+			AuthenticationException authenticationException) {
+		if (authenticationException instanceof UsernameNotFoundException) {
+			return new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodesExpand.USERNAME_NOT_FOUND,
+					this.messages.getMessage("JdbcDaoImpl.notFound", new Object[] { authentication.getName() },
+							"Username {0} not found"),
+					""));
+		}
+		if (authenticationException instanceof BadCredentialsException) {
+			return new OAuth2AuthenticationException(
+					new OAuth2Error(OAuth2ErrorCodesExpand.BAD_CREDENTIALS, this.messages.getMessage(
+							"AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), ""));
+		}
+		if (authenticationException instanceof LockedException) {
+			return new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodesExpand.USER_LOCKED, this.messages
+				.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"), ""));
+		}
+		if (authenticationException instanceof DisabledException) {
+			return new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodesExpand.USER_DISABLE,
+					this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"),
+					""));
+		}
+		if (authenticationException instanceof AccountExpiredException) {
+			return new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodesExpand.USER_EXPIRED, this.messages
+				.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"), ""));
+		}
+		if (authenticationException instanceof CredentialsExpiredException) {
+			return new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodesExpand.CREDENTIALS_EXPIRED,
+					this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.credentialsExpired",
+							"User credentials have expired"),
+					""));
+		}
+		if (authenticationException instanceof ScopeException) {
+			return new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_SCOPE,
+					this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "invalid_scope"), ""));
+		}
+		return new OAuth2AuthenticationException(OAuth2ErrorCodesExpand.UN_KNOW_LOGIN_ERROR);
+	}
+
+	/**
+	 * 获取已认证的客户端主体,否则抛出无效客户端异常
+	 * @param authentication 认证信息
+	 * @return 已认证的客户端主体
+	 * @throws OAuth2AuthenticationException 客户端未认证时抛出异常
+	 */
+	private OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(
+			Authentication authentication) {
+
+		OAuth2ClientAuthenticationToken clientPrincipal = null;
+
+		if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
+			clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
+		}
+
+		if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
+			return clientPrincipal;
+		}
+
+		throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
+	}
+
+}

+ 65 - 0
pig-auth/src/main/java/com/pig4cloud/pig/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationToken.java

@@ -0,0 +1,65 @@
+package com.pig4cloud.pig.auth.support.base;
+
+import lombok.Getter;
+import org.springframework.lang.Nullable;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.util.Assert;
+
+import java.io.Serial;
+import java.util.*;
+
+/**
+ * OAuth2资源所有者基础认证令牌抽象类
+ *
+ * @author lengleng
+ * @date 2025/05/30
+ */
+public abstract class OAuth2ResourceOwnerBaseAuthenticationToken extends AbstractAuthenticationToken {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	@Getter
+	private final AuthorizationGrantType authorizationGrantType;
+
+	@Getter
+	private final Authentication clientPrincipal;
+
+	@Getter
+	private final Set<String> scopes;
+
+	@Getter
+	private final Map<String, Object> additionalParameters;
+
+	public OAuth2ResourceOwnerBaseAuthenticationToken(AuthorizationGrantType authorizationGrantType,
+			Authentication clientPrincipal, @Nullable Set<String> scopes,
+			@Nullable Map<String, Object> additionalParameters) {
+		super(Collections.emptyList());
+		Assert.notNull(authorizationGrantType, "authorizationGrantType cannot be null");
+		Assert.notNull(clientPrincipal, "clientPrincipal cannot be null");
+		this.authorizationGrantType = authorizationGrantType;
+		this.clientPrincipal = clientPrincipal;
+		this.scopes = Collections.unmodifiableSet(scopes != null ? new HashSet<>(scopes) : Collections.emptySet());
+		this.additionalParameters = Collections.unmodifiableMap(
+				additionalParameters != null ? new HashMap<>(additionalParameters) : Collections.emptyMap());
+	}
+
+	/**
+	 * 扩展模式一般不需要密码
+	 */
+	@Override
+	public Object getCredentials() {
+		return "";
+	}
+
+	/**
+	 * 获取用户名
+	 */
+	@Override
+	public Object getPrincipal() {
+		return this.clientPrincipal;
+	}
+
+}

+ 4 - 0
pig-auth/src/main/java/com/pig4cloud/pig/auth/support/base/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * 自定义认证模式接入的抽象实现
+ */
+package com.pig4cloud.pig.auth.support.base;

+ 38 - 0
pig-auth/src/main/java/com/pig4cloud/pig/auth/support/core/CustomeOAuth2TokenCustomizer.java

@@ -0,0 +1,38 @@
+package com.pig4cloud.pig.auth.support.core;
+
+import com.pig4cloud.pig.common.core.constant.SecurityConstants;
+import com.pig4cloud.pig.common.security.service.PigUser;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsSet;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
+
+/**
+ * OAuth2 Token 自定义增强实现类
+ *
+ * @author lengleng
+ * @date 2025/05/30
+ */
+public class CustomeOAuth2TokenCustomizer implements OAuth2TokenCustomizer<OAuth2TokenClaimsContext> {
+
+	/**
+	 * 自定义OAuth 2.0 Token属性
+	 * @param context 包含OAuth 2.0 Token属性的上下文
+	 */
+	@Override
+	public void customize(OAuth2TokenClaimsContext context) {
+		OAuth2TokenClaimsSet.Builder claims = context.getClaims();
+		claims.claim(SecurityConstants.DETAILS_LICENSE, SecurityConstants.PROJECT_LICENSE);
+		String clientId = context.getAuthorizationGrant().getName();
+		claims.claim(SecurityConstants.CLIENT_ID, clientId);
+		// 客户端模式不返回具体用户信息
+		if (SecurityConstants.CLIENT_CREDENTIALS.equals(context.getAuthorizationGrantType().getValue())) {
+			return;
+		}
+
+		PigUser pigUser = (PigUser) context.getPrincipal().getPrincipal();
+		claims.claim(SecurityConstants.DETAILS_USER, pigUser);
+		claims.claim(SecurityConstants.DETAILS_USER_ID, pigUser.getId());
+		claims.claim(SecurityConstants.USERNAME, pigUser.getUsername());
+	}
+
+}

+ 33 - 0
pig-auth/src/main/java/com/pig4cloud/pig/auth/support/core/FormIdentityLoginConfigurer.java

@@ -0,0 +1,33 @@
+package com.pig4cloud.pig.auth.support.core;
+
+import com.pig4cloud.pig.auth.support.handler.FormAuthenticationFailureHandler;
+import com.pig4cloud.pig.auth.support.handler.SsoLogoutSuccessHandler;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+
+/**
+ * 基于授权码模式的统一认证登录配置类,适用于Spring Security和SAS
+ *
+ * @author lengleng
+ * @date 2025/05/30
+ */
+public final class FormIdentityLoginConfigurer
+		extends AbstractHttpConfigurer<FormIdentityLoginConfigurer, HttpSecurity> {
+
+	@Override
+	public void init(HttpSecurity http) throws Exception {
+		http.formLogin(formLogin -> {
+			formLogin.loginPage("/token/login");
+			formLogin.loginProcessingUrl("/oauth2/form");
+			formLogin.failureHandler(new FormAuthenticationFailureHandler());
+
+		})
+			.logout(logout -> logout.logoutUrl("/oauth2/logout")
+				.logoutSuccessHandler(new SsoLogoutSuccessHandler())
+				.deleteCookies("JSESSIONID")
+				.invalidateHttpSession(true)) // SSO登出成功处理
+
+			.csrf(AbstractHttpConfigurer::disable);
+	}
+
+}

+ 226 - 0
pig-auth/src/main/java/com/pig4cloud/pig/auth/support/core/PigDaoAuthenticationProvider.java

@@ -0,0 +1,226 @@
+package com.pig4cloud.pig.auth.support.core;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import com.pig4cloud.pig.common.core.util.WebUtils;
+import com.pig4cloud.pig.common.security.service.PigUserDetailsService;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.SneakyThrows;
+import org.springframework.core.Ordered;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.InternalAuthenticationServiceException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsPasswordService;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.crypto.factory.PasswordEncoderFactories;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.web.authentication.www.BasicAuthenticationConverter;
+import org.springframework.util.Assert;
+
+import java.util.Comparator;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Supplier;
+
+/**
+ * 基于DAO的认证提供者实现,用于处理用户名密码认证
+ *
+ * @author lengleng
+ * @date 2025/05/30
+ */
+public class PigDaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
+
+	/**
+	 * 用户未找到时用于PasswordEncoder#matches(CharSequence, String)的明文密码,避免SEC-2056问题
+	 */
+	private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
+
+	private final static BasicAuthenticationConverter basicConvert = new BasicAuthenticationConverter();
+
+	/**
+	 * 密码编码器
+	 */
+	private PasswordEncoder passwordEncoder;
+
+	/**
+	 * 用户未找到时的加密密码,用于避免SEC-2056问题,某些密码编码器在密码格式无效时会短路处理
+	 */
+	private volatile String userNotFoundEncodedPassword;
+
+	private UserDetailsService userDetailsService;
+
+	private UserDetailsPasswordService userDetailsPasswordService;
+
+	public PigDaoAuthenticationProvider() {
+		setMessageSource(SpringUtil.getBean("securityMessageSource"));
+		setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
+	}
+
+	/**
+	 * 执行额外的身份验证检查
+	 * @param userDetails 用户详细信息
+	 * @param authentication 身份验证令牌
+	 * @throws AuthenticationException 身份验证失败时抛出异常
+	 */
+	@Override
+	protected void additionalAuthenticationChecks(UserDetails userDetails,
+			UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
+
+		// 只有密码模式需要校验密码
+		String grantType = WebUtils.getRequest().get().getParameter(OAuth2ParameterNames.GRANT_TYPE);
+		if (!StrUtil.equals(AuthorizationGrantType.PASSWORD.getValue(), grantType)) {
+			return;
+		}
+
+		if (authentication.getCredentials() == null) {
+			this.logger.debug("Failed to authenticate since no credentials provided");
+			throw new BadCredentialsException(this.messages
+				.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
+		}
+		String presentedPassword = authentication.getCredentials().toString();
+		if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
+			this.logger.debug("Failed to authenticate since password does not match stored value");
+			throw new BadCredentialsException(this.messages
+				.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
+		}
+	}
+
+	/**
+	 * 根据用户名检索用户详情
+	 * @param username 用户名
+	 * @param authentication 认证令牌
+	 * @return 用户详情信息
+	 * @throws InternalAuthenticationServiceException
+	 * 当无法获取请求、未注册UserDetailsService或加载用户失败时抛出
+	 * @throws UsernameNotFoundException 当用户名不存在时抛出
+	 */
+	@SneakyThrows
+	@Override
+	protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) {
+		prepareTimingAttackProtection();
+		HttpServletRequest request = WebUtils.getRequest()
+			.orElseThrow(
+					(Supplier<Throwable>) () -> new InternalAuthenticationServiceException("web request is empty"));
+
+		String grantType = WebUtils.getRequest().get().getParameter(OAuth2ParameterNames.GRANT_TYPE);
+		String clientId = WebUtils.getRequest().get().getParameter(OAuth2ParameterNames.CLIENT_ID);
+
+		if (StrUtil.isBlank(clientId)) {
+			clientId = basicConvert.convert(request).getName();
+		}
+
+		Map<String, PigUserDetailsService> userDetailsServiceMap = SpringUtil
+			.getBeansOfType(PigUserDetailsService.class);
+
+		String finalClientId = clientId;
+		Optional<PigUserDetailsService> optional = userDetailsServiceMap.values()
+			.stream()
+			.filter(service -> service.support(finalClientId, grantType))
+			.max(Comparator.comparingInt(Ordered::getOrder));
+
+		if (optional.isEmpty()) {
+			throw new InternalAuthenticationServiceException("UserDetailsService error , not register");
+		}
+
+		try {
+			UserDetails loadedUser = optional.get().loadUserByUsername(username);
+			if (loadedUser == null) {
+				throw new InternalAuthenticationServiceException(
+						"UserDetailsService returned null, which is an interface contract violation");
+			}
+			return loadedUser;
+		}
+		catch (UsernameNotFoundException ex) {
+			mitigateAgainstTimingAttack(authentication);
+			throw ex;
+		}
+		catch (InternalAuthenticationServiceException ex) {
+			throw ex;
+		}
+		catch (Exception ex) {
+			throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
+		}
+	}
+
+	/**
+	 * 创建认证成功后的Authentication对象
+	 * @param principal 认证主体
+	 * @param authentication 认证信息
+	 * @param user 用户详情
+	 * @return 认证成功后的Authentication对象
+	 */
+	@Override
+	protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
+			UserDetails user) {
+		boolean upgradeEncoding = this.userDetailsPasswordService != null
+				&& this.passwordEncoder.upgradeEncoding(user.getPassword());
+		if (upgradeEncoding) {
+			String presentedPassword = authentication.getCredentials().toString();
+			String newPassword = this.passwordEncoder.encode(presentedPassword);
+			user = this.userDetailsPasswordService.updatePassword(user, newPassword);
+		}
+		return super.createSuccessAuthentication(principal, authentication, user);
+	}
+
+	/**
+	 * 准备定时攻击保护,如果未找到用户编码密码为空则进行编码
+	 */
+	private void prepareTimingAttackProtection() {
+		if (this.userNotFoundEncodedPassword == null) {
+			this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);
+		}
+	}
+
+	/**
+	 * 防止时序攻击的缓解措施
+	 * @param authentication 用户名密码认证令牌
+	 */
+	private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
+		if (authentication.getCredentials() != null) {
+			String presentedPassword = authentication.getCredentials().toString();
+			this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
+		}
+	}
+
+	/**
+	 * 设置用于编码和验证密码的PasswordEncoder实例
+	 * @param passwordEncoder 密码编码器实例,不能为null
+	 */
+	public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
+		Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
+		this.passwordEncoder = passwordEncoder;
+		this.userNotFoundEncodedPassword = null;
+	}
+
+	protected PasswordEncoder getPasswordEncoder() {
+		return this.passwordEncoder;
+	}
+
+	/**
+	 * 设置用户详情服务
+	 * @param userDetailsService 用户详情服务
+	 */
+	public void setUserDetailsService(UserDetailsService userDetailsService) {
+		this.userDetailsService = userDetailsService;
+	}
+
+	protected UserDetailsService getUserDetailsService() {
+		return this.userDetailsService;
+	}
+
+	/**
+	 * 设置用户详情密码服务
+	 * @param userDetailsPasswordService 用户详情密码服务
+	 */
+	public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {
+		this.userDetailsPasswordService = userDetailsPasswordService;
+	}
+
+}

+ 42 - 0
pig-auth/src/main/java/com/pig4cloud/pig/auth/support/filter/AuthSecurityConfigProperties.java

@@ -0,0 +1,42 @@
+package com.pig4cloud.pig.auth.support.filter;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * 安全认证配置属性类
+ *
+ * <p>
+ * 用于配置网关安全相关属性
+ * </p>
+ *
+ * @author lengleng
+ * @date 2025/05/30
+ * @since 2020/10/4
+ */
+@Data
+@Component
+@RefreshScope
+@ConfigurationProperties("security")
+public class AuthSecurityConfigProperties {
+
+	/**
+	 * 是否是微服务架构
+	 */
+	private boolean isMicro;
+
+	/**
+	 * 网关解密登录前端密码 秘钥
+	 */
+	private String encodeKey;
+
+	/**
+	 * 网关不需要校验验证码的客户端
+	 */
+	private List<String> ignoreClients;
+
+}

+ 102 - 0
pig-auth/src/main/java/com/pig4cloud/pig/auth/support/filter/PasswordDecoderFilter.java

@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.auth.support.filter;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.Mode;
+import cn.hutool.crypto.Padding;
+import cn.hutool.crypto.SecureUtil;
+import cn.hutool.crypto.symmetric.AES;
+import com.pig4cloud.pig.common.core.constant.SecurityConstants;
+import com.pig4cloud.pig.common.core.servlet.RepeatBodyRequestWrapper;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * 密码解密过滤器:用于处理登录请求中的密码解密
+ *
+ * @author lengleng
+ * @date 2025/05/30
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class PasswordDecoderFilter extends OncePerRequestFilter {
+
+	private final AuthSecurityConfigProperties authSecurityConfigProperties;
+
+	private static final String PASSWORD = "password";
+
+	private static final String KEY_ALGORITHM = "AES";
+
+	static {
+		// 关闭hutool 强制关闭Bouncy Castle库的依赖
+		SecureUtil.disableBouncyCastle();
+	}
+
+	/**
+	 * 过滤器内部处理逻辑,用于处理登录请求中的密码解密
+	 * @param request HTTP请求对象
+	 * @param response HTTP响应对象
+	 * @param chain 过滤器链
+	 * @throws ServletException 如果发生servlet相关异常
+	 * @throws IOException 如果发生I/O异常
+	 */
+	@Override
+	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
+			throws ServletException, IOException {
+		// 不是登录请求,直接向下执行
+		if (!StrUtil.containsAnyIgnoreCase(request.getRequestURI(), SecurityConstants.OAUTH_TOKEN_URL)) {
+			chain.doFilter(request, response);
+			return;
+		}
+
+		// 将请求流转换为可多次读取的请求流
+		RepeatBodyRequestWrapper requestWrapper = new RepeatBodyRequestWrapper(request);
+		Map<String, String[]> parameterMap = requestWrapper.getParameterMap();
+
+		// 构建前端对应解密AES 因子
+		AES aes = new AES(Mode.CFB, Padding.NoPadding,
+				new SecretKeySpec(authSecurityConfigProperties.getEncodeKey().getBytes(), KEY_ALGORITHM),
+				new IvParameterSpec(authSecurityConfigProperties.getEncodeKey().getBytes()));
+
+		parameterMap.forEach((k, v) -> {
+			String[] values = parameterMap.get(k);
+			if (!PASSWORD.equals(k) || ArrayUtil.isEmpty(values)) {
+				return;
+			}
+
+			// 解密密码
+			String decryptPassword = aes.decryptStr(values[0]);
+			parameterMap.put(k, new String[] { decryptPassword });
+		});
+		chain.doFilter(requestWrapper, response);
+	}
+
+}

+ 125 - 0
pig-auth/src/main/java/com/pig4cloud/pig/auth/support/filter/ValidateCodeFilter.java

@@ -0,0 +1,125 @@
+package com.pig4cloud.pig.auth.support.filter;
+
+/**
+ * 登录前处理器
+ *
+ * @author lengleng
+ * @date 2024/4/3
+ */
+
+import cn.hutool.core.util.StrUtil;
+import com.pig4cloud.pig.common.core.constant.CacheConstants;
+import com.pig4cloud.pig.common.core.constant.SecurityConstants;
+import com.pig4cloud.pig.common.core.exception.ValidateCodeException;
+import com.pig4cloud.pig.common.core.util.RedisUtils;
+import com.pig4cloud.pig.common.core.util.WebUtils;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import java.io.IOException;
+import java.util.Optional;
+
+/**
+ * 验证码过滤器:用于处理登录请求中的验证码校验
+ *
+ * @author lengleng
+ * @date 2025/05/30
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class ValidateCodeFilter extends OncePerRequestFilter {
+
+	private final AuthSecurityConfigProperties authSecurityConfigProperties;
+
+	/**
+	 * 过滤器内部处理逻辑,用于验证码校验
+	 * @param request HTTP请求
+	 * @param response HTTP响应
+	 * @param filterChain 过滤器链
+	 * @throws ServletException Servlet异常
+	 * @throws IOException IO异常
+	 */
+	@Override
+	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+			throws ServletException, IOException {
+
+		String requestUrl = request.getServletPath();
+
+		// 不是登录URL 请求直接跳过
+		if (!SecurityConstants.OAUTH_TOKEN_URL.equals(requestUrl)) {
+			filterChain.doFilter(request, response);
+			return;
+		}
+
+		// 如果登录URL 但是刷新token的请求,直接向下执行
+		String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
+		if (StrUtil.equals(SecurityConstants.REFRESH_TOKEN, grantType)) {
+			filterChain.doFilter(request, response);
+			return;
+		}
+
+		// 如果是密码模式 && 客户端不需要校验验证码
+		boolean isIgnoreClient = authSecurityConfigProperties.getIgnoreClients().contains(WebUtils.getClientId());
+		if (StrUtil.equalsAnyIgnoreCase(grantType, SecurityConstants.PASSWORD, SecurityConstants.CLIENT_CREDENTIALS,
+				SecurityConstants.AUTHORIZATION_CODE) && isIgnoreClient) {
+			filterChain.doFilter(request, response);
+			return;
+		}
+
+		// 校验验证码 1. 客户端开启验证码 2. 短信模式
+		try {
+			checkCode();
+			filterChain.doFilter(request, response);
+		}
+		catch (ValidateCodeException validateCodeException) {
+			throw new OAuth2AuthenticationException(validateCodeException.getMessage());
+		}
+	}
+
+	/**
+	 * 校验验证码
+	 */
+	private void checkCode() throws ValidateCodeException {
+		Optional<HttpServletRequest> request = WebUtils.getRequest();
+		String code = request.get().getParameter("code");
+
+		if (StrUtil.isBlank(code)) {
+			throw new ValidateCodeException("验证码不能为空");
+		}
+
+		String randomStr = request.get().getParameter("randomStr");
+
+		// https://gitee.com/log4j/pig/issues/IWA0D
+		String mobile = request.get().getParameter("mobile");
+		if (StrUtil.isNotBlank(mobile)) {
+			randomStr = mobile;
+		}
+
+		String key = CacheConstants.DEFAULT_CODE_KEY + randomStr;
+		if (!RedisUtils.hasKey(key)) {
+			throw new ValidateCodeException("验证码不合法");
+		}
+
+		String saveCode = RedisUtils.get(key);
+
+		if (StrUtil.isBlank(saveCode)) {
+			RedisUtils.delete(key);
+			throw new ValidateCodeException("验证码不合法");
+		}
+
+		if (!StrUtil.equals(saveCode, code)) {
+			RedisUtils.delete(key);
+			throw new ValidateCodeException("验证码不合法");
+		}
+	}
+
+}

+ 68 - 0
pig-auth/src/main/java/com/pig4cloud/pig/auth/support/handler/FormAuthenticationFailureHandler.java

@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.auth.support.handler;
+
+import cn.hutool.core.util.CharsetUtil;
+import cn.hutool.http.HttpUtil;
+import com.pig4cloud.pig.common.core.util.WebUtils;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
+
+import java.io.IOException;
+
+/**
+ * 表单登录失败处理逻辑
+ *
+ * @author lengleng
+ * @date 2025/05/30
+ */
+@Slf4j
+public class FormAuthenticationFailureHandler implements AuthenticationFailureHandler {
+
+	/**
+	 * 当认证失败时调用
+	 * @param request 认证尝试发生的请求
+	 * @param response 响应对象
+	 * @param exception 拒绝认证时抛出的异常
+	 */
+	@Override
+	@SneakyThrows
+	public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
+			AuthenticationException exception) {
+		log.debug("表单登录失败:{}", exception.getLocalizedMessage());
+
+		// 获取当前请求的context-path
+		String contextPath = request.getContextPath();
+
+		// 构建重定向URL,加入context-path
+		String url = HttpUtil.encodeParams(
+				String.format("%s/token/login?error=%s", contextPath, exception.getMessage()),
+				CharsetUtil.CHARSET_UTF_8);
+
+		try {
+			WebUtils.getResponse().sendRedirect(url);
+		}
+		catch (IOException e) {
+			log.error("重定向失败", e);
+		}
+	}
+
+}

+ 109 - 0
pig-auth/src/main/java/com/pig4cloud/pig/auth/support/handler/PigAuthenticationFailureEventHandler.java

@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.auth.support.handler;
+
+import cn.hutool.core.util.StrUtil;
+import com.pig4cloud.pig.admin.api.entity.SysLog;
+import com.pig4cloud.pig.common.core.constant.CommonConstants;
+import com.pig4cloud.pig.common.core.util.R;
+import com.pig4cloud.pig.common.core.util.SpringContextHolder;
+import com.pig4cloud.pig.common.log.event.SysLogEvent;
+import com.pig4cloud.pig.common.log.util.LogTypeEnum;
+import com.pig4cloud.pig.common.log.util.SysLogUtils;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.http.server.ServletServerHttpResponse;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
+
+import java.io.IOException;
+
+/**
+ * 认证失败处理器:处理用户认证失败事件并记录日志
+ *
+ * @author lengleng
+ * @date 2025/05/30
+ */
+@Slf4j
+public class PigAuthenticationFailureEventHandler implements AuthenticationFailureHandler {
+
+	private final MappingJackson2HttpMessageConverter errorHttpResponseConverter = new MappingJackson2HttpMessageConverter();
+
+	/**
+	 * 当认证失败时调用
+	 * @param request 认证请求
+	 * @param response 认证响应
+	 * @param exception 认证失败的异常
+	 */
+	@Override
+	@SneakyThrows
+	public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
+			AuthenticationException exception) {
+		String username = request.getParameter(OAuth2ParameterNames.USERNAME);
+
+		log.info("用户:{} 登录失败,异常:{}", username, exception.getLocalizedMessage());
+		SysLog logVo = SysLogUtils.getSysLog();
+		logVo.setTitle("登录失败");
+		logVo.setLogType(LogTypeEnum.ERROR.getType());
+		logVo.setException(exception.getLocalizedMessage());
+		// 发送异步日志事件
+		String startTimeStr = request.getHeader(CommonConstants.REQUEST_START_TIME);
+		if (StrUtil.isNotBlank(startTimeStr)) {
+			Long startTime = Long.parseLong(startTimeStr);
+			Long endTime = System.currentTimeMillis();
+			logVo.setTime(endTime - startTime);
+		}
+		logVo.setCreateBy(username);
+		SpringContextHolder.publishEvent(new SysLogEvent(logVo));
+		// 写出错误信息
+		sendErrorResponse(request, response, exception);
+	}
+
+	/**
+	 * 发送错误响应
+	 * @param request HTTP请求
+	 * @param response HTTP响应
+	 * @param exception 认证异常
+	 * @throws IOException 写入响应时发生IO异常
+	 */
+	private void sendErrorResponse(HttpServletRequest request, HttpServletResponse response,
+			AuthenticationException exception) throws IOException {
+		ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
+		httpResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
+		String errorMessage;
+
+		if (exception instanceof OAuth2AuthenticationException) {
+			OAuth2AuthenticationException authorizationException = (OAuth2AuthenticationException) exception;
+			errorMessage = StrUtil.isBlank(authorizationException.getError().getDescription())
+					? authorizationException.getError().getErrorCode()
+					: authorizationException.getError().getDescription();
+		}
+		else {
+			errorMessage = exception.getLocalizedMessage();
+		}
+
+		this.errorHttpResponseConverter.write(R.failed(errorMessage), MediaType.APPLICATION_JSON, httpResponse);
+	}
+
+}

+ 129 - 0
pig-auth/src/main/java/com/pig4cloud/pig/auth/support/handler/PigAuthenticationSuccessEventHandler.java

@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.auth.support.handler;
+
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.StrUtil;
+import com.pig4cloud.pig.admin.api.entity.SysLog;
+import com.pig4cloud.pig.common.core.constant.CommonConstants;
+import com.pig4cloud.pig.common.core.constant.SecurityConstants;
+import com.pig4cloud.pig.common.core.util.SpringContextHolder;
+import com.pig4cloud.pig.common.log.event.SysLogEvent;
+import com.pig4cloud.pig.common.log.util.SysLogUtils;
+import com.pig4cloud.pig.common.security.component.PigCustomOAuth2AccessTokenResponseHttpMessageConverter;
+import com.pig4cloud.pig.common.security.service.PigUser;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.server.ServletServerHttpResponse;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.core.OAuth2RefreshToken;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
+import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
+import org.springframework.util.CollectionUtils;
+
+import java.io.IOException;
+import java.time.temporal.ChronoUnit;
+import java.util.Map;
+
+/**
+ * 处理认证成功事件的处理器
+ *
+ * @author lengleng
+ * @date 2025/05/30
+ */
+@Slf4j
+public class PigAuthenticationSuccessEventHandler implements AuthenticationSuccessHandler {
+
+	private final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter = new PigCustomOAuth2AccessTokenResponseHttpMessageConverter();
+
+	/**
+	 * 用户认证成功时调用
+	 * @param request 触发认证成功的请求
+	 * @param response 响应对象
+	 * @param authentication 认证过程中创建的认证对象
+	 */
+	@SneakyThrows
+	@Override
+	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
+			Authentication authentication) {
+		OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) authentication;
+		Map<String, Object> map = accessTokenAuthentication.getAdditionalParameters();
+		if (MapUtil.isNotEmpty(map)) {
+			// 发送异步日志事件
+			PigUser userInfo = (PigUser) map.get(SecurityConstants.DETAILS_USER);
+			log.info("用户:{} 登录成功", userInfo.getName());
+			SecurityContextHolder.getContext().setAuthentication(accessTokenAuthentication);
+			SysLog logVo = SysLogUtils.getSysLog();
+			logVo.setTitle("登录成功");
+			String startTimeStr = request.getHeader(CommonConstants.REQUEST_START_TIME);
+			if (StrUtil.isNotBlank(startTimeStr)) {
+				Long startTime = Long.parseLong(startTimeStr);
+				Long endTime = System.currentTimeMillis();
+				logVo.setTime(endTime - startTime);
+			}
+			logVo.setCreateBy(userInfo.getName());
+			SpringContextHolder.publishEvent(new SysLogEvent(logVo));
+		}
+
+		// 输出token
+		sendAccessTokenResponse(request, response, authentication);
+	}
+
+	/**
+	 * 发送访问令牌响应
+	 * @param request HTTP请求
+	 * @param response HTTP响应
+	 * @param authentication 认证信息
+	 * @throws IOException 写入响应时可能抛出IO异常
+	 */
+	private void sendAccessTokenResponse(HttpServletRequest request, HttpServletResponse response,
+			Authentication authentication) throws IOException {
+
+		OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) authentication;
+
+		OAuth2AccessToken accessToken = accessTokenAuthentication.getAccessToken();
+		OAuth2RefreshToken refreshToken = accessTokenAuthentication.getRefreshToken();
+		Map<String, Object> additionalParameters = accessTokenAuthentication.getAdditionalParameters();
+
+		OAuth2AccessTokenResponse.Builder builder = OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
+			.tokenType(accessToken.getTokenType())
+			.scopes(accessToken.getScopes());
+		if (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) {
+			builder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()));
+		}
+		if (refreshToken != null) {
+			builder.refreshToken(refreshToken.getTokenValue());
+		}
+		if (!CollectionUtils.isEmpty(additionalParameters)) {
+			builder.additionalParameters(additionalParameters);
+		}
+		OAuth2AccessTokenResponse accessTokenResponse = builder.build();
+		ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
+
+		// 无状态 注意删除 context 上下文的信息
+		SecurityContextHolder.clearContext();
+
+		this.accessTokenHttpResponseConverter.write(accessTokenResponse, null, httpResponse);
+	}
+
+}

+ 88 - 0
pig-auth/src/main/java/com/pig4cloud/pig/auth/support/handler/PigLogoutSuccessEventHandler.java

@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.auth.support.handler;
+
+import cn.hutool.core.util.StrUtil;
+import com.pig4cloud.pig.admin.api.entity.SysLog;
+import com.pig4cloud.pig.common.core.constant.CommonConstants;
+import com.pig4cloud.pig.common.core.util.SpringContextHolder;
+import com.pig4cloud.pig.common.core.util.WebUtils;
+import com.pig4cloud.pig.common.log.event.SysLogEvent;
+import com.pig4cloud.pig.common.log.util.SysLogUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationListener;
+import org.springframework.http.HttpHeaders;
+import org.springframework.security.authentication.event.LogoutSuccessEvent;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
+import org.springframework.stereotype.Component;
+
+/**
+ * 处理用户退出成功事件处理器
+ *
+ * @author lengleng
+ * @date 2025/05/30
+ */
+@Slf4j
+@Component
+public class PigLogoutSuccessEventHandler implements ApplicationListener<LogoutSuccessEvent> {
+
+	/**
+	 * 处理登出成功事件
+	 * @param event 登出成功事件
+	 */
+	@Override
+	public void onApplicationEvent(LogoutSuccessEvent event) {
+		Authentication authentication = (Authentication) event.getSource();
+		if (authentication instanceof PreAuthenticatedAuthenticationToken) {
+			handle(authentication);
+		}
+	}
+
+	/**
+	 * 处理退出成功方法
+	 * <p>
+	 * 获取到登录的authentication 对象
+	 * @param authentication 登录对象
+	 */
+	public void handle(Authentication authentication) {
+		log.info("用户:{} 退出成功", authentication.getPrincipal());
+		SysLog logVo = SysLogUtils.getSysLog();
+		logVo.setTitle("退出成功");
+
+		// 设置对应的token
+		WebUtils.getRequest().ifPresent(request -> {
+			logVo.setParams(request.getHeader(HttpHeaders.AUTHORIZATION));
+			// 计算请求耗时
+			String startTimeStr = request.getHeader(CommonConstants.REQUEST_START_TIME);
+			if (StrUtil.isNotBlank(startTimeStr)) {
+				Long startTime = Long.parseLong(startTimeStr);
+				Long endTime = System.currentTimeMillis();
+				logVo.setTime(endTime - startTime);
+			}
+		});
+
+		// 这边设置ServiceId
+		if (authentication instanceof PreAuthenticatedAuthenticationToken) {
+			logVo.setServiceId(authentication.getCredentials().toString());
+		}
+		logVo.setCreateBy(authentication.getName());
+		// 发送异步日志事件
+		SpringContextHolder.publishEvent(new SysLogEvent(logVo));
+	}
+
+}

+ 48 - 0
pig-auth/src/main/java/com/pig4cloud/pig/auth/support/handler/SsoLogoutSuccessHandler.java

@@ -0,0 +1,48 @@
+package com.pig4cloud.pig.auth.support.handler;
+
+import cn.hutool.core.util.StrUtil;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.http.HttpHeaders;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
+
+import java.io.IOException;
+
+/**
+ * SSO 登出成功处理器,根据客户端传入的跳转地址进行重定向
+ *
+ * @author lengleng
+ * @date 2025/05/30
+ */
+public class SsoLogoutSuccessHandler implements LogoutSuccessHandler {
+
+	private static final String REDIRECT_URL = "redirect_url";
+
+	/**
+	 * 登出成功处理逻辑
+	 * @param request HTTP请求
+	 * @param response HTTP响应
+	 * @param authentication 认证信息
+	 * @throws IOException 重定向失败时抛出IO异常
+	 */
+	@Override
+	public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
+			throws IOException {
+		if (response == null) {
+			return;
+		}
+
+		// 获取请求参数中是否包含 回调地址
+		String redirectUrl = request.getParameter(REDIRECT_URL);
+		if (StrUtil.isNotBlank(redirectUrl)) {
+			response.sendRedirect(redirectUrl);
+		}
+		else if (StrUtil.isNotBlank(request.getHeader(HttpHeaders.REFERER))) {
+			// 默认跳转referer 地址
+			String referer = request.getHeader(HttpHeaders.REFERER);
+			response.sendRedirect(referer);
+		}
+	}
+
+}

+ 71 - 0
pig-auth/src/main/java/com/pig4cloud/pig/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationConverter.java

@@ -0,0 +1,71 @@
+package com.pig4cloud.pig.auth.support.password;
+
+import com.pig4cloud.pig.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationConverter;
+import com.pig4cloud.pig.common.security.util.OAuth2EndpointUtils;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.StringUtils;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * OAuth2 资源所有者密码认证转换器
+ *
+ * @author lengleng
+ * @author jumuning
+ * @date 2025/05/30
+ */
+public class OAuth2ResourceOwnerPasswordAuthenticationConverter
+		extends OAuth2ResourceOwnerBaseAuthenticationConverter<OAuth2ResourceOwnerPasswordAuthenticationToken> {
+
+	/**
+	 * 支持密码模式
+	 * @param grantType 授权类型
+	 */
+	@Override
+	public boolean support(String grantType) {
+		return AuthorizationGrantType.PASSWORD.getValue().equals(grantType);
+	}
+
+	/**
+	 * 构建OAuth2资源所有者密码认证令牌
+	 * @param clientPrincipal 客户端主体认证信息
+	 * @param requestedScopes 请求的作用域集合
+	 * @param additionalParameters 附加参数映射
+	 * @return 构建完成的OAuth2资源所有者密码认证令牌
+	 */
+	@Override
+	public OAuth2ResourceOwnerPasswordAuthenticationToken buildToken(Authentication clientPrincipal,
+			Set requestedScopes, Map additionalParameters) {
+		return new OAuth2ResourceOwnerPasswordAuthenticationToken(AuthorizationGrantType.PASSWORD, clientPrincipal,
+				requestedScopes, additionalParameters);
+	}
+
+	/**
+	 * 校验扩展参数 密码模式密码必须不为空
+	 * @param request 参数列表
+	 */
+	@Override
+	public void checkParams(HttpServletRequest request) {
+		MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
+		// username (REQUIRED)
+		String username = parameters.getFirst(OAuth2ParameterNames.USERNAME);
+		if (!StringUtils.hasText(username) || parameters.get(OAuth2ParameterNames.USERNAME).size() != 1) {
+			OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.USERNAME,
+					OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
+		}
+
+		// password (REQUIRED)
+		String password = parameters.getFirst(OAuth2ParameterNames.PASSWORD);
+		if (!StringUtils.hasText(password) || parameters.get(OAuth2ParameterNames.PASSWORD).size() != 1) {
+			OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.PASSWORD,
+					OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
+		}
+	}
+
+}

+ 82 - 0
pig-auth/src/main/java/com/pig4cloud/pig/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationProvider.java

@@ -0,0 +1,82 @@
+package com.pig4cloud.pig.auth.support.password;
+
+import com.pig4cloud.pig.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationProvider;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.security.oauth2.core.OAuth2Token;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
+
+import java.util.Map;
+
+/**
+ * OAuth2 资源所有者密码认证提供者
+ *
+ * @author lengleng
+ * @author jumuning
+ * @date 2025/05/30
+ * @since 0.2.3
+ */
+public class OAuth2ResourceOwnerPasswordAuthenticationProvider
+		extends OAuth2ResourceOwnerBaseAuthenticationProvider<OAuth2ResourceOwnerPasswordAuthenticationToken> {
+
+	private static final Logger LOGGER = LogManager.getLogger(OAuth2ResourceOwnerPasswordAuthenticationProvider.class);
+
+	/**
+	 * 使用提供的参数构造一个OAuth2ResourceOwnerPasswordAuthenticationProvider
+	 * @param authenticationManager 认证管理器
+	 * @param authorizationService 授权服务
+	 * @param tokenGenerator 令牌生成器
+	 * @since 0.2.3
+	 */
+	public OAuth2ResourceOwnerPasswordAuthenticationProvider(AuthenticationManager authenticationManager,
+			OAuth2AuthorizationService authorizationService,
+			OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
+		super(authenticationManager, authorizationService, tokenGenerator);
+	}
+
+	/**
+	 * 构建用户名密码认证令牌
+	 * @param reqParameters 请求参数映射,包含用户名和密码
+	 * @return 用户名密码认证令牌
+	 */
+	@Override
+	public UsernamePasswordAuthenticationToken buildToken(Map<String, Object> reqParameters) {
+		String username = (String) reqParameters.get(OAuth2ParameterNames.USERNAME);
+		String password = (String) reqParameters.get(OAuth2ParameterNames.PASSWORD);
+		return new UsernamePasswordAuthenticationToken(username, password);
+	}
+
+	/**
+	 * 判断是否支持指定的认证类型
+	 * @param authentication 待验证的认证类型
+	 * @return 如果支持该认证类型则返回true,否则返回false
+	 */
+	@Override
+	public boolean supports(Class<?> authentication) {
+		boolean supports = OAuth2ResourceOwnerPasswordAuthenticationToken.class.isAssignableFrom(authentication);
+		LOGGER.debug("supports authentication=" + authentication + " returning " + supports);
+		return supports;
+	}
+
+	/**
+	 * 检查客户端是否支持密码授权模式
+	 * @param registeredClient 已注册的客户端
+	 * @throws OAuth2AuthenticationException 当客户端不支持密码授权模式时抛出异常
+	 */
+	@Override
+	public void checkClient(RegisteredClient registeredClient) {
+		assert registeredClient != null;
+		if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.PASSWORD)) {
+			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
+		}
+	}
+
+}

+ 35 - 0
pig-auth/src/main/java/com/pig4cloud/pig/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationToken.java

@@ -0,0 +1,35 @@
+package com.pig4cloud.pig.auth.support.password;
+
+import com.pig4cloud.pig.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+
+import java.io.Serial;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * OAuth2资源所有者密码认证令牌
+ *
+ * @author lengleng
+ * @author jumuning
+ * @date 2025/05/30
+ */
+public class OAuth2ResourceOwnerPasswordAuthenticationToken extends OAuth2ResourceOwnerBaseAuthenticationToken {
+
+	@Serial
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 构造OAuth2资源所有者密码认证令牌
+	 * @param authorizationGrantType 授权类型
+	 * @param clientPrincipal 客户端认证主体
+	 * @param scopes 权限范围集合
+	 * @param additionalParameters 附加参数映射
+	 */
+	public OAuth2ResourceOwnerPasswordAuthenticationToken(AuthorizationGrantType authorizationGrantType,
+			Authentication clientPrincipal, Set<String> scopes, Map<String, Object> additionalParameters) {
+		super(authorizationGrantType, clientPrincipal, scopes, additionalParameters);
+	}
+
+}

+ 4 - 0
pig-auth/src/main/java/com/pig4cloud/pig/auth/support/password/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * 密码模式
+ */
+package com.pig4cloud.pig.auth.support.password;

+ 57 - 0
pig-auth/src/main/java/com/pig4cloud/pig/auth/support/sms/OAuth2ResourceOwnerSmsAuthenticationConverter.java

@@ -0,0 +1,57 @@
+package com.pig4cloud.pig.auth.support.sms;
+
+import com.pig4cloud.pig.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationConverter;
+import com.pig4cloud.pig.common.core.constant.SecurityConstants;
+import com.pig4cloud.pig.common.security.util.OAuth2EndpointUtils;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.StringUtils;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author lengleng
+ * @date 2022-05-31
+ *
+ * 短信登录转换器
+ */
+public class OAuth2ResourceOwnerSmsAuthenticationConverter
+		extends OAuth2ResourceOwnerBaseAuthenticationConverter<OAuth2ResourceOwnerSmsAuthenticationToken> {
+
+	/**
+	 * 是否支持此convert
+	 * @param grantType 授权类型
+	 * @return
+	 */
+	@Override
+	public boolean support(String grantType) {
+		return SecurityConstants.MOBILE.equals(grantType);
+	}
+
+	@Override
+	public OAuth2ResourceOwnerSmsAuthenticationToken buildToken(Authentication clientPrincipal, Set requestedScopes,
+			Map additionalParameters) {
+		return new OAuth2ResourceOwnerSmsAuthenticationToken(new AuthorizationGrantType(SecurityConstants.MOBILE),
+				clientPrincipal, requestedScopes, additionalParameters);
+	}
+
+	/**
+	 * 校验扩展参数 密码模式密码必须不为空
+	 * @param request 参数列表
+	 */
+	@Override
+	public void checkParams(HttpServletRequest request) {
+		MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
+		// PHONE (REQUIRED)
+		String phone = parameters.getFirst(SecurityConstants.SMS_PARAMETER_NAME);
+		if (!StringUtils.hasText(phone) || parameters.get(SecurityConstants.SMS_PARAMETER_NAME).size() != 1) {
+			OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, SecurityConstants.SMS_PARAMETER_NAME,
+					OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
+		}
+	}
+
+}

+ 66 - 0
pig-auth/src/main/java/com/pig4cloud/pig/auth/support/sms/OAuth2ResourceOwnerSmsAuthenticationProvider.java

@@ -0,0 +1,66 @@
+package com.pig4cloud.pig.auth.support.sms;
+
+import com.pig4cloud.pig.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationProvider;
+import com.pig4cloud.pig.common.core.constant.SecurityConstants;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.security.oauth2.core.OAuth2Token;
+import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
+
+import java.util.Map;
+
+/**
+ * @author lengleng
+ * @date date
+ *
+ * 短信登录的核心处理
+ */
+public class OAuth2ResourceOwnerSmsAuthenticationProvider
+		extends OAuth2ResourceOwnerBaseAuthenticationProvider<OAuth2ResourceOwnerSmsAuthenticationToken> {
+
+	private static final Logger LOGGER = LogManager.getLogger(OAuth2ResourceOwnerSmsAuthenticationProvider.class);
+
+	/**
+	 * Constructs an {@code OAuth2AuthorizationCodeAuthenticationProvider} using the
+	 * provided parameters.
+	 * @param authenticationManager
+	 * @param authorizationService the authorization service
+	 * @param tokenGenerator the token generator
+	 * @since 0.2.3
+	 */
+	public OAuth2ResourceOwnerSmsAuthenticationProvider(AuthenticationManager authenticationManager,
+			OAuth2AuthorizationService authorizationService,
+			OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
+		super(authenticationManager, authorizationService, tokenGenerator);
+	}
+
+	@Override
+	public boolean supports(Class<?> authentication) {
+		boolean supports = OAuth2ResourceOwnerSmsAuthenticationToken.class.isAssignableFrom(authentication);
+		LOGGER.debug("supports authentication=" + authentication + " returning " + supports);
+		return supports;
+	}
+
+	@Override
+	public void checkClient(RegisteredClient registeredClient) {
+		assert registeredClient != null;
+		if (!registeredClient.getAuthorizationGrantTypes()
+			.contains(new AuthorizationGrantType(SecurityConstants.MOBILE))) {
+			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
+		}
+	}
+
+	@Override
+	public UsernamePasswordAuthenticationToken buildToken(Map<String, Object> reqParameters) {
+		String phone = (String) reqParameters.get(SecurityConstants.SMS_PARAMETER_NAME);
+		return new UsernamePasswordAuthenticationToken(phone, null);
+	}
+
+}

+ 21 - 0
pig-auth/src/main/java/com/pig4cloud/pig/auth/support/sms/OAuth2ResourceOwnerSmsAuthenticationToken.java

@@ -0,0 +1,21 @@
+package com.pig4cloud.pig.auth.support.sms;
+
+import com.pig4cloud.pig.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author lengleng
+ * @description 短信登录token信息
+ */
+public class OAuth2ResourceOwnerSmsAuthenticationToken extends OAuth2ResourceOwnerBaseAuthenticationToken {
+
+	public OAuth2ResourceOwnerSmsAuthenticationToken(AuthorizationGrantType authorizationGrantType,
+			Authentication clientPrincipal, Set<String> scopes, Map<String, Object> additionalParameters) {
+		super(authorizationGrantType, clientPrincipal, scopes, additionalParameters);
+	}
+
+}

+ 4 - 0
pig-auth/src/main/java/com/pig4cloud/pig/auth/support/sms/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * 短信模式
+ */
+package com.pig4cloud.pig.auth.support.sms;

+ 21 - 0
pig-auth/src/main/resources/application.yml

@@ -0,0 +1,21 @@
+server:
+  port: 3000
+
+spring:
+  application:
+    name: @artifactId@
+  cloud:
+    nacos:
+      username: @nacos.username@
+      password: @nacos.password@
+      discovery:
+        server-addr: ${NACOS_HOST:127.0.0.1}:${NACOS_PORT:8848}
+      config:
+        server-addr: ${spring.cloud.nacos.discovery.server-addr}
+  config:
+    import:
+      - nacos:[email protected]@.yml
+      - nacos:${spring.application.name}[email protected]@.yml
+
+
+

+ 78 - 0
pig-auth/src/main/resources/logback-spring.xml

@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<configuration debug="false" scan="false">
+	<springProperty scop="context" name="spring.application.name" source="spring.application.name" defaultValue=""/>
+	<property name="log.path" value="logs/${spring.application.name}"/>
+	<!-- 彩色日志格式 -->
+	<property name="CONSOLE_LOG_PATTERN"
+			  value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
+	<property name="FILE_LOG_PATTERN" value=":%d{yyyy-MM-dd HH:mm:ss.SSS} %5p %t %c{1}: %m%n"/>
+	<!-- 彩色日志依赖的渲染类 -->
+	<conversionRule conversionWord="clr" class="org.springframework.boot.logging.logback.ColorConverter"/>
+	<conversionRule conversionWord="wex"
+					class="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
+	<conversionRule conversionWord="wEx"
+					class="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
+	<!-- Console log output -->
+	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>${CONSOLE_LOG_PATTERN}</pattern>
+		</encoder>
+	</appender>
+
+	<!-- Log file debug output -->
+	<appender name="debug" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<file>${log.path}/debug.log</file>
+		<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+			<fileNamePattern>${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
+			<maxFileSize>50MB</maxFileSize>
+			<maxHistory>30</maxHistory>
+		</rollingPolicy>
+		<encoder>
+			<pattern>${FILE_LOG_PATTERN}</pattern>
+		</encoder>
+	</appender>
+
+	<!-- Log file error output -->
+	<appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<file>${log.path}/error.log</file>
+		<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+			<fileNamePattern>${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
+			<maxFileSize>50MB</maxFileSize>
+			<maxHistory>30</maxHistory>
+		</rollingPolicy>
+		<encoder>
+			<pattern>${FILE_LOG_PATTERN}</pattern>
+		</encoder>
+		<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+			<level>ERROR</level>
+		</filter>
+	</appender>
+
+	<!--nacos 心跳 INFO 屏蔽-->
+	<logger name="com.alibaba.nacos" level="OFF">
+		<appender-ref ref="error"/>
+	</logger>
+
+	<!-- Level: FATAL 0  ERROR 3  WARN 4  INFO 6  DEBUG 7 -->
+	<root level="DEBUG">
+		<appender-ref ref="console"/>
+		<appender-ref ref="debug"/>
+		<appender-ref ref="error"/>
+	</root>
+</configuration>

+ 52 - 0
pig-auth/src/main/resources/templates/ftl/confirm.ftl

@@ -0,0 +1,52 @@
+<#assign content>
+	<div class="mb-7">
+		<h3 class="font-semibold text-2xl text-gray-800 dark:text-gray-200 text-center transition-colors">应用授权确认</h3>
+		<div class="mt-4 flex items-center justify-center text-sm">
+			<div class="px-4 py-2 bg-gray-50 dark:bg-gray-800 rounded-full transition-all">
+				<div class="text-gray-700 dark:text-gray-300">
+                    <#if principalName=="anonymousUser">
+						<span class="text-gray-500 dark:text-gray-400">未登录用户</span>
+                    <#else>
+						<a href="https://pig4cloud.com" class="text-purple-600 dark:text-purple-400 hover:text-purple-700 dark:hover:text-purple-300 font-medium transition-colors">
+                            ${principalName}
+						</a>
+                    </#if>
+				</div>
+			</div>
+		</div>
+	</div>
+
+	<form id='confirmationForm' name='confirmationForm' action="${request.contextPath}/oauth2/authorize" method='post'>
+		<input type="hidden" name="client_id" value="${clientId}">
+		<input type="hidden" name="state" value="${state}">
+
+		<div class="space-y-6">
+			<div class="mb-4">
+				<p class="text-gray-700 dark:text-gray-300 mb-3 transition-colors">将获得以下权限:</p>
+				<div class="space-y-3 bg-gray-50 dark:bg-gray-800 p-4 rounded-lg border border-gray-200 dark:border-gray-700 transition-all">
+                    <#list scopeList as scope>
+						<div class="flex items-center">
+							<input type="checkbox" checked="checked" name="scope" value="${scope}"
+							       class="h-4 w-4 text-purple-600 focus:ring-purple-500 dark:focus:ring-purple-400 border-gray-300 dark:border-gray-600 rounded transition-colors">
+							<label class="ml-3 text-gray-600 dark:text-gray-400 transition-colors">${scope}</label>
+						</div>
+                    </#list>
+				</div>
+			</div>
+
+			<div class="text-sm text-gray-500 dark:text-gray-400 mb-4 transition-colors">
+				授权后表明你已同意
+				<a href="#" class="text-purple-600 dark:text-purple-400 hover:text-purple-700 dark:hover:text-purple-300 transition-colors">服务协议</a>
+			</div>
+
+			<div>
+				<button type="submit" id="write-email-btn"
+				        class="w-full flex justify-center bg-purple-600 hover:bg-purple-700 dark:bg-purple-700 dark:hover:bg-purple-600 text-gray-100 p-3 rounded-lg tracking-wide font-semibold cursor-pointer transition-all duration-300 transform hover:scale-[1.02]">
+					确认授权
+				</button>
+			</div>
+		</div>
+	</form>
+</#assign>
+
+<#include "layout/base.ftl">

+ 117 - 0
pig-auth/src/main/resources/templates/ftl/layout/base.ftl

@@ -0,0 +1,117 @@
+<!doctype html>
+<html class="dark">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title><#if title??>${title}<#else>Pig 统一身份认证</#if></title>
+    <script src="https://cdn.tailwindcss.com"></script>
+    <script>
+        tailwind.config = {
+            darkMode: 'class',
+            theme: {
+                extend: {
+                    colors: {
+                        primary: '#6366f1'
+                    }
+                }
+            }
+        }
+    </script>
+    <style>
+        .dark-mode-toggle {
+            transition: all 0.3s ease;
+        }
+        .dark-mode-toggle:hover {
+            transform: scale(1.1);
+        }
+        .animate-spin-slow {
+            animation: spin 2s linear infinite;
+        }
+    </style>
+    <#if extraHead??>${extraHead}</#if>
+</head>
+<body class="min-h-screen">
+<!-- 暗黑模式切换按钮 -->
+<div class="fixed top-6 right-6 z-50">
+    <button id="theme-toggle" class="dark-mode-toggle p-3 rounded-xl bg-purple-100/90 dark:bg-gray-800/90 text-purple-600 dark:text-yellow-300 hover:bg-purple-200 dark:hover:bg-gray-700 focus:outline-none transition-all duration-300 shadow-lg">
+        <!-- 月亮图标 -->
+        <svg id="moon-icon" class="w-6 h-6 hidden dark:block" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"></path>
+        </svg>
+        <!-- 太阳图标 -->
+        <svg id="sun-icon" class="w-6 h-6 block dark:hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"></path>
+        </svg>
+    </button>
+</div>
+
+<!-- 背景渐变 -->
+<div class="bg-purple-900 fixed top-0 left-0 bg-gradient-to-b from-gray-900 via-gray-900 to-purple-800 w-full h-full -z-10">
+</div>
+
+<div class="relative min-h-screen flex flex-col md:flex-row items-center justify-center px-4 py-12 md:px-8">
+    <!-- 左侧说明文本 -->
+    <div class="flex-1 max-w-xl mb-8 md:mb-0 md:mr-12 z-10">
+        <div class="hidden md:block">
+            <h2 class="text-3xl font-bold text-white mb-6">统一身份认证平台</h2>
+            <p class="text-gray-300 text-lg leading-relaxed opacity-90">
+                为企业提供一套集中式的账号、权限、认证、审计工具,帮助企业打通身份数据孤岛,实现"一个账号、一次认证、多点通行"的效果,强化企业安全体系的同时,提升组织管理效率,助力企业数字化升级转型。
+            </p>
+        </div>
+    </div>
+
+    <!-- 右侧内容区 -->
+    <div class="flex-1 w-full max-w-md z-10">
+        <div class="p-8 md:p-10 bg-white dark:bg-gray-900 rounded-2xl shadow-2xl backdrop-blur-sm transition-all duration-300">
+            <#if content??>${content}</#if>
+
+            <!-- 版权信息 -->
+            <div class="mt-8 pt-6 text-center text-gray-400 dark:text-gray-500 text-sm border-t border-gray-100 dark:border-gray-800">
+                <span>
+                    Copyright © 2021-2025
+                    <a href="#" class="text-purple-600 dark:text-purple-400 hover:text-purple-700 dark:hover:text-purple-300 transition-colors">PIGCLOUD</a>
+                </span>
+            </div>
+        </div>
+    </div>
+</div>
+
+<!-- 底部波浪装饰 -->
+<svg class="fixed bottom-0 left-0 w-full transition-colors -z-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320">
+    <path class="fill-white dark:fill-gray-900 transition-colors"
+          d="M0,0L40,42.7C80,85,160,171,240,197.3C320,224,400,192,480,154.7C560,117,640,75,720,74.7C800,75,880,117,960,154.7C1040,192,1120,224,1200,213.3C1280,203,1360,149,1400,122.7L1440,96L1440,320L1400,320C1360,320,1280,320,1200,320C1120,320,1040,320,960,320C880,320,800,320,720,320C640,320,560,320,480,320C400,320,320,320,240,320C160,320,80,320,40,320L0,320Z">
+    </path>
+</svg>
+
+<script>
+    // 主题切换功能
+    const themeToggle = document.getElementById('theme-toggle');
+    const html = document.documentElement;
+
+    // 检查本地存储中的主题设置
+    if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
+        html.classList.add('dark');
+    } else {
+        html.classList.remove('dark');
+    }
+
+    // 切换主题
+    themeToggle.addEventListener('click', () => {
+        html.classList.toggle('dark');
+
+        // 保存主题设置到本地存储
+        if (html.classList.contains('dark')) {
+            localStorage.theme = 'dark';
+        } else {
+            localStorage.theme = 'light';
+        }
+
+        // 添加点击动画效果
+        themeToggle.classList.add('animate-spin-slow');
+        setTimeout(() => {
+            themeToggle.classList.remove('animate-spin-slow');
+        }, 300);
+    });
+</script>
+</body>
+</html>

+ 41 - 0
pig-auth/src/main/resources/templates/ftl/login.ftl

@@ -0,0 +1,41 @@
+<#assign content>
+    <div class="mb-8 text-center">
+        <svg class="w-16 h-16 mx-auto mb-4 text-purple-600 dark:text-purple-400" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+            <path d="M12 14.5V16.5M7 10.0288C7.47142 10.0288 7.86284 9.63734 7.86284 9.16592C7.86284 8.69449 7.47142 8.30307 7 8.30307C6.52858 8.30307 6.13716 8.69449 6.13716 9.16592C6.13716 9.63734 6.52858 10.0288 7 10.0288ZM17 10.0288C17.4714 10.0288 17.8628 9.63734 17.8628 9.16592C17.8628 8.69449 17.4714 8.30307 17 8.30307C16.5286 8.30307 16.1372 8.69449 16.1372 9.16592C16.1372 9.63734 16.5286 10.0288 17 10.0288ZM12 12.5C13.6569 12.5 15 11.1569 15 9.5C15 7.84315 13.6569 6.5 12 6.5C10.3431 6.5 9 7.84315 9 9.5C9 11.1569 10.3431 12.5 12 12.5Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+            <path d="M12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21Z" stroke="currentColor" stroke-width="1.5"/>
+            <path d="M17.6972 19.7C16.0993 18.0307 14.125 17 12 17C9.87499 17 7.90072 18.0307 6.30283 19.7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
+        </svg>
+        <p class="mt-2 text-sm text-gray-600 dark:text-gray-400">安全便捷的企业级认证服务</p>
+    </div>
+
+    <form class="form-signin" action="${request.contextPath}/oauth2/form" method="post">
+        <input type="hidden" name="client_id" value="pig">
+        <input type="hidden" name="grant_type" value="password">
+        <div class="space-y-6">
+            <div class="">
+                <input class="w-full text-sm px-4 py-3 bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg focus:outline-none focus:border-purple-400 dark:focus:border-purple-500 dark:text-gray-300 transition-colors"
+                       type="text" placeholder="账号" name="username" required>
+            </div>
+
+            <div class="relative">
+                <input placeholder="密码" type="password" name="password" required
+                       class="w-full text-sm px-4 py-3 bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg focus:outline-none focus:border-purple-400 dark:focus:border-purple-500 dark:text-gray-300 transition-colors">
+            </div>
+
+            <#if error??>
+                <div class="relative text-center">
+                    <span class="text-red-600 dark:text-red-400">${error}</span>
+                </div>
+            </#if>
+
+            <div>
+                <button type="submit"
+                        class="w-full flex justify-center bg-purple-600 hover:bg-purple-700 dark:bg-purple-700 dark:hover:bg-purple-600 text-gray-100 p-3 rounded-lg tracking-wide font-semibold cursor-pointer transition-all duration-300 transform hover:scale-[1.02]">
+                    登 录
+                </button>
+            </div>
+        </div>
+    </form>
+</#assign>
+
+<#include "layout/base.ftl">

+ 236 - 0
pig-common/pig-common-bom/pom.xml

@@ -0,0 +1,236 @@
+<?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">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.pig4cloud</groupId>
+    <artifactId>pig-common-bom</artifactId>
+    <version>${revision}</version>
+    <packaging>pom</packaging>
+
+    <name>pig-common-bom</name>
+    <url>pig cloud parent</url>
+    <description>pig cloud parent</description>
+
+    <properties>
+        <!-- 项目版本号 -->
+        <revision>3.9.0</revision>
+        <spring-boot.version>3.5.0</spring-boot.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <maven.compiler.source>17</maven.compiler.source>
+        <maven.compiler.target>17</maven.compiler.target>
+        <git.commit.plugin>9.0.1</git.commit.plugin>
+        <fastjson.version>1.2.83_noneautotype</fastjson.version>
+        <springdoc.version>2.8.8</springdoc.version>
+        <swagger.core.version>2.2.32</swagger.core.version>
+        <mybatis-plus.version>3.5.12</mybatis-plus.version>
+        <mysql.version>9.2.0</mysql.version>
+        <dynamic-ds.version>4.3.1</dynamic-ds.version>
+        <seata.version>1.7.0</seata.version>
+        <excel.version>3.4.2</excel.version>
+        <asm.version>7.1</asm.version>
+        <sms.version>3.3.5</sms.version>
+        <jaxb.version>2.3.5</jaxb.version>
+        <shardingsphere.version>5.4.1</shardingsphere.version>
+        <hutool.version>5.8.38</hutool.version>
+        <sentinel.version>1.8.4</sentinel.version>
+        <common.io.version>2.18.0</common.io.version>
+        <spring.checkstyle.plugin>0.0.43</spring.checkstyle.plugin>
+        <flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
+    </properties>
+
+    <!-- 定义全局jar版本,模块使用需要再次引入但不用写版本号-->
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>com.pig4cloud</groupId>
+                <artifactId>pig-common-core</artifactId>
+                <version>${revision}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.pig4cloud</groupId>
+                <artifactId>pig-common-datasource</artifactId>
+                <version>${revision}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.pig4cloud</groupId>
+                <artifactId>pig-common-log</artifactId>
+                <version>${revision}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.pig4cloud</groupId>
+                <artifactId>pig-common-mybatis</artifactId>
+                <version>${revision}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.pig4cloud</groupId>
+                <artifactId>pig-common-oss</artifactId>
+                <version>${revision}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.pig4cloud</groupId>
+                <artifactId>pig-common-security</artifactId>
+                <version>${revision}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.pig4cloud</groupId>
+                <artifactId>pig-common-feign</artifactId>
+                <version>${revision}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.pig4cloud</groupId>
+                <artifactId>pig-common-swagger</artifactId>
+                <version>${revision}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.pig4cloud</groupId>
+                <artifactId>pig-common-xss</artifactId>
+                <version>${revision}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.pig4cloud</groupId>
+                <artifactId>pig-common-excel</artifactId>
+                <version>${revision}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.pig4cloud</groupId>
+                <artifactId>pig-upms-api</artifactId>
+                <version>${revision}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.mysql</groupId>
+                <artifactId>mysql-connector-j</artifactId>
+                <version>${mysql.version}</version>
+            </dependency>
+            <!--springdoc -->
+            <dependency>
+                <groupId>org.springdoc</groupId>
+                <artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
+                <version>${springdoc.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.springdoc</groupId>
+                <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+                <version>${springdoc.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.springdoc</groupId>
+                <artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
+                <version>${springdoc.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>io.swagger.core.v3</groupId>
+                <artifactId>swagger-annotations-jakarta</artifactId>
+                <version>${swagger.core.version}</version>
+            </dependency>
+            <!--fastjson 版本-->
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>fastjson</artifactId>
+                <version>${fastjson.version}</version>
+            </dependency>
+            <!-- excel 导入导出 -->
+            <dependency>
+                <groupId>com.pig4cloud.excel</groupId>
+                <artifactId>excel-spring-boot-starter</artifactId>
+                <version>${excel.version}</version>
+            </dependency>
+            <!-- commons.io 覆盖easyexcel-->
+            <dependency>
+                <artifactId>commons-io</artifactId>
+                <groupId>commons-io</groupId>
+                <version>${common.io.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.shardingsphere</groupId>
+                <artifactId>shardingsphere-jdbc-core</artifactId>
+                <version>${shardingsphere.version}</version>
+            </dependency>
+            <!-- 多数据源依赖 -->
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
+                <version>${dynamic-ds.version}</version>
+            </dependency>
+            <!--  短信工具:https://sms4j.com -->
+			<dependency>
+				<groupId>org.dromara.sms4j</groupId>
+				<artifactId>sms4j-spring-boot-starter</artifactId>
+				<version>${sms.version}</version>
+			</dependency>
+            <!--orm 相关-->
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>mybatis-plus-bom</artifactId>
+                <version>${mybatis-plus.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <!--hutool bom 工具类-->
+            <dependency>
+                <groupId>cn.hutool</groupId>
+                <artifactId>hutool-bom</artifactId>
+                <version>${hutool.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <!--web 模块-->
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-starter-web</artifactId>
+                <version>${spring-boot.version}</version>
+                <exclusions>
+                    <!--排除tomcat依赖-->
+                    <exclusion>
+                        <artifactId>spring-boot-starter-tomcat</artifactId>
+                        <groupId>org.springframework.boot</groupId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <build>
+        <plugins>
+            <!--打包jar 与git commit 关联插件-->
+            <plugin>
+                <groupId>io.github.git-commit-id</groupId>
+                <artifactId>git-commit-id-maven-plugin</artifactId>
+                <version>${git.commit.plugin}</version>
+            </plugin>
+            <!--代码格式插件,默认使用spring 规则-->
+            <plugin>
+                <groupId>io.spring.javaformat</groupId>
+                <artifactId>spring-javaformat-maven-plugin</artifactId>
+                <version>${spring.checkstyle.plugin}</version>
+            </plugin>
+            <!-- 统一 revision 版本 -->
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>flatten-maven-plugin</artifactId>
+                <version>${flatten-maven-plugin.version}</version>
+                <configuration>
+                    <flattenMode>resolveCiFriendliesOnly</flattenMode>
+                    <updatePomFile>true</updatePomFile>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>flatten</id>
+                        <phase>process-resources</phase>
+                        <goals>
+                            <goal>flatten</goal>
+                        </goals>
+                    </execution>
+                    <execution>
+                        <id>flatten.clean</id>
+                        <phase>clean</phase>
+                        <goals>
+                            <goal>clean</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 69 - 0
pig-common/pig-common-core/pom.xml

@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<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>
+    <parent>
+        <groupId>com.pig4cloud</groupId>
+        <artifactId>pig-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <artifactId>pig-common-core</artifactId>
+    <packaging>jar</packaging>
+
+    <description>pig 公共工具类核心包</description>
+
+
+    <dependencies>
+        <!--hutool-->
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-core</artifactId>
+        </dependency>
+        <!--redis-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+        <!--server-api-->
+        <dependency>
+            <groupId>jakarta.servlet</groupId>
+            <artifactId>jakarta.servlet-api</artifactId>
+        </dependency>
+        <!--hibernate-validator-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+        <!--json模块-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-json</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-webmvc</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-commons</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 65 - 0
pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/config/JacksonConfiguration.java

@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.core.config;
+
+import cn.hutool.core.date.DatePattern;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import com.pig4cloud.pig.common.core.jackson.PigJavaTimeModule;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
+import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+
+import java.time.ZoneId;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Jackson配置类,用于自定义Jackson的ObjectMapper配置
+ *
+ * @author lengleng
+ * @author L.cm
+ * @author lishangbu
+ * @date 2025/05/30
+ */
+@AutoConfiguration
+@ConditionalOnClass(ObjectMapper.class)
+@AutoConfigureBefore(JacksonAutoConfiguration.class)
+public class JacksonConfiguration {
+
+	/**
+	 * 自定义Jackson2ObjectMapperBuilder配置
+	 * @return Jackson2ObjectMapperBuilderCustomizer实例,包含以下配置: 1. 设置地区为中国 2. 设置系统默认时区 3.
+	 * 设置默认日期时间格式 4. 配置Long类型序列化为字符串 5. 注册自定义时间模块
+	 */
+	@Bean
+	@ConditionalOnMissingBean
+	public Jackson2ObjectMapperBuilderCustomizer customizer() {
+		return builder -> {
+			builder.locale(Locale.CHINA);
+			builder.timeZone(TimeZone.getTimeZone(ZoneId.systemDefault()));
+			builder.simpleDateFormat(DatePattern.NORM_DATETIME_PATTERN);
+			builder.serializerByType(Long.class, ToStringSerializer.instance);
+			builder.modules(new PigJavaTimeModule());
+		};
+	}
+
+}

+ 107 - 0
pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/config/RedisTemplateConfiguration.java

@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.core.config;
+
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.*;
+import org.springframework.data.redis.serializer.RedisSerializer;
+
+/**
+ * Redis 配置类
+ *
+ * @author lengleng
+ * @date 2025/05/30
+ */
+@EnableCaching
+@AutoConfiguration
+@AutoConfigureBefore(RedisAutoConfiguration.class)
+public class RedisTemplateConfiguration {
+
+	/**
+	 * 创建并配置RedisTemplate实例
+	 * @param factory Redis连接工厂
+	 * @return 配置好的RedisTemplate实例
+	 */
+	@Bean
+	@Primary
+	public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
+		RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
+		redisTemplate.setKeySerializer(RedisSerializer.string());
+		redisTemplate.setHashKeySerializer(RedisSerializer.string());
+		redisTemplate.setValueSerializer(RedisSerializer.java());
+		redisTemplate.setHashValueSerializer(RedisSerializer.java());
+		redisTemplate.setConnectionFactory(factory);
+		return redisTemplate;
+	}
+
+	/**
+	 * 创建并返回HashOperations实例
+	 * @param redisTemplate Redis模板
+	 * @return HashOperations实例
+	 */
+	@Bean
+	public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
+		return redisTemplate.opsForHash();
+	}
+
+	/**
+	 * 创建并返回用于操作Redis String类型数据的ValueOperations实例
+	 * @param redisTemplate Redis模板,用于操作Redis
+	 * @return ValueOperations实例,提供对Redis String类型数据的操作
+	 */
+	@Bean
+	public ValueOperations<String, String> valueOperations(RedisTemplate<String, String> redisTemplate) {
+		return redisTemplate.opsForValue();
+	}
+
+	/**
+	 * 创建并返回ListOperations实例
+	 * @param redisTemplate Redis模板
+	 * @return ListOperations实例
+	 */
+	@Bean
+	public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
+		return redisTemplate.opsForList();
+	}
+
+	/**
+	 * 创建并返回SetOperations实例
+	 * @param redisTemplate Redis模板
+	 * @return SetOperations实例
+	 */
+	@Bean
+	public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
+		return redisTemplate.opsForSet();
+	}
+
+	/**
+	 * 创建并返回ZSetOperations实例
+	 * @param redisTemplate Redis模板对象
+	 * @return ZSetOperations实例
+	 */
+	@Bean
+	public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
+		return redisTemplate.opsForZSet();
+	}
+
+}

+ 57 - 0
pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/config/RestTemplateConfiguration.java

@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.core.config;
+
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.cloud.client.loadbalancer.LoadBalanced;
+import org.springframework.context.annotation.Bean;
+import org.springframework.web.client.RestClient;
+import org.springframework.web.client.RestTemplate;
+
+/**
+ * RestTemplate 自动配置类
+ *
+ * @author lengleng
+ * @date 2025/05/30
+ */
+@AutoConfiguration
+public class RestTemplateConfiguration {
+
+	/**
+	 * 创建动态REST模板
+	 * @return {@link RestTemplate} REST模板实例
+	 */
+	@Bean
+	@LoadBalanced
+	@ConditionalOnProperty(value = "spring.cloud.nacos.discovery.enabled", havingValue = "true", matchIfMissing = true)
+	public RestTemplate restTemplate() {
+		return new RestTemplate();
+	}
+
+	/**
+	 * 创建支持负载均衡的REST客户端构建器
+	 * @return {@link RestClient.Builder} REST客户端构建器
+	 */
+	@Bean
+	@LoadBalanced
+	@ConditionalOnProperty(value = "spring.cloud.nacos.discovery.enabled", havingValue = "true", matchIfMissing = true)
+	RestClient.Builder restClientBuilder() {
+		return RestClient.builder();
+	}
+
+}

+ 67 - 0
pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/config/WebMvcConfiguration.java

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

+ 67 - 0
pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/CacheConstants.java

@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.core.constant;
+
+/**
+ * @author lengleng
+ * @date 2020年01月01日
+ * <p>
+ * 缓存的key 常量
+ */
+public interface CacheConstants {
+
+	/**
+	 * oauth 缓存前缀
+	 */
+	String PROJECT_OAUTH_ACCESS = "token::access_token";
+
+	/**
+	 * 验证码前缀
+	 */
+	String DEFAULT_CODE_KEY = "DEFAULT_CODE_KEY:";
+
+	/**
+	 * 菜单信息缓存
+	 */
+	String MENU_DETAILS = "menu_details";
+
+	/**
+	 * 用户信息缓存
+	 */
+	String USER_DETAILS = "user_details";
+
+	/**
+	 * 字典信息缓存
+	 */
+	String DICT_DETAILS = "dict_details";
+
+	/**
+	 * 角色信息缓存
+	 */
+	String ROLE_DETAILS = "role_details";
+
+	/**
+	 * oauth 客户端信息
+	 */
+	String CLIENT_DETAILS_KEY = "client:details";
+
+	/**
+	 * 参数缓存
+	 */
+	String PARAMS_DETAILS = "params_details";
+
+}

+ 95 - 0
pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/CommonConstants.java

@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.core.constant;
+
+/**
+ * @author lengleng
+ * @date 2019/2/1
+ */
+public interface CommonConstants {
+
+	/**
+	 * 删除
+	 */
+	String STATUS_DEL = "1";
+
+	/**
+	 * 正常
+	 */
+	String STATUS_NORMAL = "0";
+
+	/**
+	 * 锁定
+	 */
+	String STATUS_LOCK = "9";
+
+	/**
+	 * 菜单树根节点
+	 */
+	Long MENU_TREE_ROOT_ID = -1L;
+
+	/**
+	 * 菜单
+	 */
+	String MENU = "0";
+
+	/**
+	 * 编码
+	 */
+	String UTF8 = "UTF-8";
+
+	/**
+	 * JSON 资源
+	 */
+	String CONTENT_TYPE = "application/json; charset=utf-8";
+
+	/**
+	 * 前端工程名
+	 */
+	String FRONT_END_PROJECT = "pig-ui";
+
+	/**
+	 * 后端工程名
+	 */
+	String BACK_END_PROJECT = "pig";
+
+	/**
+	 * 成功标记
+	 */
+	Integer SUCCESS = 0;
+
+	/**
+	 * 失败标记
+	 */
+	Integer FAIL = 1;
+
+	/**
+	 * 当前页
+	 */
+	String CURRENT = "current";
+
+	/**
+	 * size
+	 */
+	String SIZE = "size";
+
+	/**
+	 * 请求开始时间
+	 */
+	String REQUEST_START_TIME = "REQUEST-START-TIME";
+
+}

+ 135 - 0
pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/SecurityConstants.java

@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.core.constant;
+
+/**
+ * @author lengleng
+ * @date 2019/2/1
+ */
+public interface SecurityConstants {
+
+	/**
+	 * 角色前缀
+	 */
+	String ROLE = "ROLE_";
+
+	/**
+	 * 前缀
+	 */
+	String PROJECT_PREFIX = "pig";
+
+	/**
+	 * 项目的license
+	 */
+	String PROJECT_LICENSE = "https://pig4cloud.com";
+
+	/**
+	 * 内部
+	 */
+	String FROM_IN = "Y";
+
+	/**
+	 * 标志
+	 */
+	String FROM = "from";
+
+	/**
+	 * 默认登录URL
+	 */
+	String OAUTH_TOKEN_URL = "/oauth2/token";
+
+	/**
+	 * grant_type
+	 */
+	String REFRESH_TOKEN = "refresh_token";
+
+	/**
+	 * password 模式
+	 */
+	String PASSWORD = "password";
+
+	/**
+	 * 授权码
+	 */
+	String AUTHORIZATION_CODE = "authorization_code";
+
+	/**
+	 * 手机号登录
+	 */
+	String MOBILE = "mobile";
+
+	/**
+	 * {bcrypt} 加密的特征码
+	 */
+	String BCRYPT = "{bcrypt}";
+
+	/**
+	 * {noop} 加密的特征码
+	 */
+	String NOOP = "{noop}";
+
+	/**
+	 * 用户名
+	 */
+	String USERNAME = "username";
+
+	/**
+	 * 用户信息
+	 */
+	String DETAILS_USER = "user_info";
+
+	/**
+	 * 用户ID
+	 */
+	String DETAILS_USER_ID = "user_id";
+
+	/**
+	 * 协议字段
+	 */
+	String DETAILS_LICENSE = "license";
+
+	/**
+	 * 验证码有效期,默认 60秒
+	 */
+	long CODE_TIME = 60;
+
+	/**
+	 * 验证码长度
+	 */
+	String CODE_SIZE = "6";
+
+	/**
+	 * 客户端模式
+	 */
+	String CLIENT_CREDENTIALS = "client_credentials";
+
+	/**
+	 * 客户端ID
+	 */
+	String CLIENT_ID = "clientId";
+
+	/**
+	 * 短信登录 参数名称
+	 */
+	String SMS_PARAMETER_NAME = "mobile";
+
+	/**
+	 * 授权码模式confirm
+	 */
+	String CUSTOM_CONSENT_PAGE_URI = "/oauth2/confirm_access";
+
+}

+ 35 - 0
pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/ServiceNameConstants.java

@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.core.constant;
+
+/**
+ * @author lengleng
+ * @date 2018年06月22日16:41:01 服务名称
+ */
+public interface ServiceNameConstants {
+
+	/**
+	 * 认证服务的SERVICEID
+	 */
+	String AUTH_SERVICE = "pig-auth";
+
+	/**
+	 * UPMS模块
+	 */
+	String UPMS_SERVICE = "pig-upms-biz";
+
+}

+ 52 - 0
pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/enums/DictTypeEnum.java

@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.core.constant.enums;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * @author lengleng
+ * @date 2019-05-16
+ * <p>
+ * 字典类型
+ */
+@Getter
+@RequiredArgsConstructor
+public enum DictTypeEnum {
+
+	/**
+	 * 字典类型-系统内置(不可修改)
+	 */
+	SYSTEM("1", "系统内置"),
+
+	/**
+	 * 字典类型-业务类型
+	 */
+	BIZ("0", "业务类");
+
+	/**
+	 * 类型
+	 */
+	private final String type;
+
+	/**
+	 * 描述
+	 */
+	private final String description;
+
+}

+ 50 - 0
pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/enums/LoginTypeEnum.java

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

+ 57 - 0
pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/constant/enums/MenuTypeEnum.java

@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.core.constant.enums;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * @author lengleng
+ * @date 2020-02-17
+ * <p>
+ * 菜单类型
+ */
+@Getter
+@RequiredArgsConstructor
+public enum MenuTypeEnum {
+
+	/**
+	 * 左侧菜单
+	 */
+	LEFT_MENU("0", "left"),
+
+	/**
+	 * 顶部菜单
+	 */
+	TOP_MENU("2", "top"),
+
+	/**
+	 * 按钮
+	 */
+	BUTTON("1", "button");
+
+	/**
+	 * 类型
+	 */
+	private final String type;
+
+	/**
+	 * 描述
+	 */
+	private final String description;
+
+}

+ 48 - 0
pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/exception/CheckedException.java

@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.core.exception;
+
+import lombok.NoArgsConstructor;
+
+/**
+ * 受检异常类,继承自RuntimeException
+ *
+ * @author lengleng
+ * @date 2025/05/30
+ */
+@NoArgsConstructor
+public class CheckedException extends RuntimeException {
+
+	private static final long serialVersionUID = 1L;
+
+	public CheckedException(String message) {
+		super(message);
+	}
+
+	public CheckedException(Throwable cause) {
+		super(cause);
+	}
+
+	public CheckedException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
+	public CheckedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+		super(message, cause, enableSuppression, writableStackTrace);
+	}
+
+}

+ 106 - 0
pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/exception/ErrorCodes.java

@@ -0,0 +1,106 @@
+package com.pig4cloud.pig.common.core.exception;
+
+/**
+ * 错误编码
+ *
+ * @author lengleng
+ * @date 2022/3/30
+ */
+public interface ErrorCodes {
+
+	/**
+	 * 系统编码错误
+	 */
+	String SYS_PARAM_CONFIG_ERROR = "sys.param.config.error";
+
+	/**
+	 * 系统内置参数不能删除
+	 */
+	String SYS_PARAM_DELETE_SYSTEM = "sys.param.delete.system";
+
+	/**
+	 * 用户已存在
+	 */
+	String SYS_USER_EXISTING = "sys.user.existing";
+
+	/**
+	 * 用户名已存在
+	 */
+	String SYS_USER_USERNAME_EXISTING = "sys.user.username.existing";
+
+	/**
+	 * 用户原密码错误,修改失败
+	 */
+	String SYS_USER_UPDATE_PASSWORDERROR = "sys.user.update.passwordError";
+
+	/**
+	 * 用户信息为空
+	 */
+	String SYS_USER_USERINFO_EMPTY = "sys.user.userInfo.empty";
+
+	/**
+	 * 获取当前用户信息失败
+	 */
+	String SYS_USER_QUERY_ERROR = "sys.user.query.error";
+
+	/**
+	 * 部门名称不存在
+	 */
+	String SYS_DEPT_DEPTNAME_INEXISTENCE = "sys.dept.deptName.inexistence";
+
+	/**
+	 * 岗位名称不存在
+	 */
+	String SYS_POST_POSTNAME_INEXISTENCE = "sys.post.postName.inexistence";
+
+	/**
+	 * 岗位名称或编码已经存在
+	 */
+	String SYS_POST_NAMEORCODE_EXISTING = "sys.post.nameOrCode.existing";
+
+	/**
+	 * 角色名称不存在
+	 */
+	String SYS_ROLE_ROLENAME_INEXISTENCE = "sys.role.roleName.inexistence";
+
+	/**
+	 * 角色名或角色编码已经存在
+	 */
+	String SYS_ROLE_NAMEORCODE_EXISTING = "sys.role.nameOrCode.existing";
+
+	/**
+	 * 菜单存在下级节点 删除失败
+	 */
+	String SYS_MENU_DELETE_EXISTING = "sys.menu.delete.existing";
+
+	/**
+	 * 系统内置字典不允许删除
+	 */
+	String SYS_DICT_DELETE_SYSTEM = "sys.dict.delete.system";
+
+	/**
+	 * 系统内置字典不能修改
+	 */
+	String SYS_DICT_UPDATE_SYSTEM = "sys.dict.update.system";
+
+	/**
+	 * 验证码发送频繁
+	 */
+	String SYS_APP_SMS_OFTEN = "sys.app.sms.often";
+
+	/**
+	 * 验证码错误
+	 */
+	String SYS_APP_SMS_ERROR = "sys.app.sms.error";
+
+	/**
+	 * 手机号未注册
+	 */
+	String SYS_APP_PHONE_UNREGISTERED = "sys.app.phone.unregistered";
+
+	/**
+	 * 未注册用户的短信混合系统配置键
+	 */
+	String SYS_SMS_BLEND_UNREGISTERED = "sys.app.sms.blend.unregistered";
+
+}

+ 48 - 0
pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/exception/PigDeniedException.java

@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.core.exception;
+
+import lombok.NoArgsConstructor;
+
+/**
+ * 授权拒绝异常类
+ *
+ * @author lengleng
+ * @date 2018/06/22
+ */
+@NoArgsConstructor
+public class PigDeniedException extends RuntimeException {
+
+	private static final long serialVersionUID = 1L;
+
+	public PigDeniedException(String message) {
+		super(message);
+	}
+
+	public PigDeniedException(Throwable cause) {
+		super(cause);
+	}
+
+	public PigDeniedException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
+	public PigDeniedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+		super(message, cause, enableSuppression, writableStackTrace);
+	}
+
+}

+ 36 - 0
pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/exception/ValidateCodeException.java

@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.core.exception;
+
+/**
+ * 验证码异常类
+ *
+ * @author lengleng
+ * @date 2018/06/22
+ */
+public class ValidateCodeException extends RuntimeException {
+
+	private static final long serialVersionUID = -7285211528095468156L;
+
+	public ValidateCodeException() {
+	}
+
+	public ValidateCodeException(String msg) {
+		super(msg);
+	}
+
+}

+ 58 - 0
pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/factory/YamlPropertySourceFactory.java

@@ -0,0 +1,58 @@
+package com.pig4cloud.pig.common.core.factory;
+
+import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
+import org.springframework.core.env.PropertiesPropertySource;
+import org.springframework.core.env.PropertySource;
+import org.springframework.core.io.support.EncodedResource;
+import org.springframework.core.io.support.PropertySourceFactory;
+import org.springframework.lang.Nullable;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ * YAML属性源工厂类:用于读取自定义YAML文件并转换为属性源
+ *
+ * @author lengleng
+ * @date 2025/05/30
+ */
+public class YamlPropertySourceFactory implements PropertySourceFactory {
+
+	/**
+	 * 创建属性源
+	 * @param name 属性源名称,可为空
+	 * @param resource 编码资源
+	 * @return 属性源对象
+	 * @throws IOException 读取资源时可能抛出IO异常
+	 */
+	@Override
+	public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {
+		Properties propertiesFromYaml = loadYamlIntoProperties(resource);
+		String sourceName = name != null ? name : resource.getResource().getFilename();
+		return new PropertiesPropertySource(sourceName, propertiesFromYaml);
+	}
+
+	/**
+	 * 将YAML资源加载为Properties对象
+	 * @param resource 编码后的资源对象
+	 * @return 加载后的Properties对象
+	 * @throws FileNotFoundException 当资源文件不存在时抛出
+	 */
+	private Properties loadYamlIntoProperties(EncodedResource resource) throws FileNotFoundException {
+		try {
+			YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
+			factory.setResources(resource.getResource());
+			factory.afterPropertiesSet();
+			return factory.getObject();
+		}
+		catch (IllegalStateException e) {
+			Throwable cause = e.getCause();
+			if (cause instanceof FileNotFoundException) {
+				throw (FileNotFoundException) e.getCause();
+			}
+			throw e;
+		}
+	}
+
+}

+ 69 - 0
pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/jackson/PigJavaTimeModule.java

@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.pig4cloud.pig.common.core.jackson;
+
+import cn.hutool.core.date.DatePattern;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.datatype.jsr310.PackageVersion;
+import com.fasterxml.jackson.datatype.jsr310.deser.*;
+import com.fasterxml.jackson.datatype.jsr310.ser.*;
+
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * Java 8 时间默认序列化模块
+ *
+ * @author L.cm
+ * @author lishanbu
+ * @author lengleng
+ * @date 2025/05/30
+ */
+
+public class PigJavaTimeModule extends SimpleModule {
+
+	/**
+	 * PigJavaTimeModule构造函数,用于初始化时间序列化和反序列化规则
+	 */
+	public PigJavaTimeModule() {
+		super(PackageVersion.VERSION);
+
+		// ======================= 时间序列化规则 ===============================
+		// yyyy-MM-dd HH:mm:ss
+		this.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DatePattern.NORM_DATETIME_FORMATTER));
+		// yyyy-MM-dd
+		this.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ISO_LOCAL_DATE));
+		// HH:mm:ss
+		this.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ISO_LOCAL_TIME));
+		// Instant 类型序列化
+		this.addSerializer(Instant.class, InstantSerializer.INSTANCE);
+		// Duration 类型序列化
+		this.addSerializer(Duration.class, DurationSerializer.INSTANCE);
+
+		// ======================= 时间反序列化规则 ==============================
+		// yyyy-MM-dd HH:mm:ss
+		this.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DatePattern.NORM_DATETIME_FORMATTER));
+		// yyyy-MM-dd
+		this.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ISO_LOCAL_DATE));
+		// HH:mm:ss
+		this.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ISO_LOCAL_TIME));
+		// Instant 反序列化
+		this.addDeserializer(Instant.class, InstantDeserializer.INSTANT);
+		// Duration 反序列化
+		this.addDeserializer(Duration.class, DurationDeserializer.INSTANCE);
+	}
+
+}

+ 149 - 0
pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/servlet/RepeatBodyRequestWrapper.java

@@ -0,0 +1,149 @@
+/*
+ * Copyright 2023-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.core.servlet;
+
+import jakarta.servlet.ReadListener;
+import jakarta.servlet.ServletInputStream;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletRequestWrapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.StreamUtils;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Request包装类:允许body重复读取
+ *
+ * @author lengleng
+ * @date 2025/05/30
+ */
+@Slf4j
+public class RepeatBodyRequestWrapper extends HttpServletRequestWrapper {
+
+	private final byte[] bodyByteArray;
+
+	private final Map<String, String[]> parameterMap;
+
+	public RepeatBodyRequestWrapper(HttpServletRequest request) {
+		super(request);
+		this.bodyByteArray = getByteBody(request);
+		// 使用 HashMap 以便后续可以修改
+		this.parameterMap = new HashMap<>(request.getParameterMap());
+	}
+
+	/**
+	 * 获取BufferedReader对象
+	 * @return 如果bodyByteArray为空则返回null,否则返回对应的BufferedReader
+	 */
+	@Override
+	public BufferedReader getReader() {
+		return ObjectUtils.isEmpty(this.bodyByteArray) ? null
+				: new BufferedReader(new InputStreamReader(getInputStream()));
+	}
+
+	/**
+	 * 获取Servlet输入流
+	 * @return ServletInputStream 基于bodyByteArray的输入流
+	 */
+	@Override
+	public ServletInputStream getInputStream() {
+		final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.bodyByteArray);
+		return new ServletInputStream() {
+			@Override
+			public boolean isFinished() {
+				return byteArrayInputStream.available() == 0;
+			}
+
+			@Override
+			public boolean isReady() {
+				return true; // 可以读取
+			}
+
+			@Override
+			public void setReadListener(ReadListener readListener) {
+				// doNothing
+			}
+
+			@Override
+			public int read() {
+				return byteArrayInputStream.read();
+			}
+		};
+	}
+
+	/**
+	 * 从HttpServletRequest中获取字节数组形式的请求体
+	 * @param request HTTP请求对象
+	 * @return 请求体字节数组,解析失败时返回空数组
+	 */
+	private static byte[] getByteBody(HttpServletRequest request) {
+		byte[] body = new byte[0];
+		try {
+			body = StreamUtils.copyToByteArray(request.getInputStream());
+		}
+		catch (IOException e) {
+			log.error("解析流中数据异常", e);
+		}
+		return body;
+	}
+
+	/**
+	 * 获取参数映射表
+	 * @return 可变的参数映射表
+	 */
+	@Override
+	public Map<String, String[]> getParameterMap() {
+		return this.parameterMap; // 返回可变的 parameterMap
+	}
+
+	/**
+	 * 设置新的参数映射
+	 * @param parameterMap 新的参数映射,将替换现有参数映射
+	 */
+	public void setParameterMap(Map<String, String[]> parameterMap) {
+		this.parameterMap.clear();
+		this.parameterMap.putAll(parameterMap);
+	}
+
+	/**
+	 * 根据参数名获取参数值
+	 * @param name 参数名
+	 * @return 参数值,如果不存在则返回null
+	 */
+	@Override
+	public String getParameter(String name) {
+		String[] values = parameterMap.get(name);
+		return (values != null && values.length > 0) ? values[0] : null;
+	}
+
+	/**
+	 * 根据参数名获取参数值数组
+	 * @param name 参数名
+	 * @return 参数值数组,如果不存在则返回null
+	 */
+	@Override
+	public String[] getParameterValues(String name) {
+		return parameterMap.get(name);
+	}
+
+}

+ 109 - 0
pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/util/ClassUtils.java

@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.core.util;
+
+import lombok.experimental.UtilityClass;
+import org.springframework.core.BridgeMethodResolver;
+import org.springframework.core.DefaultParameterNameDiscoverer;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.ParameterNameDiscoverer;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.core.annotation.SynthesizingMethodParameter;
+import org.springframework.web.method.HandlerMethod;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+/**
+ * 类工具类
+ *
+ * @author L.cm
+ */
+@UtilityClass
+public class ClassUtils extends org.springframework.util.ClassUtils {
+
+	private final ParameterNameDiscoverer PARAMETERNAMEDISCOVERER = new DefaultParameterNameDiscoverer();
+
+	/**
+	 * 获取方法参数信息
+	 * @param constructor 构造器
+	 * @param parameterIndex 参数序号
+	 * @return {MethodParameter}
+	 */
+	public MethodParameter getMethodParameter(Constructor<?> constructor, int parameterIndex) {
+		MethodParameter methodParameter = new SynthesizingMethodParameter(constructor, parameterIndex);
+		methodParameter.initParameterNameDiscovery(PARAMETERNAMEDISCOVERER);
+		return methodParameter;
+	}
+
+	/**
+	 * 获取方法参数信息
+	 * @param method 方法
+	 * @param parameterIndex 参数序号
+	 * @return {MethodParameter}
+	 */
+	public MethodParameter getMethodParameter(Method method, int parameterIndex) {
+		MethodParameter methodParameter = new SynthesizingMethodParameter(method, parameterIndex);
+		methodParameter.initParameterNameDiscovery(PARAMETERNAMEDISCOVERER);
+		return methodParameter;
+	}
+
+	/**
+	 * 获取Annotation
+	 * @param method Method
+	 * @param annotationType 注解类
+	 * @param <A> 泛型标记
+	 * @return {Annotation}
+	 */
+	public <A extends Annotation> A getAnnotation(Method method, Class<A> annotationType) {
+		Class<?> targetClass = method.getDeclaringClass();
+		// The method may be on an interface, but we need attributes from the target
+		// class.
+		// If the target class is null, the method will be unchanged.
+		Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
+		// If we are dealing with method with generic parameters, find the original
+		// method.
+		specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
+		// 先找方法,再找方法上的类
+		A annotation = AnnotatedElementUtils.findMergedAnnotation(specificMethod, annotationType);
+		if (null != annotation) {
+			return annotation;
+		}
+		// 获取类上面的Annotation,可能包含组合注解,故采用spring的工具类
+		return AnnotatedElementUtils.findMergedAnnotation(specificMethod.getDeclaringClass(), annotationType);
+	}
+
+	/**
+	 * 获取Annotation
+	 * @param handlerMethod HandlerMethod
+	 * @param annotationType 注解类
+	 * @param <A> 泛型标记
+	 * @return {Annotation}
+	 */
+	public <A extends Annotation> A getAnnotation(HandlerMethod handlerMethod, Class<A> annotationType) {
+		// 先找方法,再找方法上的类
+		A annotation = handlerMethod.getMethodAnnotation(annotationType);
+		if (null != annotation) {
+			return annotation;
+		}
+		// 获取类上面的Annotation,可能包含组合注解,故采用spring的工具类
+		Class<?> beanType = handlerMethod.getBeanType();
+		return AnnotatedElementUtils.findMergedAnnotation(beanType, annotationType);
+	}
+
+}

+ 49 - 0
pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/util/MsgUtils.java

@@ -0,0 +1,49 @@
+package com.pig4cloud.pig.common.core.util;
+
+import lombok.experimental.UtilityClass;
+import org.springframework.context.MessageSource;
+
+import java.util.Locale;
+
+/**
+ * i18n 工具类
+ *
+ * @author lengleng
+ * @date 2022/3/30
+ */
+@UtilityClass
+public class MsgUtils {
+
+	/**
+	 * 根据错误码获取中文错误信息
+	 * @param code 错误码
+	 * @return 对应的中文错误信息
+	 */
+	public String getMessage(String code) {
+		MessageSource messageSource = SpringContextHolder.getBean("messageSource");
+		return messageSource.getMessage(code, null, Locale.CHINA);
+	}
+
+	/**
+	 * 通过错误码和参数获取中文错误信息
+	 * @param code 错误码
+	 * @param objects 格式化参数
+	 * @return 格式化后的中文错误信息
+	 */
+	public String getMessage(String code, Object... objects) {
+		MessageSource messageSource = SpringContextHolder.getBean("messageSource");
+		return messageSource.getMessage(code, objects, Locale.CHINA);
+	}
+
+	/**
+	 * 通过错误码和参数获取中文错误信息
+	 * @param code 错误码
+	 * @param objects 格式化参数
+	 * @return 格式化后的中文错误信息
+	 */
+	public String getSecurityMessage(String code, Object... objects) {
+		MessageSource messageSource = SpringContextHolder.getBean("securityMessageSource");
+		return messageSource.getMessage(code, objects, Locale.CHINA);
+	}
+
+}

+ 89 - 0
pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/util/R.java

@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.core.util;
+
+import com.pig4cloud.pig.common.core.constant.CommonConstants;
+import lombok.*;
+import lombok.experimental.Accessors;
+import lombok.experimental.FieldNameConstants;
+
+import java.io.Serializable;
+
+/**
+ * 响应信息主体
+ *
+ * @param <T>
+ * @author lengleng
+ */
+@ToString
+@NoArgsConstructor
+@AllArgsConstructor
+@Accessors(chain = true)
+@FieldNameConstants
+public class R<T> implements Serializable {
+
+	private static final long serialVersionUID = 1L;
+
+	@Getter
+	@Setter
+	private int code;
+
+	@Getter
+	@Setter
+	private String msg;
+
+	@Getter
+	@Setter
+	private T data;
+
+	public static <T> R<T> ok() {
+		return restResult(null, CommonConstants.SUCCESS, null);
+	}
+
+	public static <T> R<T> ok(T data) {
+		return restResult(data, CommonConstants.SUCCESS, null);
+	}
+
+	public static <T> R<T> ok(T data, String msg) {
+		return restResult(data, CommonConstants.SUCCESS, msg);
+	}
+
+	public static <T> R<T> failed() {
+		return restResult(null, CommonConstants.FAIL, null);
+	}
+
+	public static <T> R<T> failed(String msg) {
+		return restResult(null, CommonConstants.FAIL, msg);
+	}
+
+	public static <T> R<T> failed(T data) {
+		return restResult(data, CommonConstants.FAIL, null);
+	}
+
+	public static <T> R<T> failed(T data, String msg) {
+		return restResult(data, CommonConstants.FAIL, msg);
+	}
+
+	public static <T> R<T> restResult(T data, int code, String msg) {
+		R<T> apiResult = new R<>();
+		apiResult.setCode(code);
+		apiResult.setData(data);
+		apiResult.setMsg(msg);
+		return apiResult;
+	}
+
+}

+ 662 - 0
pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/util/RedisUtils.java

@@ -0,0 +1,662 @@
+package com.pig4cloud.pig.common.core.util;
+
+import cn.hutool.core.convert.Convert;
+import lombok.experimental.UtilityClass;
+import org.springframework.data.redis.connection.RedisConnection;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.*;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.data.redis.core.script.RedisScript;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 缓存工具类
+ *
+ * @author XX
+ * @date 2023/05/12
+ */
+@UtilityClass
+public class RedisUtils {
+
+	private static final Long SUCCESS = 1L;
+
+	/**
+	 * 指定缓存失效时间
+	 * @param key 键
+	 * @param time 时间(秒)
+	 */
+	public boolean expire(String key, long time) {
+		RedisTemplate<Object, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		Optional.ofNullable(redisTemplate)
+			.filter(template -> time > 0)
+			.ifPresent(template -> template.expire(key, time, TimeUnit.SECONDS));
+		return true;
+	}
+
+	/**
+	 * 根据 key 获取过期时间
+	 * @param key 键 不能为null
+	 * @return 时间(秒) 返回0代表为永久有效
+	 */
+	public long getExpire(Object key) {
+		RedisTemplate<Object, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		return Optional.ofNullable(redisTemplate)
+			.map(template -> template.getExpire(key, TimeUnit.SECONDS))
+			.orElse(-1L);
+	}
+
+	/**
+	 * 查找匹配key
+	 * @param pattern key
+	 * @return /
+	 */
+	public List<String> scan(String pattern) {
+		RedisTemplate<Object, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		ScanOptions options = ScanOptions.scanOptions().match(pattern).build();
+		return Optional.ofNullable(redisTemplate).map(template -> {
+			RedisConnectionFactory factory = template.getConnectionFactory();
+			RedisConnection rc = Objects.requireNonNull(factory).getConnection();
+			Cursor<byte[]> cursor = rc.scan(options);
+			List<String> result = new ArrayList<>();
+			while (cursor.hasNext()) {
+				result.add(new String(cursor.next()));
+			}
+			RedisConnectionUtils.releaseConnection(rc, factory);
+			return result;
+		}).orElse(Collections.emptyList());
+	}
+
+	/**
+	 * 查找匹配key (使用KEYS命令)
+	 * @param pattern key模式,支持通配符 * ? [] 等
+	 * @return 匹配的key列表
+	 * @apiNote 注意:KEYS命令会阻塞Redis服务器,生产环境建议使用scan方法
+	 */
+	public Set<String> keys(String pattern) {
+		RedisTemplate<String, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		return Optional.ofNullable(redisTemplate)
+			.map(template -> template.keys(pattern))
+			.orElse(Collections.emptySet());
+	}
+
+	/**
+	 * 分页查询 key
+	 * @param patternKey key
+	 * @param page 页码
+	 * @param size 每页数目
+	 * @return /
+	 */
+	public List<String> findKeysForPage(String patternKey, int page, int size) {
+		RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		ScanOptions options = ScanOptions.scanOptions().match(patternKey).build();
+		RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
+		RedisConnection rc = Objects.requireNonNull(factory).getConnection();
+		Cursor<byte[]> cursor = rc.scan(options);
+		List<String> result = new ArrayList<>(size);
+		int tmpIndex = 0;
+		int fromIndex = page * size;
+		int toIndex = page * size + size;
+		while (cursor.hasNext()) {
+			if (tmpIndex >= fromIndex && tmpIndex < toIndex) {
+				result.add(new String(cursor.next()));
+				tmpIndex++;
+				continue;
+			}
+			// 获取到满足条件的数据后,就可以退出了
+			if (tmpIndex >= toIndex) {
+				break;
+			}
+			tmpIndex++;
+			cursor.next();
+		}
+		RedisConnectionUtils.releaseConnection(rc, factory);
+		return result;
+	}
+
+	/**
+	 * 判断key是否存在
+	 * @param key 键
+	 * @return true 存在 false不存在
+	 */
+	public boolean hasKey(String key) {
+		RedisTemplate<Object, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		return Optional.ofNullable(redisTemplate).map(template -> template.hasKey(key)).orElse(false);
+	}
+
+	/**
+	 * 删除缓存
+	 * @param keys 可以传一个值 或多个
+	 */
+	public void delete(String... keys) {
+		RedisTemplate<Object, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		if (keys != null) {
+			Arrays.stream(keys).forEach(redisTemplate::delete);
+		}
+	}
+
+	/**
+	 * 获取锁
+	 * @param lockKey 锁key
+	 * @param value value
+	 * @param expireTime:单位-秒
+	 * @return boolean
+	 */
+	public boolean getLock(String lockKey, String value, int expireTime) {
+		RedisTemplate<Object, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		return Optional.ofNullable(redisTemplate)
+			.map(template -> template.opsForValue().setIfAbsent(lockKey, value, expireTime, TimeUnit.SECONDS))
+			.orElse(false);
+	}
+
+	/**
+	 * 释放锁
+	 * @param lockKey 锁key
+	 * @param value value
+	 * @return boolean
+	 */
+	public boolean releaseLock(String lockKey, String value) {
+		RedisTemplate<Object, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
+		RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
+		return Optional.ofNullable(redisTemplate.execute(redisScript, Collections.singletonList(lockKey), value))
+			.map(Convert::toLong)
+			.filter(SUCCESS::equals)
+			.isPresent();
+	}
+
+	// ============================String=============================
+
+	/**
+	 * 普通缓存获取
+	 * @param key 键
+	 * @return 值
+	 */
+	public <T> T get(String key) {
+		RedisTemplate<String, T> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		return redisTemplate.opsForValue().get(key);
+	}
+
+	/**
+	 * 批量获取
+	 * @param keys
+	 * @return
+	 */
+	public <T> List<T> multiGet(List<String> keys) {
+		RedisTemplate<String, T> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		return redisTemplate.opsForValue().multiGet(keys);
+	}
+
+	/**
+	 * 普通缓存放入
+	 * @param key 键
+	 * @param value 值
+	 * @return true成功 false失败
+	 */
+	public boolean set(String key, Object value) {
+		RedisTemplate<Object, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		Optional.ofNullable(redisTemplate).map(template -> {
+			template.opsForValue().set(key, value);
+			return true;
+		});
+		return true;
+	}
+
+	/**
+	 * 普通缓存放入并设置时间
+	 * @param key 键
+	 * @param value 值
+	 * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
+	 * @return true成功 false 失败
+	 */
+	public boolean set(String key, Object value, long time) {
+		RedisTemplate<Object, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		return Optional.ofNullable(redisTemplate).map(template -> {
+			if (time > 0) {
+				template.opsForValue().set(key, value, time, TimeUnit.SECONDS);
+			}
+			else {
+				template.opsForValue().set(key, value);
+			}
+			return true;
+		}).orElse(false);
+	}
+
+	/**
+	 * 普通缓存放入并设置时间
+	 * @param key 键
+	 * @param value 值
+	 * @param time 时间
+	 * @param timeUnit 类型
+	 * @return true成功 false 失败
+	 */
+	public <T> boolean set(String key, T value, long time, TimeUnit timeUnit) {
+		RedisTemplate<String, T> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		Optional.ofNullable(redisTemplate).map(template -> {
+			if (time > 0) {
+				template.opsForValue().set(key, value, time, timeUnit);
+			}
+			else {
+				template.opsForValue().set(key, value);
+			}
+			return true;
+		});
+		return true;
+	}
+
+	/**
+	 * 执行 Redis 命令回调
+	 * @param callback Redis回调函数
+	 * @return 执行结果
+	 */
+	@SuppressWarnings("unchecked")
+	public <T> T execute(RedisCallback<T> callback) {
+		RedisTemplate<String, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		return (T) redisTemplate.execute(callback);
+	}
+
+	// ================================Map=================================
+
+	/**
+	 * HashGet
+	 * @param key 键 不能为null
+	 * @param hashKey 项 不能为null
+	 * @return 值
+	 */
+	public <HK, HV> HV hget(String key, HK hashKey) {
+		RedisTemplate<String, HV> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		return redisTemplate.<HK, HV>opsForHash().get(key, hashKey);
+	}
+
+	/**
+	 * 获取hashKey对应的所有键值
+	 * @param key 键
+	 * @return 对应的多个键值
+	 */
+	public <HK, HV> Map<HK, HV> hmget(String key) {
+		RedisTemplate<String, HV> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		return redisTemplate.<HK, HV>opsForHash().entries(key);
+	}
+
+	/**
+	 * HashSet
+	 * @param key 键
+	 * @param map 对应多个键值
+	 * @return true 成功 false 失败
+	 */
+	public boolean hmset(String key, Map<String, Object> map) {
+		RedisTemplate<Object, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		Optional.ofNullable(redisTemplate).map(template -> {
+			template.opsForHash().putAll(key, map);
+			return true;
+		});
+		return true;
+	}
+
+	/**
+	 * HashSet 并设置时间
+	 * @param key 键
+	 * @param map 对应多个键值
+	 * @param time 时间(秒)
+	 * @return true成功 false失败
+	 */
+	public boolean hmset(String key, Map<String, Object> map, long time) {
+		RedisTemplate<Object, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		Optional.ofNullable(redisTemplate).map(template -> {
+			template.opsForHash().putAll(key, map);
+			if (time > 0) {
+				template.expire(key, time, TimeUnit.SECONDS);
+			}
+			return true;
+		});
+		return true;
+	}
+
+	/**
+	 * 向一张hash表中放入数据,如果不存在将创建
+	 * @param key 键
+	 * @param item 项
+	 * @param value 值
+	 * @return true 成功 false失败
+	 */
+	public boolean hset(String key, String item, Object value) {
+		RedisTemplate<Object, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		return Optional.ofNullable(redisTemplate).map(template -> {
+			template.opsForHash().put(key, item, value);
+			return true;
+		}).orElse(false);
+	}
+
+	/**
+	 * 向一张hash表中放入数据,如果不存在将创建
+	 * @param key 键
+	 * @param item 项
+	 * @param value 值
+	 * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
+	 * @return true 成功 false失败
+	 */
+	public boolean hset(String key, String item, Object value, long time) {
+		RedisTemplate<Object, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		return Optional.ofNullable(redisTemplate).map(template -> {
+			template.opsForHash().put(key, item, value);
+			if (time > 0) {
+				template.expire(key, time, TimeUnit.SECONDS);
+			}
+			return true;
+		}).orElse(false);
+	}
+
+	/**
+	 * 删除hash表中的值
+	 * @param key 键 不能为null
+	 * @param item 项 可以使多个 不能为null
+	 */
+	public void hdel(String key, Object... item) {
+		RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		redisTemplate.opsForHash().delete(key, item);
+	}
+
+	/**
+	 * 判断hash表中是否有该项的值
+	 * @param key 键 不能为null
+	 * @param item 项 不能为null
+	 * @return true 存在 false不存在
+	 */
+	public boolean hHasKey(String key, String item) {
+		RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		return redisTemplate.opsForHash().hasKey(key, item);
+	}
+
+	/**
+	 * hash递增 如果不存在,就会创建一个 并把新增后的值返回
+	 * @param key 键
+	 * @param item 项
+	 * @param by 要增加几(大于0)
+	 * @return
+	 */
+	public double hincr(String key, String item, double by) {
+		RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		return redisTemplate.opsForHash().increment(key, item, by);
+	}
+
+	/**
+	 * hash递减
+	 * @param key 键
+	 * @param item 项
+	 * @param by 要减少记(小于0)
+	 * @return
+	 */
+	public double hdecr(String key, String item, double by) {
+		RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		return redisTemplate.opsForHash().increment(key, item, -by);
+	}
+
+	// ============================set=============================
+
+	/**
+	 * 根据key获取Set中的所有值
+	 * @param key 键
+	 * @return
+	 */
+	public <T> Set<T> sGet(String key) {
+		RedisTemplate<String, T> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		return redisTemplate.opsForSet().members(key);
+	}
+
+	/**
+	 * 根据value从一个set中查询,是否存在
+	 * @param key 键
+	 * @param value 值
+	 * @return true 存在 false不存在
+	 */
+	public boolean sHasKey(String key, Object value) {
+		RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		return redisTemplate.opsForSet().isMember(key, value);
+	}
+
+	/**
+	 * 将数据放入set缓存
+	 * @param key 键
+	 * @param values 值 可以是多个
+	 * @return 成功个数
+	 */
+	public long sSet(String key, Object... values) {
+		RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		return redisTemplate.opsForSet().add(key, values);
+	}
+
+	/**
+	 * 将set数据放入缓存
+	 * @param key 键
+	 * @param time 时间(秒)
+	 * @param values 值 可以是多个
+	 * @return 成功个数
+	 */
+	public long sSetAndTime(String key, long time, Object... values) {
+		RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		Long count = redisTemplate.opsForSet().add(key, values);
+		if (time > 0) {
+			expire(key, time);
+		}
+		return count;
+	}
+
+	/**
+	 * 获取set缓存的长度
+	 * @param key 键
+	 * @return
+	 */
+	public long sGetSetSize(String key) {
+		RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		return redisTemplate.opsForSet().size(key);
+	}
+
+	/**
+	 * 移除值为value的
+	 * @param key 键
+	 * @param values 值 可以是多个
+	 * @return 移除的个数
+	 */
+	public long setRemove(String key, Object... values) {
+		RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		Long count = redisTemplate.opsForSet().remove(key, values);
+		return count;
+	}
+
+	/**
+	 * 获集合key1和集合key2的差集元素
+	 * @param key 键
+	 * @return
+	 */
+	public <T> Set<T> sDifference(String key, String otherKey) {
+		RedisTemplate<String, T> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		return redisTemplate.opsForSet().difference(key, otherKey);
+	}
+
+	// ===============================list=================================
+
+	/**
+	 * 获取list缓存的内容
+	 * @param key 键
+	 * @param start 开始
+	 * @param end 结束 0 到 -1代表所有值
+	 * @return
+	 */
+	public <T> List<T> lGet(String key, long start, long end) {
+		RedisTemplate<String, T> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		return redisTemplate.opsForList().range(key, start, end);
+	}
+
+	/**
+	 * 获取list缓存的长度
+	 * @param key 键
+	 * @return
+	 */
+	public long lGetListSize(String key) {
+		RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		return redisTemplate.opsForList().size(key);
+	}
+
+	/**
+	 * 通过索引 获取list中的值
+	 * @param key 键
+	 * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
+	 * @return
+	 */
+	public Object lGetIndex(String key, long index) {
+		RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		return redisTemplate.opsForList().index(key, index);
+	}
+
+	/**
+	 * 将list放入缓存
+	 * @param key 键
+	 * @param value 值
+	 * @return
+	 */
+	public boolean lSet(String key, Object value) {
+		RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		redisTemplate.opsForList().rightPush(key, value);
+		return true;
+	}
+
+	/**
+	 * 将list放入缓存
+	 * @param key 键
+	 * @param value 值
+	 * @param time 时间(秒)
+	 * @return
+	 */
+	public boolean lSet(String key, Object value, long time) {
+		RedisTemplate<Object, Object> redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		redisTemplate.opsForList().rightPush(key, value);
+		if (time > 0) {
+			Optional.ofNullable(redisTemplate).ifPresent(template -> template.expire(key, time, TimeUnit.SECONDS));
+		}
+		return true;
+	}
+
+	/**
+	 * 将list放入缓存
+	 * @param key 键
+	 * @param value 值
+	 * @return
+	 */
+	public boolean lSet(String key, List<Object> value) {
+		RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		redisTemplate.opsForList().rightPushAll(key, value);
+		return true;
+	}
+
+	/**
+	 * 将list放入缓存
+	 * @param key 键
+	 * @param value 值
+	 * @param time 时间(秒)
+	 * @return
+	 */
+	public boolean lSet(String key, List<Object> value, long time) {
+		RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		redisTemplate.opsForList().rightPushAll(key, value);
+		if (time > 0) {
+			expire(key, time);
+		}
+		return true;
+	}
+
+	/**
+	 * 根据索引修改list中的某条数据
+	 * @param key 键
+	 * @param index 索引
+	 * @param value 值
+	 * @return /
+	 */
+	public boolean lUpdateIndex(String key, long index, Object value) {
+		RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		redisTemplate.opsForList().set(key, index, value);
+		return true;
+	}
+
+	/**
+	 * 移除N个值为value
+	 * @param key 键
+	 * @param count 移除多少个
+	 * @param value 值
+	 * @return 移除的个数
+	 */
+	public long lRemove(String key, long count, Object value) {
+		RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		return redisTemplate.opsForList().remove(key, count, value);
+	}
+
+	/**
+	 * 将zSet数据放入缓存
+	 * @param key
+	 * @param time
+	 * @param tuples
+	 * @return
+	 */
+	public long zSetAndTime(String key, long time, Set<ZSetOperations.TypedTuple<Object>> tuples) {
+		RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		Long count = redisTemplate.opsForZSet().add(key, tuples);
+		if (time > 0) {
+			expire(key, time);
+		}
+		return count;
+
+	}
+
+	/**
+	 * Sorted set:有序集合获取
+	 * @param key
+	 * @param min
+	 * @param max
+	 * @return
+	 */
+	public Set<Object> zRangeByScore(String key, double min, double max) {
+		RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
+		return zset.rangeByScore(key, min, max);
+
+	}
+
+	/**
+	 * Sorted set:有序集合获取 正序
+	 * @param key
+	 * @param start
+	 * @param end
+	 * @return
+	 */
+	public Set<Object> zRange(String key, long start, long end) {
+		RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
+		return zset.range(key, start, end);
+
+	}
+
+	/**
+	 * Sorted set:有序集合获取 倒叙
+	 * @param key
+	 * @param start
+	 * @param end
+	 * @return
+	 */
+	public Set<Object> zReverseRange(String key, long start, long end) {
+		RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
+		return zset.reverseRange(key, start, end);
+
+	}
+
+	/**
+	 * 获取zSet缓存的长度
+	 * @param key 键
+	 * @return
+	 */
+	public long zGetSetSize(String key) {
+		RedisTemplate redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
+		return redisTemplate.opsForZSet().size(key);
+	}
+
+}

+ 289 - 0
pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/util/RetOps.java

@@ -0,0 +1,289 @@
+/*
+ *
+ *      Copyright (c) 2018-2025, lengleng All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the pig4cloud.com developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: lengleng ([email protected])
+ *
+ */
+
+package com.pig4cloud.pig.common.core.util;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.pig4cloud.pig.common.core.constant.CommonConstants;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+/**
+ * 简化{@code R<T>} 的访问操作,例子 <pre>
+ * R<Integer> result = R.ok(0);
+ * // 使用场景1: 链式操作: 断言然后消费
+ * RetOps.of(result)
+ * 		.assertCode(-1,r -> new RuntimeException("error "+r.getCode()))
+ * 		.assertDataNotEmpty(r -> new IllegalStateException("oops!"))
+ * 		.useData(System.out::println);
+ *
+ * // 使用场景2: 读取原始值(data),这里返回的是Optional
+ * RetOps.of(result).getData().orElse(null);
+ *
+ * // 使用场景3: 类型转换
+ * R<String> s = RetOps.of(result)
+ *        .assertDataNotNull(r -> new IllegalStateException("nani??"))
+ *        .map(i -> Integer.toHexString(i))
+ *        .peek();
+ * </pre>
+ *
+ * @author CJ ([email protected])
+ * @date 2022/5/12
+ * @since 4.4
+ */
+public class RetOps<T> {
+
+	/** 状态码为成功 */
+	public static final Predicate<R<?>> CODE_SUCCESS = r -> CommonConstants.SUCCESS == r.getCode();
+
+	/** 数据有值 */
+	public static final Predicate<R<?>> HAS_DATA = r -> ObjectUtil.isNotEmpty(r.getData());
+
+	/** 数据有值,并且包含元素 */
+	public static final Predicate<R<?>> HAS_ELEMENT = r -> ObjectUtil.isNotEmpty(r.getData());
+
+	/** 状态码为成功并且有值 */
+	public static final Predicate<R<?>> DATA_AVAILABLE = CODE_SUCCESS.and(HAS_DATA);
+
+	private final R<T> original;
+
+	// ~ 初始化
+	// ===================================================================================================
+
+	RetOps(R<T> original) {
+		this.original = original;
+	}
+
+	public static <T> RetOps<T> of(R<T> original) {
+		return new RetOps<>(Objects.requireNonNull(original));
+	}
+
+	// ~ 杂项方法
+	// ===================================================================================================
+
+	/**
+	 * 观察原始值
+	 * @return R
+	 */
+	public R<T> peek() {
+		return original;
+	}
+
+	/**
+	 * 读取{@code code}的值
+	 * @return 返回code的值
+	 */
+	public int getCode() {
+		return original.getCode();
+	}
+
+	/**
+	 * 读取{@code data}的值
+	 * @return 返回 Optional 包装的data
+	 */
+	public Optional<T> getData() {
+		return Optional.ofNullable(original.getData());
+	}
+
+	/**
+	 * 有条件地读取{@code data}的值
+	 * @param predicate 断言函数
+	 * @return 返回 Optional 包装的data,如果断言失败返回empty
+	 */
+	public Optional<T> getDataIf(Predicate<? super R<?>> predicate) {
+		return predicate.test(original) ? getData() : Optional.empty();
+	}
+
+	/**
+	 * 读取{@code msg}的值
+	 * @return 返回Optional包装的 msg
+	 */
+	public Optional<String> getMsg() {
+		return Optional.of(original.getMsg());
+	}
+
+	/**
+	 * 对{@code code}的值进行相等性测试
+	 * @param value 基准值
+	 * @return 返回ture表示相等
+	 */
+	public boolean codeEquals(int value) {
+		return original.getCode() == value;
+	}
+
+	/**
+	 * 对{@code code}的值进行相等性测试
+	 * @param value 基准值
+	 * @return 返回ture表示不相等
+	 */
+	public boolean codeNotEquals(int value) {
+		return !codeEquals(value);
+	}
+
+	/**
+	 * 是否成功
+	 * @return 返回ture表示成功
+	 * @see CommonConstants#SUCCESS
+	 */
+	public boolean isSuccess() {
+		return codeEquals(CommonConstants.SUCCESS);
+	}
+
+	/**
+	 * 是否失败
+	 * @return 返回ture表示失败
+	 */
+	public boolean notSuccess() {
+		return !isSuccess();
+	}
+
+	// ~ 链式操作
+	// ===================================================================================================
+
+	/**
+	 * 断言{@code code}的值
+	 * @param expect 预期的值
+	 * @param func 用户函数,负责创建异常对象
+	 * @param <Ex> 异常类型
+	 * @return 返回实例,以便于继续进行链式操作
+	 * @throws Ex 断言失败时抛出
+	 */
+	public <Ex extends Exception> RetOps<T> assertCode(int expect, Function<? super R<T>, ? extends Ex> func)
+			throws Ex {
+		if (codeNotEquals(expect)) {
+			throw func.apply(original);
+		}
+		return this;
+	}
+
+	/**
+	 * 断言成功
+	 * @param func 用户函数,负责创建异常对象
+	 * @param <Ex> 异常类型
+	 * @return 返回实例,以便于继续进行链式操作
+	 * @throws Ex 断言失败时抛出
+	 */
+	public <Ex extends Exception> RetOps<T> assertSuccess(Function<? super R<T>, ? extends Ex> func) throws Ex {
+		return assertCode(CommonConstants.SUCCESS, func);
+	}
+
+	/**
+	 * 断言业务数据有值
+	 * @param func 用户函数,负责创建异常对象
+	 * @param <Ex> 异常类型
+	 * @return 返回实例,以便于继续进行链式操作
+	 * @throws Ex 断言失败时抛出
+	 */
+	public <Ex extends Exception> RetOps<T> assertDataNotNull(Function<? super R<T>, ? extends Ex> func) throws Ex {
+		if (Objects.isNull(original.getData())) {
+			throw func.apply(original);
+		}
+		return this;
+	}
+
+	/**
+	 * 断言业务数据有值,并且包含元素
+	 * @param func 用户函数,负责创建异常对象
+	 * @param <Ex> 异常类型
+	 * @return 返回实例,以便于继续进行链式操作
+	 * @throws Ex 断言失败时抛出
+	 */
+	public <Ex extends Exception> RetOps<T> assertDataNotEmpty(Function<? super R<T>, ? extends Ex> func) throws Ex {
+		if (ObjectUtil.isNotEmpty(original.getData())) {
+			throw func.apply(original);
+		}
+		return this;
+	}
+
+	/**
+	 * 对业务数据(data)转换
+	 * @param mapper 业务数据转换函数
+	 * @param <U> 数据类型
+	 * @return 返回新实例,以便于继续进行链式操作
+	 */
+	public <U> RetOps<U> map(Function<? super T, ? extends U> mapper) {
+		R<U> result = R.restResult(mapper.apply(original.getData()), original.getCode(), original.getMsg());
+		return of(result);
+	}
+
+	/**
+	 * 对业务数据(data)转换
+	 * @param predicate 断言函数
+	 * @param mapper 业务数据转换函数
+	 * @param <U> 数据类型
+	 * @return 返回新实例,以便于继续进行链式操作
+	 * @see RetOps#CODE_SUCCESS
+	 * @see RetOps#HAS_DATA
+	 * @see RetOps#HAS_ELEMENT
+	 * @see RetOps#DATA_AVAILABLE
+	 */
+	public <U> RetOps<U> mapIf(Predicate<? super R<T>> predicate, Function<? super T, ? extends U> mapper) {
+		R<U> result = R.restResult(mapper.apply(original.getData()), original.getCode(), original.getMsg());
+		return of(result);
+	}
+
+	// ~ 数据消费
+	// ===================================================================================================
+
+	/**
+	 * 消费数据,注意此方法保证数据可用
+	 * @param consumer 消费函数
+	 */
+	public void useData(Consumer<? super T> consumer) {
+		consumer.accept(original.getData());
+	}
+
+	/**
+	 * 条件消费(错误代码匹配某个值)
+	 * @param consumer 消费函数
+	 * @param codes 错误代码集合,匹配任意一个则调用消费函数
+	 */
+	public void useDataOnCode(Consumer<? super T> consumer, int... codes) {
+		useDataIf(o -> Arrays.stream(codes).filter(c -> original.getCode() == c).findFirst().isPresent(), consumer);
+	}
+
+	/**
+	 * 条件消费(错误代码表示成功)
+	 * @param consumer 消费函数
+	 */
+	public void useDataIfSuccess(Consumer<? super T> consumer) {
+		useDataIf(CODE_SUCCESS, consumer);
+	}
+
+	/**
+	 * 条件消费
+	 * @param predicate 断言函数
+	 * @param consumer 消费函数,断言函数返回{@code true}时被调用
+	 * @see RetOps#CODE_SUCCESS
+	 * @see RetOps#HAS_DATA
+	 * @see RetOps#HAS_ELEMENT
+	 * @see RetOps#DATA_AVAILABLE
+	 */
+	public void useDataIf(Predicate<? super R<T>> predicate, Consumer<? super T> consumer) {
+		if (predicate.test(original)) {
+			consumer.accept(original.getData());
+		}
+	}
+
+}

+ 123 - 0
pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/util/SpringContextHolder.java

@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.core.util;
+
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.ApplicationEvent;
+import org.springframework.context.EnvironmentAware;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.core.env.Environment;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author lengleng
+ * @date 2019/2/1 Spring 工具类
+ */
+@Slf4j
+@Service
+@Lazy(false)
+public class SpringContextHolder implements ApplicationContextAware, EnvironmentAware, DisposableBean {
+
+	private static ApplicationContext applicationContext = null;
+
+	private static Environment environment = null;
+
+	/**
+	 * 取得存储在静态变量中的ApplicationContext.
+	 */
+	public static ApplicationContext getApplicationContext() {
+		return applicationContext;
+	}
+
+	/**
+	 * 获取环境
+	 * @return {@link Environment }
+	 */
+	public static Environment getEnvironment() {
+		return environment;
+	}
+
+	/**
+	 * 实现ApplicationContextAware接口, 注入Context到静态变量中.
+	 */
+	@Override
+	public void setApplicationContext(ApplicationContext applicationContext) {
+		SpringContextHolder.applicationContext = applicationContext;
+	}
+
+	/**
+	 * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
+	 */
+	public static <T> T getBean(String name) {
+		return (T) applicationContext.getBean(name);
+	}
+
+	/**
+	 * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
+	 */
+	public static <T> T getBean(Class<T> requiredType) {
+		return applicationContext.getBean(requiredType);
+	}
+
+	/**
+	 * 清除SpringContextHolder中的ApplicationContext为Null.
+	 */
+	public static void clearHolder() {
+		if (log.isDebugEnabled()) {
+			log.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext);
+		}
+		applicationContext = null;
+	}
+
+	/**
+	 * 发布事件
+	 * @param event
+	 */
+	public static void publishEvent(ApplicationEvent event) {
+		if (applicationContext == null) {
+			return;
+		}
+		applicationContext.publishEvent(event);
+	}
+
+	/**
+	 * 是否是微服务
+	 * @return boolean
+	 */
+	public static boolean isMicro() {
+		return environment.getProperty("spring.cloud.nacos.discovery.enabled", Boolean.class, true);
+	}
+
+	/**
+	 * 实现DisposableBean接口, 在Context关闭时清理静态变量.
+	 */
+	@Override
+	@SneakyThrows
+	public void destroy() {
+		SpringContextHolder.clearHolder();
+	}
+
+	@Override
+	public void setEnvironment(Environment environment) {
+		SpringContextHolder.environment = environment;
+	}
+
+}

+ 168 - 0
pig-common/pig-common-core/src/main/java/com/pig4cloud/pig/common/core/util/WebUtils.java

@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.core.util;
+
+import cn.hutool.core.codec.Base64;
+import com.pig4cloud.pig.common.core.exception.CheckedException;
+import jakarta.servlet.http.Cookie;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.NotNull;
+import lombok.SneakyThrows;
+import lombok.experimental.UtilityClass;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import org.springframework.web.method.HandlerMethod;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Optional;
+
+/**
+ * Miscellaneous utilities for web applications.
+ *
+ * @author L.cm
+ */
+@Slf4j
+@UtilityClass
+public class WebUtils extends org.springframework.web.util.WebUtils {
+
+	private final String BASIC_ = "Basic ";
+
+	private final String UNKNOWN = "unknown";
+
+	/**
+	 * 判断是否ajax请求 spring ajax 返回含有 ResponseBody 或者 RestController注解
+	 * @param handlerMethod HandlerMethod
+	 * @return 是否ajax请求
+	 */
+	public boolean isBody(HandlerMethod handlerMethod) {
+		ResponseBody responseBody = ClassUtils.getAnnotation(handlerMethod, ResponseBody.class);
+		return responseBody != null;
+	}
+
+	/**
+	 * 读取cookie
+	 * @param name cookie name
+	 * @return cookie value
+	 */
+	public String getCookieVal(String name) {
+		if (WebUtils.getRequest().isPresent()) {
+			return getCookieVal(WebUtils.getRequest().get(), name);
+		}
+		return null;
+	}
+
+	/**
+	 * 读取cookie
+	 * @param request HttpServletRequest
+	 * @param name cookie name
+	 * @return cookie value
+	 */
+	public String getCookieVal(HttpServletRequest request, String name) {
+		Cookie cookie = getCookie(request, name);
+		return cookie != null ? cookie.getValue() : null;
+	}
+
+	/**
+	 * 清除 某个指定的cookie
+	 * @param response HttpServletResponse
+	 * @param key cookie key
+	 */
+	public void removeCookie(HttpServletResponse response, String key) {
+		setCookie(response, key, null, 0);
+	}
+
+	/**
+	 * 设置cookie
+	 * @param response HttpServletResponse
+	 * @param name cookie name
+	 * @param value cookie value
+	 * @param maxAgeInSeconds maxage
+	 */
+	public void setCookie(HttpServletResponse response, String name, String value, int maxAgeInSeconds) {
+		Cookie cookie = new Cookie(name, value);
+		cookie.setPath("/");
+		cookie.setMaxAge(maxAgeInSeconds);
+		cookie.setHttpOnly(true);
+		response.addCookie(cookie);
+	}
+
+	/**
+	 * 获取 HttpServletRequest
+	 * @return {HttpServletRequest}
+	 */
+	public Optional<HttpServletRequest> getRequest() {
+		return Optional
+			.ofNullable(((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest());
+	}
+
+	/**
+	 * 获取 HttpServletResponse
+	 * @return {HttpServletResponse}
+	 */
+	public HttpServletResponse getResponse() {
+		return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
+	}
+
+	/**
+	 * 从request 获取CLIENT_ID
+	 * @return
+	 */
+	@SneakyThrows
+	public String getClientId(ServerHttpRequest request) {
+		String header = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
+		return splitClient(header)[0];
+	}
+
+	@SneakyThrows
+	public String getClientId() {
+		if (WebUtils.getRequest().isPresent()) {
+			String header = WebUtils.getRequest().get().getHeader(HttpHeaders.AUTHORIZATION);
+			return splitClient(header)[0];
+		}
+		return null;
+	}
+
+	@NotNull
+	private static String[] splitClient(String header) {
+		if (header == null || !header.startsWith(BASIC_)) {
+			throw new CheckedException("请求头中client信息为空");
+		}
+		byte[] base64Token = header.substring(6).getBytes(StandardCharsets.UTF_8);
+		byte[] decoded;
+		try {
+			decoded = Base64.decode(base64Token);
+		}
+		catch (IllegalArgumentException e) {
+			throw new CheckedException("Failed to decode basic authentication token");
+		}
+
+		String token = new String(decoded, StandardCharsets.UTF_8);
+
+		int delim = token.indexOf(":");
+
+		if (delim == -1) {
+			throw new CheckedException("Invalid basic authentication token");
+		}
+		return new String[] { token.substring(0, delim), token.substring(delim + 1) };
+	}
+
+}

+ 5 - 0
pig-common/pig-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1,5 @@
+com.pig4cloud.pig.common.core.config.JacksonConfiguration
+com.pig4cloud.pig.common.core.config.RedisTemplateConfiguration
+com.pig4cloud.pig.common.core.config.RestTemplateConfiguration
+com.pig4cloud.pig.common.core.util.SpringContextHolder
+com.pig4cloud.pig.common.core.config.WebMvcConfiguration

+ 17 - 0
pig-common/pig-common-core/src/main/resources/banner.txt

@@ -0,0 +1,17 @@
+${AnsiColor.BRIGHT_YELLOW}
+
+                :::::::::       :::::::::::       ::::::::
+               :+:    :+:          :+:          :+:    :+:
+              +:+    +:+          +:+          +:+
+             +#++:++#+           +#+          :#:
+            +#+                 +#+          +#+   +#+#
+           #+#                 #+#          #+#    #+#
+          ###             ###########       ########
+
+                       www.pig4cloud.com
+
+                  Pig Microservice Architecture
+${AnsiColor.DEFAULT}
+
+
+

+ 26 - 0
pig-common/pig-common-core/src/main/resources/i18n/messages_zh_CN.properties

@@ -0,0 +1,26 @@
+sys.user.update.passwordError=\u539F\u5BC6\u7801\u9519\u8BEF\uFF0C\u4FEE\u6539\u5931\u8D25
+sys.user.query.error=\u83B7\u53D6\u5F53\u524D\u7528\u6237\u4FE1\u606F\u5931\u8D25
+sys.user.existing=\u7528\u6237\u5DF2\u5B58\u5728
+sys.user.username.existing={0} \u7528\u6237\u540D\u5DF2\u5B58\u5728
+sys.user.userInfo.empty={0} \u7528\u6237\u4FE1\u606F\u4E3A\u7A7A
+
+sys.dept.deptName.inexistence={0} \u90E8\u95E8\u540D\u79F0\u4E0D\u5B58\u5728
+
+sys.post.postName.inexistence={0} \u5C97\u4F4D\u540D\u79F0\u4E0D\u5B58\u5728
+sys.post.nameOrCode.existing={0} {1} \u5C97\u4F4D\u540D\u6216\u5C97\u4F4D\u7F16\u7801\u5DF2\u7ECF\u5B58\u5728
+
+sys.role.roleName.inexistence={0} \u89D2\u8272\u540D\u79F0\u4E0D\u5B58\u5728
+sys.role.nameOrCode.existing={0} {1} \u89D2\u8272\u540D\u6216\u89D2\u8272\u7F16\u7801\u5DF2\u7ECF\u5B58\u5728
+
+sys.param.delete.system=\u7CFB\u7EDF\u5185\u7F6E\u53C2\u6570\u4E0D\u80FD\u5220\u9664
+sys.param.config.error={0} \u7CFB\u7EDF\u53C2\u6570\u914D\u7F6E\u9519\u8BEF
+
+sys.menu.delete.existing=\u83DC\u5355\u542B\u6709\u4E0B\u7EA7\u4E0D\u80FD\u5220\u9664
+
+sys.app.sms.often=\u9A8C\u8BC1\u7801\u53D1\u9001\u8FC7\u9891\u7E41
+sys.app.sms.error=\u9A8C\u8BC1\u7801\u9519\u8BEF
+sys.app.phone.unregistered={0} \u624B\u673A\u53F7\u672A\u6CE8\u518C
+sys.app.sms.blend.unregistered=\u77ED\u4FE1\u6E20\u9053\u672A\u914D\u7F6E\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458
+
+sys.dict.delete.system=\u7CFB\u7EDF\u5185\u7F6E\u5B57\u5178\u9879\u76EE\u4E0D\u80FD\u5220\u9664
+sys.dict.update.system=\u7CFB\u7EDF\u5185\u7F6E\u5B57\u5178\u9879\u76EE\u4E0D\u80FD\u4FEE\u6539

+ 73 - 0
pig-common/pig-common-core/src/main/resources/logback-spring.xml

@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<configuration debug="false" scan="false">
+	<springProperty scop="context" name="spring.application.name" source="spring.application.name" defaultValue=""/>
+	<property name="log.path" value="logs/${spring.application.name}"/>
+	<!-- 彩色日志格式 -->
+	<property name="CONSOLE_LOG_PATTERN"
+			  value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
+	<property name="FILE_LOG_PATTERN" value=":%d{yyyy-MM-dd HH:mm:ss.SSS} %5p %t %c{1}: %m%n"/>
+	<!-- 彩色日志依赖的渲染类 -->
+	<conversionRule conversionWord="clr" class="org.springframework.boot.logging.logback.ColorConverter"/>
+	<conversionRule conversionWord="wex"
+					class="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
+	<conversionRule conversionWord="wEx"
+					class="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
+	<!-- Console log output -->
+	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>${CONSOLE_LOG_PATTERN}</pattern>
+		</encoder>
+	</appender>
+
+	<!-- Log file debug output -->
+	<appender name="debug" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<file>${log.path}/debug.log</file>
+		<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+			<fileNamePattern>${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
+			<maxFileSize>50MB</maxFileSize>
+			<maxHistory>30</maxHistory>
+		</rollingPolicy>
+		<encoder>
+			<pattern>${FILE_LOG_PATTERN}</pattern>
+		</encoder>
+	</appender>
+
+	<!-- Log file error output -->
+	<appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<file>${log.path}/error.log</file>
+		<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+			<fileNamePattern>${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
+			<maxFileSize>50MB</maxFileSize>
+			<maxHistory>30</maxHistory>
+		</rollingPolicy>
+		<encoder>
+			<pattern>${FILE_LOG_PATTERN}</pattern>
+		</encoder>
+		<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+			<level>ERROR</level>
+		</filter>
+	</appender>
+
+	<!-- Level: FATAL 0  ERROR 3  WARN 4  INFO 6  DEBUG 7 -->
+	<root level="INFO">
+		<appender-ref ref="console"/>
+		<appender-ref ref="debug"/>
+		<appender-ref ref="error"/>
+	</root>
+</configuration>

+ 42 - 0
pig-common/pig-common-datasource/pom.xml

@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>pig-common</artifactId>
+        <groupId>com.pig4cloud</groupId>
+        <version>${revision}</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>pig-common-datasource</artifactId>
+    <packaging>jar</packaging>
+    <description>pig 动态切换数据源</description>
+
+    <dependencies>
+        <!--mybatis-->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>jakarta.servlet</groupId>
+            <artifactId>jakarta.servlet-api</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 107 - 0
pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/DynamicDataSourceAutoConfiguration.java

@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.datasource;
+
+import com.baomidou.dynamic.datasource.creator.DataSourceCreator;
+import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
+import com.baomidou.dynamic.datasource.creator.hikaricp.HikariDataSourceCreator;
+import com.baomidou.dynamic.datasource.processor.DsJakartaHeaderProcessor;
+import com.baomidou.dynamic.datasource.processor.DsJakartaSessionProcessor;
+import com.baomidou.dynamic.datasource.processor.DsProcessor;
+import com.baomidou.dynamic.datasource.processor.DsSpelExpressionProcessor;
+import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;
+import com.pig4cloud.pig.common.datasource.config.ClearTtlDataSourceFilter;
+import com.pig4cloud.pig.common.datasource.config.DataSourceProperties;
+import com.pig4cloud.pig.common.datasource.config.JdbcDynamicDataSourceProvider;
+import com.pig4cloud.pig.common.datasource.config.LastParamDsProcessor;
+import org.jasypt.encryption.StringEncryptor;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.expression.BeanFactoryResolver;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 动态数据源切换自动配置类
+ *
+ * @author lengleng
+ * @date 2025/05/31
+ */
+@Configuration(proxyBeanMethods = false)
+@AutoConfigureAfter(DataSourceAutoConfiguration.class)
+@EnableConfigurationProperties(DataSourceProperties.class)
+public class DynamicDataSourceAutoConfiguration {
+
+	/**
+	 * 动态数据源提供者
+	 * @param defaultDataSourceCreator 默认数据源创建器
+	 * @param stringEncryptor 字符串加密器
+	 * @param properties 数据源配置属性
+	 * @return 动态数据源提供者
+	 */
+	@Bean
+	public DynamicDataSourceProvider dynamicDataSourceProvider(DefaultDataSourceCreator defaultDataSourceCreator,
+			StringEncryptor stringEncryptor, DataSourceProperties properties) {
+		return new JdbcDynamicDataSourceProvider(defaultDataSourceCreator, stringEncryptor, properties);
+	}
+
+	/**
+	 * 获取数据源处理器
+	 * @return 数据源处理器
+	 */
+	@Bean
+	public DsProcessor dsProcessor(BeanFactory beanFactory) {
+		DsProcessor lastParamDsProcessor = new LastParamDsProcessor();
+		DsProcessor headerProcessor = new DsJakartaHeaderProcessor();
+		DsProcessor sessionProcessor = new DsJakartaSessionProcessor();
+		DsSpelExpressionProcessor spelExpressionProcessor = new DsSpelExpressionProcessor();
+		spelExpressionProcessor.setBeanResolver(new BeanFactoryResolver(beanFactory));
+		lastParamDsProcessor.setNextProcessor(headerProcessor);
+		headerProcessor.setNextProcessor(sessionProcessor);
+		sessionProcessor.setNextProcessor(spelExpressionProcessor);
+		return lastParamDsProcessor;
+	}
+
+	/**
+	 * 默认数据源创建器
+	 * @param hikariDataSourceCreator Hikari数据源创建器
+	 * @return 默认数据源创建器
+	 */
+	@Bean
+	public DefaultDataSourceCreator defaultDataSourceCreator(HikariDataSourceCreator hikariDataSourceCreator) {
+		DefaultDataSourceCreator defaultDataSourceCreator = new DefaultDataSourceCreator();
+		List<DataSourceCreator> creators = new ArrayList<>();
+		creators.add(hikariDataSourceCreator);
+		defaultDataSourceCreator.setCreators(creators);
+		return defaultDataSourceCreator;
+	}
+
+	/**
+	 * 清除Ttl数据源过滤器
+	 * @return 清除Ttl数据源过滤器
+	 */
+	@Bean
+	public ClearTtlDataSourceFilter clearTtlDsFilter() {
+		return new ClearTtlDataSourceFilter();
+	}
+
+}

+ 37 - 0
pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/annotation/EnableDynamicDataSource.java

@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.datasource.annotation;
+
+import com.pig4cloud.pig.common.datasource.DynamicDataSourceAutoConfiguration;
+import org.springframework.context.annotation.Import;
+
+import java.lang.annotation.*;
+
+/**
+ * 开启动态数据源注解
+ *
+ * @author lengleng
+ * @date 2025/05/31
+ */
+@Target({ ElementType.TYPE })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+@Import(DynamicDataSourceAutoConfiguration.class)
+public @interface EnableDynamicDataSource {
+
+}

+ 42 - 0
pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/config/ClearTtlDataSourceFilter.java

@@ -0,0 +1,42 @@
+package com.pig4cloud.pig.common.datasource.config;
+
+import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import org.springframework.core.Ordered;
+import org.springframework.web.filter.GenericFilterBean;
+
+import java.io.IOException;
+
+/**
+ * 清空上文的DS设置避免污染当前线程的过滤器
+ *
+ * @author lengleng
+ * @date 2025/05/31
+ */
+public class ClearTtlDataSourceFilter extends GenericFilterBean implements Ordered {
+
+	/**
+	 * 过滤器方法,用于清除动态数据源上下文
+	 * @param servletRequest 请求对象
+	 * @param servletResponse 响应对象
+	 * @param filterChain 过滤器链
+	 * @throws IOException IO异常
+	 * @throws ServletException Servlet异常
+	 */
+	@Override
+	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
+			throws IOException, ServletException {
+		DynamicDataSourceContextHolder.clear();
+		filterChain.doFilter(servletRequest, servletResponse);
+		DynamicDataSourceContextHolder.clear();
+	}
+
+	@Override
+	public int getOrder() {
+		return Integer.MIN_VALUE;
+	}
+
+}

+ 57 - 0
pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/config/DataSourceProperties.java

@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.datasource.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * 数据源配置属性类
+ *
+ * @author lengleng
+ * @date 2025/05/31
+ */
+@Data
+@ConfigurationProperties("spring.datasource")
+public class DataSourceProperties {
+
+	/**
+	 * 用户名
+	 */
+	private String username;
+
+	/**
+	 * 密码
+	 */
+	private String password;
+
+	/**
+	 * jdbcurl
+	 */
+	private String url;
+
+	/**
+	 * 驱动类型
+	 */
+	private String driverClassName;
+
+	/**
+	 * 查询数据源的SQL
+	 */
+	private String queryDsSql = "select * from gen_datasource_conf where del_flag = 0";
+
+}

+ 85 - 0
pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/config/JdbcDynamicDataSourceProvider.java

@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.datasource.config;
+
+import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
+import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
+import com.baomidou.dynamic.datasource.provider.AbstractJdbcDataSourceProvider;
+import com.pig4cloud.pig.common.datasource.support.DataSourceConstants;
+import org.jasypt.encryption.StringEncryptor;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * JDBC动态数据源提供者:从数据源中获取配置信息
+ *
+ * @author lengleng
+ * @date 2025/05/31
+ */
+public class JdbcDynamicDataSourceProvider extends AbstractJdbcDataSourceProvider {
+
+	private final DataSourceProperties properties;
+
+	private final StringEncryptor stringEncryptor;
+
+	public JdbcDynamicDataSourceProvider(DefaultDataSourceCreator defaultDataSourceCreator,
+			StringEncryptor stringEncryptor, DataSourceProperties properties) {
+		super(defaultDataSourceCreator, properties.getDriverClassName(), properties.getUrl(), properties.getUsername(),
+				properties.getPassword());
+		this.stringEncryptor = stringEncryptor;
+		this.properties = properties;
+	}
+
+	/**
+	 * 执行语句获得数据源参数
+	 * @param statement 语句
+	 * @return 数据源参数
+	 * @throws SQLException sql异常
+	 */
+	@Override
+	protected Map<String, DataSourceProperty> executeStmt(Statement statement) throws SQLException {
+		ResultSet rs = statement.executeQuery(properties.getQueryDsSql());
+
+		Map<String, DataSourceProperty> map = new HashMap<>(8);
+		while (rs.next()) {
+			String name = rs.getString(DataSourceConstants.DS_NAME);
+			String username = rs.getString(DataSourceConstants.DS_USER_NAME);
+			String password = rs.getString(DataSourceConstants.DS_USER_PWD);
+			String url = rs.getString(DataSourceConstants.DS_JDBC_URL);
+			DataSourceProperty property = new DataSourceProperty();
+			property.setUsername(username);
+			property.setLazy(true);
+			property.setPassword(stringEncryptor.decrypt(password));
+			property.setUrl(url);
+			map.put(name, property);
+		}
+
+		// 添加默认主数据源
+		DataSourceProperty property = new DataSourceProperty();
+		property.setUsername(properties.getUsername());
+		property.setPassword(properties.getPassword());
+		property.setUrl(properties.getUrl());
+		property.setLazy(true);
+		map.put(DataSourceConstants.DS_MASTER, property);
+		return map;
+	}
+
+}

+ 63 - 0
pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/config/LastParamDsProcessor.java

@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.datasource.config;
+
+import com.baomidou.dynamic.datasource.processor.DsProcessor;
+import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
+import org.aopalliance.intercept.MethodInvocation;
+
+/**
+ * 基于方法最后一个参数的数据源处理器
+ * <p>
+ * 用于解析@DS("#last")注解,将数据源切换为方法最后一个参数
+ * </p>
+ *
+ * @author lengleng
+ * @date 2025/05/31
+ */
+public class LastParamDsProcessor extends DsProcessor {
+
+	private static final String LAST_PREFIX = "#last";
+
+	/**
+	 * 抽象匹配条件 匹配才会走当前执行器否则走下一级执行器
+	 * @param key DS注解里的内容
+	 * @return 是否匹配
+	 */
+	@Override
+	public boolean matches(String key) {
+		if (key.startsWith(LAST_PREFIX)) {
+			// https://github.com/baomidou/dynamic-datasource-spring-boot-starter/issues/213
+			DynamicDataSourceContextHolder.clear();
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * 抽象最终决定数据源
+	 * @param invocation 方法执行信息
+	 * @param key DS注解里的内容
+	 * @return 数据源名称
+	 */
+	@Override
+	public String doDetermineDatasource(MethodInvocation invocation, String key) {
+		Object[] arguments = invocation.getArguments();
+		return String.valueOf(arguments[arguments.length - 1]);
+	}
+
+}

+ 30 - 0
pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/enums/DsConfTypeEnum.java

@@ -0,0 +1,30 @@
+package com.pig4cloud.pig.common.datasource.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author lengleng
+ * @date 2020/12/11
+ * <p>
+ * 数据源配置类型
+ */
+@Getter
+@AllArgsConstructor
+public enum DsConfTypeEnum {
+
+	/**
+	 * 主机链接
+	 */
+	HOST(0, "主机链接"),
+
+	/**
+	 * JDBC链接
+	 */
+	JDBC(1, "JDBC链接");
+
+	private final Integer type;
+
+	private final String description;
+
+}

+ 72 - 0
pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/enums/DsJdbcUrlEnum.java

@@ -0,0 +1,72 @@
+package com.pig4cloud.pig.common.datasource.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * @author lengleng
+ * @date 2020/12/11
+ * <p>
+ * jdbc-url
+ */
+@Getter
+@AllArgsConstructor
+public enum DsJdbcUrlEnum {
+
+	/**
+	 * mysql 数据库
+	 */
+	MYSQL("mysql",
+			"jdbc:mysql://%s:%s/%s?characterEncoding=utf8"
+					+ "&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true"
+					+ "&useLegacyDatetimeCode=false&allowMultiQueries=true&allowPublicKeyRetrieval=true",
+			"select 1", "mysql8 链接"),
+
+	/**
+	 * pg 数据库
+	 */
+	PG("pg", "jdbc:postgresql://%s:%s/%s", "select 1", "postgresql 链接"),
+
+	/**
+	 * SQL SERVER
+	 */
+	MSSQL("mssql", "jdbc:sqlserver://%s:%s;database=%s;characterEncoding=UTF-8", "select 1", "sqlserver 链接"),
+
+	/**
+	 * oracle
+	 */
+	ORACLE("oracle", "jdbc:oracle:thin:@%s:%s:%s", "select 1 from dual", "oracle 链接"),
+
+	/**
+	 * db2
+	 */
+	DB2("db2", "jdbc:db2://%s:%s/%s", "select 1 from sysibm.sysdummy1", "DB2 TYPE4 连接"),
+
+	/**
+	 * 达梦
+	 */
+	DM("dm", "jdbc:dm://%s:%s/%s", "select 1 from dual", "达梦连接"),
+
+	/**
+	 * pg 数据库
+	 */
+	HIGHGO("highgo", "jdbc:highgo://%s:%s/%s", "select 1", "highgo 链接");
+
+	private final String dbName;
+
+	private final String url;
+
+	private final String validationQuery;
+
+	private final String description;
+
+	public static DsJdbcUrlEnum get(String dsType) {
+		return Arrays.stream(DsJdbcUrlEnum.values())
+			.filter(dsJdbcUrlEnum -> dsType.equals(dsJdbcUrlEnum.getDbName()))
+			.findFirst()
+			.get();
+	}
+
+}

+ 57 - 0
pig-common/pig-common-datasource/src/main/java/com/pig4cloud/pig/common/datasource/support/DataSourceConstants.java

@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.datasource.support;
+
+/**
+ * 数据源相关常量定义
+ *
+ * @author lengleng
+ * @date 2019-04-01
+ */
+public interface DataSourceConstants {
+
+	/**
+	 * 数据源名称
+	 */
+	String DS_NAME = "name";
+
+	/**
+	 * 默认数据源(master)
+	 */
+	String DS_MASTER = "master";
+
+	/**
+	 * jdbcurl
+	 */
+	String DS_JDBC_URL = "url";
+
+	/**
+	 * 用户名
+	 */
+	String DS_USER_NAME = "username";
+
+	/**
+	 * 密码
+	 */
+	String DS_USER_PWD = "password";
+
+	/**
+	 * 驱动包名称
+	 */
+	String DS_DRIVER_CLASS_NAME = "driver_class_name";
+
+}

+ 46 - 0
pig-common/pig-common-excel/pom.xml

@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ /*
+  ~  *  Copyright (c) 2019-2020, 冷冷 ([email protected]).
+  ~  *  <p>
+  ~  *  Licensed under the GNU Lesser General Public License 3.0 (the "License");
+  ~  *  you may not use this file except in compliance with the License.
+  ~  *  You may obtain a copy of the License at
+  ~  *  <p>
+  ~  * https://www.gnu.org/licenses/lgpl.html
+  ~  *  <p>
+  ~  * 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.
+  ~  */
+  -->
+
+<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>
+    <parent>
+        <groupId>com.pig4cloud</groupId>
+        <artifactId>pig-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <artifactId>pig-common-excel</artifactId>
+    <packaging>jar</packaging>
+
+    <description>excel 导入导出处理模块</description>
+
+    <dependencies>
+        <!--核心依赖,提供字典查询能力-->
+        <dependency>
+            <groupId>com.pig4cloud</groupId>
+            <artifactId>pig-common-core</artifactId>
+        </dependency>
+        <!-- excel 导入导出工具类:https://github.com/pig-mesh/excel-spring-boot-starter-->
+        <dependency>
+            <groupId>com.pig4cloud.excel</groupId>
+            <artifactId>excel-spring-boot-starter</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 69 - 0
pig-common/pig-common-excel/src/main/java/com/pig4cloud/pig/common/excel/ExcelAutoConfiguration.java

@@ -0,0 +1,69 @@
+package com.pig4cloud.pig.common.excel;
+
+import com.pig4cloud.pig.common.core.constant.SecurityConstants;
+import com.pig4cloud.pig.common.core.constant.ServiceNameConstants;
+import com.pig4cloud.pig.common.core.util.SpringContextHolder;
+import com.pig4cloud.pig.common.excel.provider.RemoteDictApiService;
+import com.pig4cloud.pig.common.excel.provider.RemoteDictDataProvider;
+import com.pig4cloud.plugin.excel.handler.DictDataProvider;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.web.client.RestClient;
+import org.springframework.web.client.support.RestClientAdapter;
+import org.springframework.web.service.invoker.HttpServiceProxyFactory;
+
+import java.util.Optional;
+
+/**
+ * Excel 自动装配类
+ *
+ * @author lengleng
+ * @date 2025/05/31
+ */
+@AutoConfiguration
+public class ExcelAutoConfiguration {
+
+	/**
+	 * 创建远程字典API服务实例
+	 * @param restClientBuilderOptional RestClient构建器的可选对象
+	 * @return {@link RemoteDictApiService} 远程字典API服务实例
+	 */
+	@Bean
+	@ConditionalOnMissingBean
+	public RemoteDictApiService remoteDictApiService(Optional<RestClient.Builder> restClientBuilderOptional) {
+		RestClient client = restClientBuilderOptional.orElseGet(RestClient::builder)
+			.baseUrl(getBaseUrl())
+			.defaultHeader(SecurityConstants.FROM, SecurityConstants.FROM_IN)
+			.build();
+		HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(RestClientAdapter.create(client)).build();
+		return factory.createClient(RemoteDictApiService.class);
+	}
+
+	/**
+	 * 创建字典数据提供程序
+	 * @param remoteDictApiService 远程字典API服务
+	 * @return 字典数据提供程序实例
+	 */
+	@Bean
+	@ConditionalOnMissingBean
+	public DictDataProvider dictDataProvider(RemoteDictApiService remoteDictApiService) {
+		return new RemoteDictDataProvider(remoteDictApiService);
+	}
+
+	/**
+	 * 获取基础URL
+	 * @return 根据当前架构模式组装的基础URL字符串
+	 */
+	private String getBaseUrl() {
+		// 根据当前架构模式,组装URL
+		if (SpringContextHolder.isMicro()) {
+			return String.format("http://%s", ServiceNameConstants.UPMS_SERVICE);
+		}
+		else {
+			return String.format("http://%s", SpringContextHolder.getEnvironment()
+				.resolvePlaceholders("127.0.0.1:${server.port}${server.servlet.context-path}"));
+		}
+	}
+
+}

+ 26 - 0
pig-common/pig-common-excel/src/main/java/com/pig4cloud/pig/common/excel/provider/RemoteDictApiService.java

@@ -0,0 +1,26 @@
+package com.pig4cloud.pig.common.excel.provider;
+
+import com.pig4cloud.pig.common.core.util.R;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.service.annotation.GetExchange;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 远程字典API服务接口,基于RestClient GetExchange实现
+ *
+ * @author lengleng
+ * @date 2025/05/31
+ */
+public interface RemoteDictApiService {
+
+	/**
+	 * 根据类型获取字典数据
+	 * @param type 字典类型
+	 * @return 包含字典数据的响应对象,字典数据以Map列表形式返回
+	 */
+	@GetExchange("/dict/remote/type/{type}")
+	R<List<Map<String, Object>>> getDictByType(@PathVariable String type);
+
+}

+ 48 - 0
pig-common/pig-common-excel/src/main/java/com/pig4cloud/pig/common/excel/provider/RemoteDictDataProvider.java

@@ -0,0 +1,48 @@
+package com.pig4cloud.pig.common.excel.provider;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
+import com.pig4cloud.pig.common.core.util.R;
+import com.pig4cloud.plugin.excel.handler.DictDataProvider;
+import com.pig4cloud.plugin.excel.vo.DictEnum;
+import lombok.RequiredArgsConstructor;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 远程字典数据提供程序实现类
+ *
+ * @author lengleng
+ * @date 2025/05/31
+ */
+@RequiredArgsConstructor
+public class RemoteDictDataProvider implements DictDataProvider {
+
+	private final RemoteDictApiService remoteDictApiService;
+
+	/**
+	 * 根据类型获取字典枚举数组
+	 * @param type 字典类型
+	 * @return 字典枚举数组,无数据时返回空数组
+	 */
+	@Override
+	public DictEnum[] getDict(String type) {
+		R<List<Map<String, Object>>> dictDataListR = remoteDictApiService.getDictByType(type);
+		List<Map<String, Object>> dictDataList = dictDataListR.getData();
+		if (CollUtil.isEmpty(dictDataList)) {
+			return new DictEnum[0];
+		}
+
+		// 构建 DictEnum 数组
+		DictEnum.Builder dictEnumBuilder = DictEnum.builder();
+		for (Map<String, Object> dictData : dictDataList) {
+			String value = MapUtil.getStr(dictData, "value");
+			String label = MapUtil.getStr(dictData, "label");
+			dictEnumBuilder.add(value, label);
+		}
+
+		return dictEnumBuilder.build();
+	}
+
+}

+ 1 - 0
pig-common/pig-common-excel/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1 @@
+ com.pig4cloud.pig.common.excel.ExcelAutoConfiguration

+ 72 - 0
pig-common/pig-common-feign/pom.xml

@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<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>com.pig4cloud</groupId>
+        <artifactId>pig-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <packaging>jar</packaging>
+    <artifactId>pig-common-feign</artifactId>
+    <description>feign-sentinel服务降级熔断、限流组件</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.pig4cloud</groupId>
+            <artifactId>pig-common-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
+        </dependency>
+        <!--feign 依赖-->
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-openfeign</artifactId>
+        </dependency>
+        <!-- okhttp 扩展 -->
+        <dependency>
+            <groupId>io.github.openfeign</groupId>
+            <artifactId>feign-okhttp</artifactId>
+        </dependency>
+        <!-- LB 扩展 -->
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
+        </dependency>
+        <!--caffeine 替换LB 默认缓存实现-->
+        <dependency>
+            <groupId>com.github.ben-manes.caffeine</groupId>
+            <artifactId>caffeine</artifactId>
+        </dependency>
+        <!--oauth server 依赖-->
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-core</artifactId>
+        </dependency>
+        <!-- 异常枚举 -->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-webmvc</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 77 - 0
pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/PigFeignAutoConfiguration.java

@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.feign;
+
+import com.alibaba.cloud.sentinel.feign.SentinelFeignAutoConfiguration;
+import com.pig4cloud.pig.common.feign.core.PigFeignInnerRequestInterceptor;
+import com.pig4cloud.pig.common.feign.core.PigFeignRequestCloseInterceptor;
+import com.pig4cloud.pig.common.feign.sentinel.ext.PigSentinelFeign;
+import feign.Feign;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.cloud.openfeign.PigFeignClientsRegistrar;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.context.annotation.Scope;
+
+/**
+ * Sentinel Feign 自动配置类
+ *
+ * @author lengleng
+ * @date 2025/05/31
+ */
+@Configuration(proxyBeanMethods = false)
+@Import(PigFeignClientsRegistrar.class)
+@AutoConfigureBefore(SentinelFeignAutoConfiguration.class)
+public class PigFeignAutoConfiguration {
+
+	/**
+	 * 创建Feign.Builder实例,支持Sentinel功能
+	 * @return Feign.Builder实例
+	 * @ConditionalOnMissingBean 当容器中不存在该类型bean时创建
+	 * @ConditionalOnProperty 当配置feign.sentinel.enabled为true时生效
+	 * @Scope 指定bean作用域为prototype
+	 */
+	@Bean
+	@Scope("prototype")
+	@ConditionalOnMissingBean
+	@ConditionalOnProperty(name = "feign.sentinel.enabled")
+	public Feign.Builder feignSentinelBuilder() {
+		return PigSentinelFeign.builder();
+	}
+
+	/**
+	 * 创建并返回PigFeignRequestCloseInterceptor实例
+	 * @return PigFeignRequestCloseInterceptor实例
+	 */
+	@Bean
+	public PigFeignRequestCloseInterceptor pigFeignRequestCloseInterceptor() {
+		return new PigFeignRequestCloseInterceptor();
+	}
+
+	/**
+	 * 创建并返回PigFeignInnerRequestInterceptor实例
+	 * @return PigFeignInnerRequestInterceptor 内部请求拦截器实例
+	 */
+	@Bean
+	public PigFeignInnerRequestInterceptor pigFeignInnerRequestInterceptor() {
+		return new PigFeignInnerRequestInterceptor();
+	}
+
+}

+ 53 - 0
pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/annotation/EnablePigFeignClients.java

@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.feign.annotation;
+
+import org.springframework.cloud.openfeign.EnableFeignClients;
+import org.springframework.core.annotation.AliasFor;
+
+import java.lang.annotation.*;
+
+/**
+ * 启用Pig Feign客户端注解
+ *
+ * @author lengleng
+ * @date 2025/05/31
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@EnableFeignClients
+public @interface EnablePigFeignClients {
+
+	/**
+	 * {@link #basePackages()}属性的别名。允许更简洁的注解声明
+	 * @return 'basePackages'数组
+	 */
+	String[] value() default {};
+
+	/**
+	 * 扫描注解组件的基础包路径
+	 * <p>
+	 * 与{@link #value()}互为别名且互斥
+	 * <p>
+	 * 对于基于字符串的包名,可使用{@link #basePackageClasses()}作为类型安全的替代方案
+	 * @return 基础包路径数组
+	 */
+	@AliasFor(annotation = EnableFeignClients.class, attribute = "basePackages")
+	String[] basePackages() default { "com.pig4cloud.pig" };
+
+}

+ 16 - 0
pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/annotation/NoToken.java

@@ -0,0 +1,16 @@
+package com.pig4cloud.pig.common.feign.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 服务无token调用声明注解
+ * <p>
+ * 只有发起方没有 token 时候才需要添加此注解, @NoToken + @Inner
+ * <p>
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface NoToken {
+
+}

+ 37 - 0
pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/core/PigFeignInnerRequestInterceptor.java

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

+ 25 - 0
pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/core/PigFeignRequestCloseInterceptor.java

@@ -0,0 +1,25 @@
+package com.pig4cloud.pig.common.feign.core;
+
+import feign.RequestInterceptor;
+import org.springframework.http.HttpHeaders;
+
+/**
+ * Feign请求连接关闭拦截器
+ * <p>
+ * 用于设置HTTP连接为关闭状态
+ *
+ * @author lengleng
+ * @date 2025/05/31
+ */
+public class PigFeignRequestCloseInterceptor implements RequestInterceptor {
+
+	/**
+	 * 设置连接关闭
+	 * @param template 请求模板
+	 */
+	@Override
+	public void apply(feign.RequestTemplate template) {
+		template.header(HttpHeaders.CONNECTION, "close");
+	}
+
+}

+ 82 - 0
pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/sentinel/SentinelAutoConfiguration.java

@@ -0,0 +1,82 @@
+/*
+ *    Copyright (c) 2018-2025, lengleng All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * Neither the name of the pig4cloud.com developer nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * Author: lengleng ([email protected])
+ */
+
+package com.pig4cloud.pig.common.feign.sentinel;
+
+import com.alibaba.cloud.sentinel.feign.SentinelFeignAutoConfiguration;
+import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.BlockExceptionHandler;
+import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.RequestOriginParser;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.pig4cloud.pig.common.feign.sentinel.ext.PigSentinelFeign;
+import com.pig4cloud.pig.common.feign.sentinel.handle.PigUrlBlockHandler;
+import com.pig4cloud.pig.common.feign.sentinel.parser.PigHeaderRequestOriginParser;
+import feign.Feign;
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Scope;
+
+/**
+ * Sentinel 自动配置类
+ *
+ * @author lengleng
+ * @date 2025/05/31
+ */
+@Configuration(proxyBeanMethods = false)
+@AutoConfigureBefore(SentinelFeignAutoConfiguration.class)
+public class SentinelAutoConfiguration {
+
+	/**
+	 * 创建Feign Sentinel构建器
+	 * @return Feign.Builder实例
+	 * @ConditionalOnMissingBean 当容器中不存在该类型bean时创建
+	 * @ConditionalOnProperty 当配置项spring.cloud.openfeign.sentinel.enabled为true时生效
+	 * @Scope 指定bean作用域为prototype
+	 */
+	@Bean
+	@Scope("prototype")
+	@ConditionalOnMissingBean
+	@ConditionalOnProperty(name = "spring.cloud.openfeign.sentinel.enabled")
+	public Feign.Builder feignSentinelBuilder() {
+		return PigSentinelFeign.builder();
+	}
+
+	/**
+	 * 创建默认的BlockExceptionHandler bean
+	 * @param objectMapper 对象映射器
+	 * @return PigUrlBlockHandler实例
+	 * @ConditionalOnMissingBean 当容器中不存在该类型bean时创建
+	 */
+	@Bean
+	@ConditionalOnMissingBean
+	public BlockExceptionHandler blockExceptionHandler(ObjectMapper objectMapper) {
+		return new PigUrlBlockHandler(objectMapper);
+	}
+
+	/**
+	 * 创建并返回一个RequestOriginParser bean,当容器中不存在该类型的bean时生效
+	 * @return 默认的PigHeaderRequestOriginParser实例
+	 */
+	@Bean
+	@ConditionalOnMissingBean
+	public RequestOriginParser requestOriginParser() {
+		return new PigHeaderRequestOriginParser();
+	}
+
+}

+ 139 - 0
pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/sentinel/ext/PigSentinelFeign.java

@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.feign.sentinel.ext;
+
+import com.alibaba.cloud.sentinel.feign.SentinelContractHolder;
+import feign.Contract;
+import feign.Feign;
+import feign.InvocationHandlerFactory;
+import feign.Target;
+import org.springframework.beans.BeansException;
+import org.springframework.cloud.openfeign.FallbackFactory;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.cloud.openfeign.FeignClientFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.util.StringUtils;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.util.Map;
+
+/**
+ * 支持自动降级注入的Feign构建器,重写 {@link com.alibaba.cloud.sentinel.feign.SentinelFeign}
+ *
+ * @author lengleng
+ * @date 2025/05/31
+ */
+public final class PigSentinelFeign {
+
+	private PigSentinelFeign() {
+
+	}
+
+	public static PigSentinelFeign.Builder builder() {
+		return new PigSentinelFeign.Builder();
+	}
+
+	public static final class Builder extends Feign.Builder implements ApplicationContextAware {
+
+		private Contract contract = new Contract.Default();
+
+		private ApplicationContext applicationContext;
+
+		private FeignClientFactory feignClientFactory;
+
+		@Override
+		public Feign.Builder invocationHandlerFactory(InvocationHandlerFactory invocationHandlerFactory) {
+			throw new UnsupportedOperationException();
+		}
+
+		@Override
+		public PigSentinelFeign.Builder contract(Contract contract) {
+			this.contract = contract;
+			return this;
+		}
+
+		@Override
+		public Feign internalBuild() {
+			super.invocationHandlerFactory(new InvocationHandlerFactory() {
+				@Override
+				public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
+
+					// 查找 FeignClient 上的 降级策略
+					FeignClient feignClient = AnnotationUtils.findAnnotation(target.type(), FeignClient.class);
+					Class<?> fallback = feignClient.fallback();
+					Class<?> fallbackFactory = feignClient.fallbackFactory();
+
+					String beanName = feignClient.contextId();
+					if (!StringUtils.hasText(beanName)) {
+						beanName = feignClient.name();
+					}
+
+					Object fallbackInstance;
+					FallbackFactory<?> fallbackFactoryInstance;
+					if (void.class != fallback) {
+						fallbackInstance = getFromContext(beanName, "fallback", fallback, target.type());
+						return new PigSentinelInvocationHandler(target, dispatch,
+								new FallbackFactory.Default(fallbackInstance));
+					}
+
+					if (void.class != fallbackFactory) {
+						fallbackFactoryInstance = (FallbackFactory<?>) getFromContext(beanName, "fallbackFactory",
+								fallbackFactory, FallbackFactory.class);
+						return new PigSentinelInvocationHandler(target, dispatch, fallbackFactoryInstance);
+					}
+					return new PigSentinelInvocationHandler(target, dispatch);
+				}
+
+				private Object getFromContext(String name, String type, Class<?> fallbackType, Class<?> targetType) {
+					Object fallbackInstance = feignClientFactory.getInstance(name, fallbackType);
+					if (fallbackInstance == null) {
+						throw new IllegalStateException(String
+							.format("No %s instance of type %s found for feign client %s", type, fallbackType, name));
+					}
+
+					if (!targetType.isAssignableFrom(fallbackType)) {
+						throw new IllegalStateException(String.format(
+								"Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",
+								type, fallbackType, targetType, name));
+					}
+					return fallbackInstance;
+				}
+			});
+
+			super.contract(new SentinelContractHolder(contract));
+			return super.internalBuild();
+		}
+
+		/**
+		 * private Object getFieldValue(Object instance, String fieldName) { Field field =
+		 * ReflectionUtils.findField(instance.getClass(), fieldName);
+		 * field.setAccessible(true); try { return field.get(instance); } catch
+		 * (IllegalAccessException e) { // ignore } return null; }
+		 **/
+
+		@Override
+		public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+			this.applicationContext = applicationContext;
+			this.feignClientFactory = this.applicationContext.getBean(FeignClientFactory.class);
+		}
+
+	}
+
+}

+ 191 - 0
pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/sentinel/ext/PigSentinelInvocationHandler.java

@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.feign.sentinel.ext;
+
+import com.alibaba.cloud.sentinel.feign.SentinelContractHolder;
+import com.alibaba.cloud.sentinel.feign.SentinelInvocationHandler;
+import com.alibaba.csp.sentinel.Entry;
+import com.alibaba.csp.sentinel.EntryType;
+import com.alibaba.csp.sentinel.SphU;
+import com.alibaba.csp.sentinel.Tracer;
+import com.alibaba.csp.sentinel.context.ContextUtil;
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+import com.pig4cloud.pig.common.core.util.R;
+import feign.Feign;
+import feign.InvocationHandlerFactory;
+import feign.MethodMetadata;
+import feign.Target;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cloud.openfeign.FallbackFactory;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static feign.Util.checkNotNull;
+
+/**
+ * 支持自动降级注入的Sentinel调用处理器,重写{@link SentinelInvocationHandler}
+ *
+ * @author lengleng
+ * @date 2025/05/31
+ */
+@Slf4j
+public class PigSentinelInvocationHandler implements InvocationHandler {
+
+	public static final String EQUALS = "equals";
+
+	public static final String HASH_CODE = "hashCode";
+
+	public static final String TO_STRING = "toString";
+
+	private final Target<?> target;
+
+	private final Map<Method, InvocationHandlerFactory.MethodHandler> dispatch;
+
+	private FallbackFactory<?> fallbackFactory;
+
+	private Map<Method, Method> fallbackMethodMap;
+
+	PigSentinelInvocationHandler(Target<?> target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch,
+			FallbackFactory<?> fallbackFactory) {
+		this.target = checkNotNull(target, "target");
+		this.dispatch = checkNotNull(dispatch, "dispatch");
+		this.fallbackFactory = fallbackFactory;
+		this.fallbackMethodMap = toFallbackMethod(dispatch);
+	}
+
+	PigSentinelInvocationHandler(Target<?> target, Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
+		this.target = checkNotNull(target, "target");
+		this.dispatch = checkNotNull(dispatch, "dispatch");
+	}
+
+	@Override
+	public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
+		if (EQUALS.equals(method.getName())) {
+			try {
+				Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
+				return equals(otherHandler);
+			}
+			catch (IllegalArgumentException e) {
+				return false;
+			}
+		}
+		else if (HASH_CODE.equals(method.getName())) {
+			return hashCode();
+		}
+		else if (TO_STRING.equals(method.getName())) {
+			return toString();
+		}
+
+		Object result;
+		InvocationHandlerFactory.MethodHandler methodHandler = this.dispatch.get(method);
+		// only handle by HardCodedTarget
+		if (target instanceof Target.HardCodedTarget) {
+			Target.HardCodedTarget<?> hardCodedTarget = (Target.HardCodedTarget) target;
+			MethodMetadata methodMetadata = SentinelContractHolder.METADATA_MAP
+				.get(hardCodedTarget.type().getName() + Feign.configKey(hardCodedTarget.type(), method));
+			// resource default is HttpMethod:protocol://url
+			if (methodMetadata == null) {
+				result = methodHandler.invoke(args);
+			}
+			else {
+				String resourceName = methodMetadata.template().method().toUpperCase() + ':' + hardCodedTarget.url()
+						+ methodMetadata.template().path();
+				Entry entry = null;
+				try {
+					ContextUtil.enter(resourceName);
+					entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
+					result = methodHandler.invoke(args);
+				}
+				catch (Throwable ex) {
+					// fallback handle
+					if (!BlockException.isBlockException(ex)) {
+						Tracer.trace(ex);
+					}
+					if (fallbackFactory != null) {
+						try {
+							return fallbackMethodMap.get(method).invoke(fallbackFactory.create(ex), args);
+						}
+						catch (IllegalAccessException e) {
+							// shouldn't happen as method is public due to being an
+							// interface
+							throw new AssertionError(e);
+						}
+						catch (InvocationTargetException e) {
+							throw new AssertionError(e.getCause());
+						}
+					}
+					else {
+						// 若是R类型 执行自动降级返回R
+						if (R.class == method.getReturnType()) {
+							log.error("feign 服务间调用异常", ex);
+							return R.failed(ex.getLocalizedMessage());
+						}
+						else {
+							throw ex;
+						}
+					}
+				}
+				finally {
+					if (entry != null) {
+						entry.exit(1, args);
+					}
+					ContextUtil.exit();
+				}
+			}
+		}
+		else {
+			// other target type using default strategy
+			result = methodHandler.invoke(args);
+		}
+
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (obj instanceof SentinelInvocationHandler) {
+			PigSentinelInvocationHandler other = (PigSentinelInvocationHandler) obj;
+			return target.equals(other.target);
+		}
+		return false;
+	}
+
+	@Override
+	public int hashCode() {
+		return target.hashCode();
+	}
+
+	@Override
+	public String toString() {
+		return target.toString();
+	}
+
+	static Map<Method, Method> toFallbackMethod(Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
+		Map<Method, Method> result = new LinkedHashMap<>();
+		for (Method method : dispatch.keySet()) {
+			method.setAccessible(true);
+			result.put(method, method);
+		}
+		return result;
+	}
+
+}

+ 140 - 0
pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/sentinel/handle/GlobalBizExceptionHandler.java

@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.feign.sentinel.handle;
+
+import com.alibaba.csp.sentinel.Tracer;
+import com.pig4cloud.pig.common.core.util.R;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.core.annotation.Order;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.core.SpringSecurityMessageSource;
+import org.springframework.util.Assert;
+import org.springframework.validation.BindException;
+import org.springframework.validation.FieldError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.servlet.resource.NoResourceFoundException;
+
+import java.util.List;
+
+/**
+ * 全局业务异常处理器,结合Sentinel处理系统异常
+ * <p>
+ * 注意:全局异常处理器不能作用在OAuth Server
+ * </p>
+ *
+ * @author lengleng
+ * @date 2025/05/31
+ */
+@Slf4j
+@Order(10000)
+@RestControllerAdvice
+@ConditionalOnExpression("!'${security.oauth2.client.clientId}'.isEmpty()")
+public class GlobalBizExceptionHandler {
+
+	/**
+	 * 全局异常.
+	 * @param e the e
+	 * @return R
+	 */
+	@ExceptionHandler(Exception.class)
+	@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+	public R handleGlobalException(Exception e) {
+		log.error("全局异常信息 ex={}", e.getMessage(), e);
+
+		// 业务异常交由 sentinel 记录
+		Tracer.trace(e);
+		return R.failed(e.getLocalizedMessage());
+	}
+
+	/**
+	 * 处理业务校验过程中碰到的非法参数异常 该异常基本由{@link org.springframework.util.Assert}抛出
+	 * @param exception 参数校验异常
+	 * @return API返回结果对象包装后的错误输出结果
+	 * @see Assert#hasLength(String, String)
+	 * @see Assert#hasText(String, String)
+	 * @see Assert#isTrue(boolean, String)
+	 * @see Assert#isNull(Object, String)
+	 * @see Assert#notNull(Object, String)
+	 */
+	@ExceptionHandler(IllegalArgumentException.class)
+	@ResponseStatus(HttpStatus.OK)
+	public R handleIllegalArgumentException(IllegalArgumentException exception) {
+		log.error("非法参数,ex = {}", exception.getMessage(), exception);
+		return R.failed(exception.getMessage());
+	}
+
+	/**
+	 * AccessDeniedException
+	 * @param e the e
+	 * @return R
+	 */
+	@ExceptionHandler(AccessDeniedException.class)
+	@ResponseStatus(HttpStatus.FORBIDDEN)
+	public R handleAccessDeniedException(AccessDeniedException e) {
+		String msg = SpringSecurityMessageSource.getAccessor()
+			.getMessage("AbstractAccessDecisionManager.accessDenied", e.getMessage());
+		log.warn("拒绝授权异常信息 ex={}", msg);
+		return R.failed(msg);
+	}
+
+	/**
+	 * validation Exception
+	 * @param exception
+	 * @return R
+	 */
+	@ExceptionHandler({ MethodArgumentNotValidException.class })
+	@ResponseStatus(HttpStatus.BAD_REQUEST)
+	public R handleBodyValidException(MethodArgumentNotValidException exception) {
+		List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();
+		log.warn("参数绑定异常,ex = {}", fieldErrors.get(0).getDefaultMessage());
+		return R.failed(String.format("%s %s", fieldErrors.get(0).getField(), fieldErrors.get(0).getDefaultMessage()));
+	}
+
+	/**
+	 * validation Exception (以form-data形式传参)
+	 * @param exception
+	 * @return R
+	 */
+	@ExceptionHandler({ BindException.class })
+	@ResponseStatus(HttpStatus.BAD_REQUEST)
+	public R bindExceptionHandler(BindException exception) {
+		List<FieldError> fieldErrors = exception.getBindingResult().getFieldErrors();
+		log.warn("参数绑定异常,ex = {}", fieldErrors.get(0).getDefaultMessage());
+		return R.failed(fieldErrors.get(0).getDefaultMessage());
+	}
+
+	/**
+	 * 保持和低版本请求路径不存在的行为一致
+	 * <p>
+	 * <a href="https://github.com/spring-projects/spring-boot/issues/38733">[Spring Boot
+	 * 3.2.0] 404 Not Found behavior #38733</a>
+	 * @param exception
+	 * @return R
+	 */
+	@ExceptionHandler({ NoResourceFoundException.class })
+	@ResponseStatus(HttpStatus.NOT_FOUND)
+	public R notFoundExceptionHandler(NoResourceFoundException exception) {
+		log.debug("请求路径 404 {}", exception.getMessage());
+		return R.failed(exception.getMessage());
+	}
+
+}

+ 54 - 0
pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/sentinel/handle/PigUrlBlockHandler.java

@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.feign.sentinel.handle;
+
+import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.BlockExceptionHandler;
+import com.alibaba.csp.sentinel.slots.block.BlockException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.pig4cloud.pig.common.core.util.R;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+
+/**
+ * Sentinel统一降级限流策略处理器
+ * <p>
+ * 实现BlockExceptionHandler接口,处理Sentinel限流降级异常
+ *
+ * @author lengleng
+ * @date 2025/05/31
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class PigUrlBlockHandler implements BlockExceptionHandler {
+
+	private final ObjectMapper objectMapper;
+
+	@Override
+	public void handle(HttpServletRequest request, HttpServletResponse response, String resourceName, BlockException e)
+			throws Exception {
+		log.error("sentinel 降级 资源名称{}", resourceName, e);
+
+		response.setContentType(MediaType.APPLICATION_JSON.getType());
+		response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
+		response.getWriter().print(objectMapper.writeValueAsString(R.failed(e.getMessage())));
+	}
+
+}

+ 45 - 0
pig-common/pig-common-feign/src/main/java/com/pig4cloud/pig/common/feign/sentinel/parser/PigHeaderRequestOriginParser.java

@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.pig4cloud.pig.common.feign.sentinel.parser;
+
+import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.RequestOriginParser;
+import jakarta.servlet.http.HttpServletRequest;
+
+/**
+ * Sentinel 请求头解析判断实现类,用于从HTTP请求头中获取Allow字段值
+ *
+ * @author lengleng
+ * @date 2025/05/31
+ */
+public class PigHeaderRequestOriginParser implements RequestOriginParser {
+
+	/**
+	 * 请求头获取allow
+	 */
+	private static final String ALLOW = "Allow";
+
+	/**
+	 * 解析HTTP请求中的来源信息
+	 * @param request HTTP请求对象
+	 * @return 解析出的来源信息
+	 */
+	@Override
+	public String parseOrigin(HttpServletRequest request) {
+		return request.getHeader(ALLOW);
+	}
+
+}

Some files were not shown because too many files changed in this diff