zhaojinyu 1 hónapja
commit
448d5076d5
100 módosított fájl, 5823 hozzáadás és 0 törlés
  1. 8 0
      .idea/.gitignore
  2. 66 0
      .idea/compiler.xml
  3. 59 0
      .idea/encodings.xml
  4. 25 0
      .idea/jarRepositories.xml
  5. 12 0
      .idea/misc.xml
  6. 4 0
      .idea/vcs.xml
  7. 130 0
      README.md
  8. 25 0
      jnpf-boot-common/jnpf-common-ai/pom.xml
  9. 193 0
      jnpf-boot-common/jnpf-common-ai/src/main/java/jnpf/config/AiAutoConfiguration.java
  10. 134 0
      jnpf-boot-common/jnpf-common-ai/src/main/java/jnpf/config/AiProperties.java
  11. 55 0
      jnpf-boot-common/jnpf-common-ai/src/main/java/jnpf/constants/AiConstants.java
  12. 42 0
      jnpf-boot-common/jnpf-common-ai/src/main/java/jnpf/service/OpenAiService.java
  13. 101 0
      jnpf-boot-common/jnpf-common-ai/src/main/java/jnpf/service/impl/DefaultOpenAiServiceImpl.java
  14. 41 0
      jnpf-boot-common/jnpf-common-ai/src/main/java/jnpf/service/impl/DisabledOpenAiServiceImpl.java
  15. 54 0
      jnpf-boot-common/jnpf-common-ai/src/main/java/jnpf/util/AiLimitUtil.java
  16. 153 0
      jnpf-boot-common/jnpf-common-ai/target/classes/META-INF/spring-configuration-metadata.json
  17. BIN
      jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/config/AiAutoConfiguration$AiEnabledConfiguration$1.class
  18. BIN
      jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/config/AiAutoConfiguration$AiEnabledConfiguration.class
  19. BIN
      jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/config/AiAutoConfiguration.class
  20. 193 0
      jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/config/AiAutoConfiguration.java
  21. BIN
      jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/config/AiProperties$ChatOption.class
  22. BIN
      jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/config/AiProperties$Proxy.class
  23. BIN
      jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/config/AiProperties.class
  24. 134 0
      jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/config/AiProperties.java
  25. BIN
      jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/constants/AiConstants$Model.class
  26. BIN
      jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/constants/AiConstants.class
  27. 55 0
      jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/constants/AiConstants.java
  28. BIN
      jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/service/OpenAiService.class
  29. 42 0
      jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/service/OpenAiService.java
  30. BIN
      jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/service/impl/DefaultOpenAiServiceImpl$1.class
  31. BIN
      jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/service/impl/DefaultOpenAiServiceImpl.class
  32. 101 0
      jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/service/impl/DefaultOpenAiServiceImpl.java
  33. BIN
      jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/service/impl/DisabledOpenAiServiceImpl.class
  34. 41 0
      jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/service/impl/DisabledOpenAiServiceImpl.java
  35. BIN
      jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/util/AiLimitUtil.class
  36. 54 0
      jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/util/AiLimitUtil.java
  37. BIN
      jnpf-boot-common/jnpf-common-ai/target/jnpf-common-ai-6.0.0-RELEASE.jar
  38. 3 0
      jnpf-boot-common/jnpf-common-ai/target/maven-archiver/pom.properties
  39. 14 0
      jnpf-boot-common/jnpf-common-ai/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
  40. 7 0
      jnpf-boot-common/jnpf-common-ai/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
  41. 71 0
      jnpf-boot-common/jnpf-common-auth/pom.xml
  42. 148 0
      jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/config/AsyncConfig.java
  43. 48 0
      jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/config/AuthAutoConfigration.java
  44. 61 0
      jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/config/JnpfOauthConfig.java
  45. 104 0
      jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/config/JnpfTokenConfig.java
  46. 83 0
      jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/consts/AuthConsts.java
  47. 40 0
      jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/consts/DeviceType.java
  48. 46 0
      jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/consts/LoginTicketStatus.java
  49. 29 0
      jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/consts/ScanCodeTicketStatus.java
  50. 286 0
      jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/granter/AbstractTokenGranter.java
  51. 24 0
      jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/granter/TokenGranter.java
  52. 83 0
      jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/granter/TokenGranterBuilder.java
  53. 38 0
      jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/granter/UserDetailsServiceBuilder.java
  54. 37 0
      jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/model/LoginTicketModel.java
  55. 25 0
      jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/model/ScanCodeLoginConfigModel.java
  56. 58 0
      jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/service/LoginService.java
  57. 19 0
      jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/service/UserDetailService.java
  58. 121 0
      jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/util/TenantProvider.java
  59. 68 0
      jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/util/TicketUtil.java
  60. 549 0
      jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/util/UserProvider.java
  61. 272 0
      jnpf-boot-common/jnpf-common-auth/target/classes/META-INF/spring-configuration-metadata.json
  62. BIN
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/config/AsyncConfig.class
  63. 148 0
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/config/AsyncConfig.java
  64. BIN
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/config/AuthAutoConfigration.class
  65. 48 0
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/config/AuthAutoConfigration.java
  66. BIN
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/config/JnpfOauthConfig.class
  67. 61 0
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/config/JnpfOauthConfig.java
  68. BIN
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/config/JnpfTokenConfig.class
  69. 104 0
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/config/JnpfTokenConfig.java
  70. BIN
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/consts/AuthConsts.class
  71. 83 0
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/consts/AuthConsts.java
  72. BIN
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/consts/DeviceType.class
  73. 40 0
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/consts/DeviceType.java
  74. BIN
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/consts/LoginTicketStatus.class
  75. 46 0
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/consts/LoginTicketStatus.java
  76. BIN
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/consts/ScanCodeTicketStatus.class
  77. 29 0
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/consts/ScanCodeTicketStatus.java
  78. BIN
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/granter/AbstractTokenGranter.class
  79. 286 0
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/granter/AbstractTokenGranter.java
  80. BIN
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/granter/TokenGranter.class
  81. 24 0
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/granter/TokenGranter.java
  82. BIN
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/granter/TokenGranterBuilder.class
  83. 83 0
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/granter/TokenGranterBuilder.java
  84. BIN
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/granter/UserDetailsServiceBuilder.class
  85. 38 0
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/granter/UserDetailsServiceBuilder.java
  86. BIN
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/model/LoginTicketModel.class
  87. 37 0
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/model/LoginTicketModel.java
  88. BIN
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/model/ScanCodeLoginConfigModel.class
  89. 25 0
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/model/ScanCodeLoginConfigModel.java
  90. BIN
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/service/LoginService.class
  91. 58 0
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/service/LoginService.java
  92. BIN
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/service/UserDetailService.class
  93. 19 0
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/service/UserDetailService.java
  94. BIN
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/util/TenantProvider.class
  95. 121 0
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/util/TenantProvider.java
  96. BIN
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/util/TicketUtil.class
  97. 68 0
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/util/TicketUtil.java
  98. BIN
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/util/UserProvider.class
  99. 549 0
      jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/util/UserProvider.java
  100. BIN
      jnpf-boot-common/jnpf-common-auth/target/jnpf-common-auth-6.0.0-RELEASE.jar

+ 8 - 0
.idea/.gitignore

@@ -0,0 +1,8 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml

+ 66 - 0
.idea/compiler.xml

@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <annotationProcessing>
+      <profile name="Maven default annotation processors profile" enabled="true">
+        <sourceOutputDir name="target/generated-sources/annotations" />
+        <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
+        <outputRelativeToContentRoot value="true" />
+        <module name="jnpf-common-mq" />
+        <module name="jnpf-common-cloudshardingsphere" />
+        <module name="jnpf-common-ai" />
+        <module name="jnpf-common-event" />
+        <module name="jnpf-common-seata" />
+        <module name="jnpf-common-connector" />
+        <module name="jnpf-common-file" />
+        <module name="jnpf-common-i18n" />
+        <module name="jnpf-common-core" />
+        <module name="jnpf-common-security" />
+        <module name="jnpf-common-redis" />
+        <module name="jnpf-common-dubbo" />
+        <module name="jnpf-common-feign" />
+        <module name="jnpf-common-scheduletask" />
+        <module name="jnpf-common-database" />
+        <module name="jnpf-common-office-v3" />
+        <module name="jnpf-common-shardingsphere" />
+        <module name="jnpf-common-socials" />
+        <module name="jnpf-common-swagger" />
+        <module name="jnpf-common-auth" />
+        <module name="jnpf-common-selenium" />
+        <module name="jnpf-common-sms" />
+        <module name="jnpf-common-office" />
+      </profile>
+    </annotationProcessing>
+  </component>
+  <component name="JavacSettings">
+    <option name="ADDITIONAL_OPTIONS_OVERRIDE">
+      <module name="jnpf-boot-common" options="-parameters" />
+      <module name="jnpf-cloud-common" options="-parameters" />
+      <module name="jnpf-common" options="-parameters" />
+      <module name="jnpf-common-ai" options="-parameters" />
+      <module name="jnpf-common-auth" options="-parameters" />
+      <module name="jnpf-common-cloudshardingsphere" options="-parameters" />
+      <module name="jnpf-common-connector" options="-parameters" />
+      <module name="jnpf-common-core" options="-parameters" />
+      <module name="jnpf-common-database" options="-parameters" />
+      <module name="jnpf-common-dubbo" options="-parameters" />
+      <module name="jnpf-common-event" options="-parameters" />
+      <module name="jnpf-common-feign" options="-parameters" />
+      <module name="jnpf-common-file" options="-parameters" />
+      <module name="jnpf-common-i18n" options="-parameters" />
+      <module name="jnpf-common-mq" options="-parameters" />
+      <module name="jnpf-common-office" options="-parameters" />
+      <module name="jnpf-common-office-v3" options="-parameters" />
+      <module name="jnpf-common-redis" options="-parameters" />
+      <module name="jnpf-common-scheduletask" options="-parameters" />
+      <module name="jnpf-common-seata" options="-parameters" />
+      <module name="jnpf-common-security" options="-parameters" />
+      <module name="jnpf-common-selenium" options="-parameters" />
+      <module name="jnpf-common-shardingsphere" options="-parameters" />
+      <module name="jnpf-common-sms" options="-parameters" />
+      <module name="jnpf-common-socials" options="-parameters" />
+      <module name="jnpf-common-swagger" options="-parameters" />
+      <module name="jnpf-dependencies" options="-parameters" />
+    </option>
+  </component>
+</project>

+ 59 - 0
.idea/encodings.xml

@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Encoding">
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-ai/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-ai/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-auth/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-auth/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-compatible/jnpf-common-office-v3/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-compatible/jnpf-common-office-v3/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-connector/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-connector/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-core/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-core/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-database/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-database/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-event/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-event/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-file/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-file/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-i18n/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-i18n/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-office/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-office/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-redis/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-redis/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-scheduletask/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-scheduletask/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-security/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-security/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-selenium/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-selenium/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-shardingsphere/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-shardingsphere/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-sms/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-sms/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-socials/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-socials/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-swagger/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/jnpf-common-swagger/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-boot-common/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-cloud-common/jnpf-common-cloudshardingsphere/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-cloud-common/jnpf-common-cloudshardingsphere/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-cloud-common/jnpf-common-dubbo/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-cloud-common/jnpf-common-dubbo/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-cloud-common/jnpf-common-feign/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-cloud-common/jnpf-common-feign/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-cloud-common/jnpf-common-mq/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-cloud-common/jnpf-common-mq/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-cloud-common/jnpf-common-seata/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-cloud-common/jnpf-common-seata/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-cloud-common/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-cloud-common/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-dependencies/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/jnpf-dependencies/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
+  </component>
+</project>

+ 25 - 0
.idea/jarRepositories.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="RemoteRepositoriesConfiguration">
+    <remote-repository>
+      <option name="id" value="central" />
+      <option name="name" value="Central Repository" />
+      <option name="url" value="http://127.0.0.1:9999/repository/maven-public/" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="getui-nexus" />
+      <option name="name" value="getui-nexus" />
+      <option name="url" value="http://127.0.0.1:9999/repository/maven-public/" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="central" />
+      <option name="name" value="Maven Central repository" />
+      <option name="url" value="https://repo1.maven.org/maven2" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="jboss.community" />
+      <option name="name" value="JBoss Community repository" />
+      <option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
+    </remote-repository>
+  </component>
+</project>

+ 12 - 0
.idea/misc.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ExternalStorageConfigurationManager" enabled="true" />
+  <component name="MavenProjectsManager">
+    <option name="originalFiles">
+      <list>
+        <option value="$PROJECT_DIR$/pom.xml" />
+      </list>
+    </option>
+  </component>
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK" />
+</project>

+ 4 - 0
.idea/vcs.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings" defaultProject="true" />
+</project>

+ 130 - 0
README.md

@@ -0,0 +1,130 @@
+> 特别说明:源码、JDK、数据库、Redis等安装或存放路径禁止包含中文、空格、特殊字符等
+
+## 一 项目结构
+
+```text
+├── jnpf-boot-common - 单体版本涉及依赖 
+│   ├── jnpf-common-auth - 认证模块
+│   ├── jnpf-common-compatible - 兼容模块文件夹
+│           ├── jnpf-common-office-v3- Boot3 Office
+│   ├── jnpf-common-connector - 单点数据推送模块
+│   ├── jnpf-common-core - 基础类及常用工具
+│   ├── jnpf-common-database - 数据库配置及多数据库兼容
+│   ├── jnpf-common-event - 事件发布
+│   ├── jnpf-common-file - 文件工具类模块
+│   ├── jnpf-common-i18n - 文件工具类模块
+│   ├── jnpf-common-office - office操作模块
+│   ├── jnpf-common-redis -  缓存工具Redis组件配置
+│   ├── jnpf-common-scheduletask - 调度工具
+│   ├── jnpf-common-security - 接口鉴权配置
+│   ├── jnpf-common-selenium - 浏览器模拟
+│   ├── jnpf-common-shardingsphere - shardingsphere配置
+│   ├── jnpf-common-sms - 短信模块
+│   ├── jnpf-common-swagger - API组件Swagger配置
+│   └── pom.xml
+├── jnpf-cloud-common - 微服务版本涉及依赖 
+│   ├── jnpf-common-cloudshardingsphere - 微服务shardingsphere配置
+│   ├── jnpf-common-dubbo - Dubbo拦截器, 自动封装认证信息
+│   ├── jnpf-common-mq - 消息队列
+│   ├── jnpf-common-feign - 远程调用Feign组件配置
+│   ├── jnpf-common-seata - seata依赖
+│   └── pom.xml
+├── jnpf-dependencies - 所有依赖版本
+│   └── pom.xml
+├── pom.xml
+└── README.md - 项目说明文档
+```
+
+## 二 环境要求
+
+| 类目    | 版本或建议  |
+|-------|---------|
+| 硬件    | 开发电脑建议使用I3及以上CPU,16G及以上内存  |
+| 操作系统  | Windows 10/11,MacOS   |
+| JDK   | 默认使用JDK 21,如需要切换JDK 8/11/17版本请参考文档调整代码,推荐使用 `OpenJDK`,如 `Liberica JDK`、`Eclipse Temurin`、`Alibaba Dragonwell`、`BiSheng`等发行版; |
+| Maven | 依赖管理工具,推荐使用 `3.6.3` 及以上版本  |
+| IDE   | 代码集成开发环境,推荐使用 `IDEA2024` 及以上版本,兼容 `Eclipse`、 `Spring Tool Suite` 等IDE工具 |
+
+## 三 关联项目
+
+> 为以下项目提供基础依赖
+
+| 项目 | 分支            | 说明 |
+| --- |---------------| --- |
+| jnpf-file-core-starter | v6.0.x-stable | 文件基础依赖项目源码 |
+| jnpf-java-datareport-univer | v6.0.x-stable | Univer报表源码 |
+| jnpf-java-datareport-univer-core | v6.0.x-stable | Univer报表核心依赖源码 |
+| jnpf-scheduletask | v6.0.x-stable | 任务调度客户端依赖及服务端项目源码 |
+| jnpf-java-boot  | v6.0.x-stable | Java单体后端项目源码 |
+| jnpf-java-cloud | v6.0.x-stable | Java微服务后端项目源码 |
+| jnpf-java-tenant  | v6.0.x-stable | Java多租户后端项目源码 |
+
+## 四 使用方式
+
+### 4.1 前置条件
+
+#### 4.1.1 本地安装jnpf-common-core
+
+在IDEA中,双击右侧 `Maven` 中 `jnpf-common` > `jnpf-boot-common` > `jnpf-common-core` > `Lifecycle` > `install`,将 `jnpf-common-core` 包安装至本地
+
+#### 4.1.2 本地安装jnpf-dependencies
+
+在IDEA中,双击右侧 `Maven` 中 `jnpf-common` > `jnpf-dependencies` > `Lifecycle` > `install`,将 `jnpf-dependencies` 包安装至本地
+
+#### 4.1.3 本地安装file-core-starter
+
+IDEA打开 `jnpf-file-core-starter` 项目, 双击右侧 `Maven`中 `jnpf-file-core-starter` > `Lifecycle` > `install`,将 `jnpf-file-core-starter` 包安装至本地
+
+#### 4.1.4 本地安装scheduletask
+
+IDEA打开 `jnpf-scheduletask` 项目, 双击右侧 `Maven`中`jnpf-scheduletask` > `Lifecycle` > `install`,将 `jnpf-scheduletask` 包安装至本地
+
+### 4.2 本地安装
+
+在IDEA中,双击右侧 `Maven` 中 `jnpf-common` > `Lifecycle` > `install`,将 `jnpf-common` 包安装至本地
+
+### 4.3 私服发布
+> 若无Maven私服,忽略本节内容
+
+#### 4.3.1 配置Maven
+
+打开Maven安装目录中的 `conf/setttings.xml`,
+
+在 `<servers></servers>` 节点增加 `<server></server>` ,如下所示:
+
+```xml
+  <!-- 发布版 -->
+  <server>
+    <id>maven-releases</id>
+    <username>jnpf-user(账号,结合私服配置设置)</username>
+    <password>123456(密码,结合私服配置设置)</password>
+  </server>
+```
+
+#### 4.3.2 配置项目
+> 注意:pom.xml里 `<id>` 和 setting.xml 配置里 `<id>` 对应。
+
+修改 `jnpf-common/jnpf-dependencies/pom.xml` 文件
+
+```xml
+<distributionManagement>
+  <repository>
+    <id>maven-releases</id>
+    <name>maven-releases</name>
+    <url>http://nexus.jnpfsoft.com/repository/maven-releases/</url>
+  </repository>
+</distributionManagement>
+```
+
+#### 4.3.3 发布到私服
+
+在IDEA中,双击右侧 `Maven` 中 `jnpf-common` > `Lifecycle` > `deploy` 发布至私服。
+
+## 五 更新版本号
+
+打开 `jnpf-common/jnpf-dependencies/` 目录,执行如下命令
+
+```
+# mvn versions:set -DnewVersion=6.0.0-RELEASE
+mvn versions:set -DnewVersion=版本号
+```

+ 25 - 0
jnpf-boot-common/jnpf-common-ai/pom.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.jnpf</groupId>
+        <artifactId>jnpf-boot-common</artifactId>
+        <version>6.0.0-RELEASE</version>
+    </parent>
+
+    <artifactId>jnpf-common-ai</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.jnpf</groupId>
+            <artifactId>jnpf-common-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.unfbx</groupId>
+            <artifactId>chatgpt-java</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 193 - 0
jnpf-boot-common/jnpf-common-ai/src/main/java/jnpf/config/AiAutoConfiguration.java

@@ -0,0 +1,193 @@
+package jnpf.config;
+
+import com.unfbx.chatgpt.OpenAiClient;
+import com.unfbx.chatgpt.function.KeyRandomStrategy;
+import com.unfbx.chatgpt.function.KeyStrategyFunction;
+import com.unfbx.chatgpt.interceptor.DefaultOpenAiAuthInterceptor;
+import com.unfbx.chatgpt.interceptor.OpenAiAuthInterceptor;
+import jnpf.constants.AiConstants;
+import jnpf.service.OpenAiService;
+import jnpf.service.impl.DefaultOpenAiServiceImpl;
+import jnpf.service.impl.DisabledOpenAiServiceImpl;
+import jnpf.util.AiLimitUtil;
+import jnpf.util.StringUtil;
+import okhttp3.Authenticator;
+import okhttp3.OkHttpClient;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.net.InetSocketAddress;
+import java.net.PasswordAuthentication;
+import java.net.Proxy;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * AI客户端 自动配置
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ * @date 2024/10/9 14:03
+ */
+@Configuration(proxyBeanMethods = false)
+public class AiAutoConfiguration {
+
+    @Bean
+    @ConditionalOnMissingBean
+    @ConditionalOnProperty(prefix = AiConstants.CONFIGURATION_PREFIX, name = "enabled", havingValue = "false", matchIfMissing = true)
+    public OpenAiService getDisabledOpenAiService() {
+        return new DisabledOpenAiServiceImpl();
+    }
+
+    @Configuration(proxyBeanMethods = false)
+    @ConditionalOnProperty(prefix = AiConstants.CONFIGURATION_PREFIX, name = "enabled", havingValue = "true")
+    public static class AiEnabledConfiguration{
+
+
+        @Bean
+        @ConfigurationProperties(prefix = AiConstants.CONFIGURATION_PREFIX)
+        public AiProperties getAiProperties() {
+            return new AiProperties();
+        }
+
+        @Bean
+        public OpenAiService getDefaultOpenAiService(OpenAiClient openAiClient, AiProperties aiProperties) {
+            return new DefaultOpenAiServiceImpl(openAiClient, aiProperties);
+        }
+
+        @Bean
+        public AiLimitUtil getAiLimitUtil(AiProperties aiProperties) {
+            return new AiLimitUtil(aiProperties);
+        }
+
+        @Bean
+        @ConditionalOnMissingBean
+        public OpenAiClient getOpenAiClient(@Qualifier(AiConstants.DEFAULT_HTTP_CLIENT_BEAN_NAME) OkHttpClient okHttpClient
+                , KeyStrategyFunction keyStrategyFunction, OpenAiAuthInterceptor authInterceptor, AiProperties aiProperties) {
+            String apiHost = aiProperties.getApiHost();
+            // 需要以 / 结尾
+            if(!apiHost.isEmpty() && !apiHost.endsWith("/")){
+                apiHost +="/";
+            }
+            // 构造openAiClient
+            return OpenAiClient.builder()
+                    .apiHost(apiHost)
+                    .apiKey(aiProperties.getApiKey())
+                    .keyStrategy(keyStrategyFunction)
+                    .authInterceptor(authInterceptor)
+                    .okHttpClient(okHttpClient)
+                    .build();
+        }
+
+    /*@Bean
+    @ConditionalOnMissingBean
+    public OpenAiStreamClient getOpenAiStreamClient(@Qualifier(AiConstants.DEFAULT_HTTP_CLIENT_BEAN_NAME) OkHttpClient okHttpClient
+            , KeyStrategyFunction keyStrategyFunction, OpenAiAuthInterceptor authInterceptor, AiProperties aiProperties) {
+        String apiHost = aiProperties.getApiHost();
+        // 需要以 / 结尾
+        if(!apiHost.isEmpty() && !apiHost.endsWith("/")){
+            apiHost +="/";
+        }
+        // 构造openAiClient
+        return OpenAiStreamClient.builder()
+                .apiHost(apiHost)
+                .apiKey(aiProperties.getApiKey())
+                .keyStrategy(keyStrategyFunction)
+                .authInterceptor(authInterceptor)
+                .okHttpClient(okHttpClient)
+                .build();
+    }*/
+
+
+        /**
+         * 多Key选择策略
+         */
+        @Bean
+        @ConditionalOnMissingBean
+        public KeyStrategyFunction getDefaultOpenAiKeyStrategyFunction() {
+            return new KeyRandomStrategy();
+        }
+
+        /**
+         * 认证拦截器
+         * @see com.unfbx.chatgpt.interceptor.DynamicKeyOpenAiAuthInterceptor 动态移除无效Key
+         */
+        @Bean
+        @ConditionalOnMissingBean
+        public OpenAiAuthInterceptor getDefaultOpenAiAuthInterceptor() {
+            return new DefaultOpenAiAuthInterceptor();
+        }
+
+        /**
+         * 默认okhttpclient
+         */
+        @Bean(name = AiConstants.DEFAULT_HTTP_CLIENT_BEAN_NAME)
+        @ConditionalOnMissingBean(name = AiConstants.DEFAULT_HTTP_CLIENT_BEAN_NAME)
+        public OkHttpClient getDefaultOpenAiOkHttpClient(AiProperties aiProperties) {
+            OkHttpClient.Builder builder = new OkHttpClient
+                    .Builder();
+
+            if(aiProperties.getProxy() != null
+                    && StringUtil.isNotEmpty(aiProperties.getProxy().getHost()) && aiProperties.getProxy().getPort() != null){
+                // 设置代理
+                builder.proxy(new Proxy(aiProperties.getProxy().getType(), new InetSocketAddress(aiProperties.getProxy().getHost(), aiProperties.getProxy().getPort())));
+                // 设置代理认证
+                if(StringUtil.isNotEmpty(aiProperties.getProxy().getUsername()) && StringUtil.isNotEmpty(aiProperties.getProxy().getPassword())){
+                    builder.proxyAuthenticator(Authenticator.JAVA_NET_AUTHENTICATOR);
+                    java.net.Authenticator.setDefault(new java.net.Authenticator() {
+                        @Override
+                        protected PasswordAuthentication getPasswordAuthentication() {
+                            // 返回代理的用户名和密码
+                            return new PasswordAuthentication(aiProperties.getProxy().getUsername(), aiProperties.getProxy().getPassword().toCharArray());
+                        }
+                    });
+                }
+            }
+        /*try {
+            // 创建一个信任所有证书的 TrustManager
+            final TrustManager[] trustAllCerts = new TrustManager[]{
+                    new X509TrustManager() {
+                        @Override
+                        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+                        }
+
+                        @Override
+                        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+                        }
+
+                        @Override
+                        public X509Certificate[] getAcceptedIssuers() {
+                            return new X509Certificate[]{};
+                        }
+                    }
+            };
+
+            // 安装所有信任管理器
+            final SSLContext sslContext = SSLContext.getInstance("SSL");
+            sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
+
+            // 创建 OkHttpClient 并配置 SSL socket factory 和 hostname verifier
+            final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
+
+            // 信任所有证书
+            builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]);
+            builder.hostnameVerifier((hostname, session) -> true);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }*/
+
+            return builder
+                    .callTimeout(aiProperties.getTimeout(), TimeUnit.SECONDS)
+                    .connectTimeout(aiProperties.getTimeout(), TimeUnit.SECONDS)
+                    .writeTimeout(aiProperties.getTimeout(), TimeUnit.SECONDS)
+                    .readTimeout(aiProperties.getTimeout(), TimeUnit.SECONDS)
+                    .build();
+        }
+
+
+    }
+
+}

+ 134 - 0
jnpf-boot-common/jnpf-common-ai/src/main/java/jnpf/config/AiProperties.java

@@ -0,0 +1,134 @@
+package jnpf.config;
+
+import jnpf.constants.AiConstants;
+import lombok.Data;
+
+import java.time.Duration;
+import java.util.List;
+
+/**
+ * AI 配置
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ * @date 2024/10/9 14:03
+ */
+@Data
+public class AiProperties {
+
+    /**
+     * 是否启用
+     */
+    private boolean enabled;
+
+    /**
+     * openai服务器
+     */
+    private String apiHost = AiConstants.OPENAI_HOST;
+
+    /**
+     * openai key
+     */
+    private List<String> apiKey;
+
+    /**
+     * 超时时间
+     */
+    private Long timeout = 30L;
+
+    /**
+     * 每个用户限制时间内的请求次数
+     */
+    private Integer userLimitCount = 1;
+
+    /**
+     * 每个用户限制时间频率
+     */
+    private Duration userLimitTime = Duration.ofSeconds(3);
+
+    /**
+     * 全部请求限制时间内的请求次数
+     */
+    private Integer totalLimitCount = 500;
+
+    /**
+     * 全部请求限制时间频率
+     */
+    private Duration totalLimitTime = Duration.ofMinutes(1L);
+
+    /**
+     * 代理配置
+     */
+    private Proxy proxy = new Proxy();
+
+    /**
+     * 对话配置
+     */
+    private ChatOption chat = new ChatOption();
+
+
+
+
+    @Data
+    public class ChatOption{
+
+        /**
+         * @see AiConstants.Model
+         */
+        private String mode = AiConstants.Model.QWEN_25_3;
+
+        /**
+         * 设置seed参数会使文本生成过程更具有确定性,通常用于使模型每次运行的结果一致。
+         * 在每次模型调用时传入相同的seed值(由您指定),并保持其他参数不变,模型将很可能返回相同的结果。
+         */
+        private Integer seed = 1234;
+
+        /**
+         * 允许模型生成的最大Token数。
+         */
+        private Integer maxTokens = 1500;
+
+        /**
+         * 核采样的概率阈值,用于控制模型生成文本的多样性。
+         * top_p越高,生成的文本更多样。反之,生成的文本更确定。
+         * 由于temperature与top_p均可以控制生成文本的多样性,因此建议您只设置其中一个值。
+         */
+        private Double topP = 0.8;
+
+        /**
+         * 采样温度,用于控制模型生成文本的多样性。
+         * temperature越高,生成的文本更多样,反之,生成的文本更确定。
+         * 由于temperature与top_p均可以控制生成文本的多样性,因此建议您只设置其中一个值。
+         */
+        private Double temperature = 0.85;
+
+        private boolean enableSearch = true;
+
+    }
+
+
+    @Data
+    public class Proxy{
+        /**
+         * HTTP, SOCKS
+         */
+        private java.net.Proxy.Type type = java.net.Proxy.Type.HTTP;
+        /**
+         * 代理域名
+         */
+        private String host;
+        /**
+         * 代理端口
+         */
+        private Integer port;
+        /**
+         * 代理用户名
+         */
+        private String username;
+        /**
+         * 代理密码
+         */
+        private String password;
+    }
+
+}

+ 55 - 0
jnpf-boot-common/jnpf-common-ai/src/main/java/jnpf/constants/AiConstants.java

@@ -0,0 +1,55 @@
+package jnpf.constants;
+
+/**
+ * AI 常量
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ * @date 2024/10/9 14:05
+ */
+public class AiConstants {
+
+    public static final String OPENAI_HOST = "https://dashscope.aliyuncs.com/compatible-mode/";
+
+    public static final String CONFIGURATION_PREFIX = "spring.cloud.ai.openai";
+
+    public static final String DEFAULT_HTTP_CLIENT_BEAN_NAME = "defaultOpenAiHttpClient";
+
+    public static final String GEN_MODEL_COMPNENT = "- input - textarea - inputNumber - switch - radio - checkbox - select - datePicker - timePicker - uploadFile - uploadImg - colorPicker - rate - slider - editor - depSelect - posSelect - userSelect - roleSelect - areaSelect - signature - sign - location";
+
+    public static final String GEN_MODEL_QUETION = "根据当前业务需求,设计相应的表单结构。请仅返回JSON数据,不包含其他任何形式的内容。预期结果是一个JSON数组,因涉及不同表单需求,故可能包含多个表单对象。请确保命名规避数据库与编程保留字。\n" +
+            "所需表单应充分利用以下组件列表进行设计: " + GEN_MODEL_COMPNENT + "。\n" +
+            "参考给定的JSON格式,属性包含:中文名(tableTitle)、英文名(tableName)、字段列表(fields);字段列表是一个json数组,包含字段英文名(fieldName)、字段中文名(fieldTitle)等;" +
+            "创建表单结构,示例如下: [ { \"tableTitle\": \"商城订单\", \"tableName\": \"online_order_form\", \"fields\": [ {\"fieldTitle\": \"订单编号\", \"fieldName\": \"order_id\", \"fieldDbType\": \"varchar\", \"fieldComponent\": \"input\"}, {\"fieldTitle\": \"订单状态\", \"fieldName\": \"order_status\", \"fieldDbType\": \"int\", \"fieldComponent\": \"radio\", \"fieldOptions\":[{\"id\":\"1\", \"fullName\":\"未付款\"},{\"id\":\"2\", \"fullName\":\"已付款\"}]}] }, { \"tableTitle\": \"订单商品明细\", \"tableName\": \"order_item_details\", \"fields\": [ {\"fieldTitle\": \"订单ID(外键)\", \"fieldName\": \"order_id_fk\", \"fieldDbType\": \"varchar\", \"fieldComponent\": \"input\"}, {\"fieldTitle\": \"商品名称\", \"fieldName\": \"product_name\", \"fieldDbType\": \"varchar\", \"fieldComponent\": \"input\"}, {\"fieldTitle\": \"商品数量\", \"fieldName\": \"quantity\", \"fieldDbType\": \"int\", \"fieldComponent\": \"inputNumber\"}] } ]\n" +
+            "请依据实际业务逻辑,合理选择组件与字段类型,确保设计的表单既能满足数据收集需求,又便于用户操作。";
+
+    public static final String CHAT_PRE_QUETION = "引迈信息技术有限公司下的低代码产品JNPF介绍";
+
+
+
+    /**
+     * 模型名称
+     * @see com.unfbx.chatgpt.entity.chat.ChatCompletion.Model
+     * <a href="https://help.aliyun.com/zh/model-studio/getting-started/models">阿里官方稳定模型列表</a>
+     */
+    public static class Model{
+
+        /**
+         * 通义千问系列效果最好的模型,适合复杂、多步骤的任务。
+         */
+        public static final String QWEN_MAX = "qwen-max";
+        /**
+         * 通义千问系列速度最快、成本很低的模型,适合简单任务。
+         */
+        public static final String QWEN_TURBO = "qwen-turbo";
+        /**
+         * 通义千问开源版, 可部署参数最高的版本, 云版本收费
+         */
+        public static final String QWEN_25_72 = "qwen2.5-72b-instruct";
+        /**
+         * 通义千问开源版, 官方提供接口免费版本, 云版本限时免费
+         */
+        public static final String QWEN_25_3 = "qwen2.5-3b-instruct";
+
+    }
+}

+ 42 - 0
jnpf-boot-common/jnpf-common-ai/src/main/java/jnpf/service/OpenAiService.java

@@ -0,0 +1,42 @@
+package jnpf.service;
+
+import com.unfbx.chatgpt.entity.chat.Message;
+import jnpf.model.ai.AiFormModel;
+
+import java.util.List;
+
+
+/**
+ * AI服务工具
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ * @date 2024/10/9 14:38
+ */
+public interface OpenAiService {
+
+    /**
+     * 简单对话
+     * @param prompt
+     */
+    String completion(String prompt);
+
+    /**
+     * 连续对话
+     * @param messages 历史对话内容
+     */
+    String completion(Message... messages);
+
+    /**
+     * 生成表单
+     * @param businessName 业务名称
+     * @return
+     */
+    String generatorModelStr(String businessName);
+
+    /**
+     * 生成表单
+     * @param prompt
+     * @return
+     */
+    List<AiFormModel> generatorModelVO(String prompt);
+}

+ 101 - 0
jnpf-boot-common/jnpf-common-ai/src/main/java/jnpf/service/impl/DefaultOpenAiServiceImpl.java

@@ -0,0 +1,101 @@
+package jnpf.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.TypeReference;
+import com.unfbx.chatgpt.OpenAiClient;
+import com.unfbx.chatgpt.entity.chat.ChatCompletion;
+import com.unfbx.chatgpt.entity.chat.ChatCompletionResponse;
+import com.unfbx.chatgpt.entity.chat.Message;
+import jnpf.config.AiProperties;
+import jnpf.constant.MsgCode;
+import jnpf.constants.AiConstants;
+import jnpf.exception.DataException;
+import jnpf.model.ai.AiFormModel;
+import jnpf.service.OpenAiService;
+import jnpf.util.StringUtil;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * OpenAi 实现类
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ * @date 2024/10/9 14:39
+ */
+@Slf4j
+@AllArgsConstructor
+public class DefaultOpenAiServiceImpl implements OpenAiService {
+
+    private OpenAiClient openAiClient;
+    private AiProperties aiProperties;
+
+    @Override
+    public String completion(String prompt) {
+        Message sendMessage = Message.builder().role(Message.Role.USER).content(prompt).build();
+        ChatCompletion chatCompletion;
+        if (StringUtil.isNotEmpty(prompt) && prompt.toLowerCase().contains("jnpf")) {
+            Message sysMessage = Message.builder().role(Message.Role.SYSTEM).content(AiConstants.CHAT_PRE_QUETION).build();
+            chatCompletion = getDefaultChatComletion(sysMessage, sendMessage);
+        } else {
+            chatCompletion = getDefaultChatComletion(sendMessage);
+        }
+        ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(chatCompletion);
+        return chatCompletionResponse.getChoices().stream().map(chatChoice -> chatChoice.getMessage().getContent()).collect(Collectors.joining());
+    }
+
+    @Override
+    public String generatorModelStr(String businessName) {
+        Message sysMessage = Message.builder().role(Message.Role.SYSTEM).content(AiConstants.GEN_MODEL_QUETION).build();
+        String userTemplate = "当前业务需求是:";
+        Message sendMessage = Message.builder().role(Message.Role.USER).content(userTemplate + businessName).build();
+
+        ChatCompletion chatCompletion = getDefaultChatComletion(sysMessage, sendMessage);
+        ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(chatCompletion);
+        return chatCompletionResponse.getChoices().stream().map(chatChoice -> chatChoice.getMessage().getContent()).collect(Collectors.joining());
+    }
+
+    @Override
+    public List<AiFormModel> generatorModelVO(String prompt) {
+        String result = "";
+        List<AiFormModel> aiFormModels;
+        try {
+            result = generatorModelStr(prompt);
+            int startIndex = result.indexOf("[");
+            int endIndex = result.lastIndexOf("]");
+            if (startIndex != -1 && endIndex != -1 && startIndex < endIndex) {
+                result = result.substring(startIndex, endIndex + 1).trim();
+            }
+            aiFormModels = JSON.parseObject(result, new TypeReference<List<AiFormModel>>() {
+            });
+        } catch (Exception e) {
+            log.error("AI表单生成转换失败: {}, {}", result, e.getMessage());
+            throw new DataException(MsgCode.SYS181.get());
+        }
+        return aiFormModels;
+    }
+
+    @Override
+    public String completion(Message... messages) {
+        ChatCompletion chatCompletion = getDefaultChatComletion(messages);
+        ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(chatCompletion);
+        return chatCompletionResponse.getChoices().stream().map(chatChoice -> chatChoice.getMessage().getContent()).collect(Collectors.joining());
+    }
+
+    private ChatCompletion getDefaultChatComletion(Message... messages) {
+        AiProperties.ChatOption chatOption = aiProperties.getChat();
+        return ChatCompletion.builder()
+                .model(chatOption.getMode())
+                .temperature(chatOption.getTemperature())
+                .topP(chatOption.getTopP())
+                .seed(chatOption.getSeed())
+                .maxTokens(chatOption.getMaxTokens())
+                .messages(Arrays.asList(messages)).build();
+    }
+
+
+}

+ 41 - 0
jnpf-boot-common/jnpf-common-ai/src/main/java/jnpf/service/impl/DisabledOpenAiServiceImpl.java

@@ -0,0 +1,41 @@
+package jnpf.service.impl;
+
+import com.unfbx.chatgpt.entity.chat.Message;
+import jnpf.constant.MsgCode;
+import jnpf.exception.DataException;
+import jnpf.model.ai.AiFormModel;
+import jnpf.service.OpenAiService;
+
+import java.util.List;
+
+/**
+ * 未启用是空实现
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ * @date 2024/10/15 17:00
+ */
+public class DisabledOpenAiServiceImpl implements OpenAiService {
+    @Override
+    public String completion(String prompt) {
+        return throwError();
+    }
+
+    @Override
+    public String completion(Message... messages) {
+        return throwError();
+    }
+
+    @Override
+    public String generatorModelStr(String businessName) {
+        return throwError();
+    }
+
+    @Override
+    public List<AiFormModel> generatorModelVO(String prompt) {
+        return throwError();
+    }
+
+    public <T> T throwError(){
+        throw new DataException(MsgCode.SYS180.get());
+    }
+}

+ 54 - 0
jnpf-boot-common/jnpf-common-ai/src/main/java/jnpf/util/AiLimitUtil.java

@@ -0,0 +1,54 @@
+package jnpf.util;
+
+import cn.hutool.cache.CacheUtil;
+import cn.hutool.cache.impl.TimedCache;
+import jnpf.config.AiProperties;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+/**
+ * AI接口限流
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ * @date 2025/3/7 10:00
+ */
+public class AiLimitUtil {
+
+    private static AiProperties aiProperties;
+
+    private static TimedCache<String, AtomicInteger> limit = null;
+
+    private static final String TOTAL_KEY = "ai_limit_total";
+
+    public AiLimitUtil(AiProperties aiProperties) {
+        AiLimitUtil.aiProperties = aiProperties;
+        if (limit == null) {
+            limit = CacheUtil.newTimedCache(aiProperties.getUserLimitTime().toMillis());
+            // 一分钟清理一次无用数据
+            limit.schedulePrune(1000L * 60);
+        }
+    }
+
+    /**
+     * 是否可以请求
+     * @param userId
+     * @return
+     */
+    public static boolean tryAcquire(String userId) {
+        if(limit == null) {
+            return true;
+        }
+        if (StringUtil.isNotEmpty(userId)) {
+            // 按用户限制
+            AtomicInteger userCount = limit.get(userId, false, AtomicInteger::new);
+            if (userCount.incrementAndGet() > aiProperties.getUserLimitCount()) {
+                return false;
+            }
+        }
+        // 所有请求限制
+        AtomicInteger totalCount = limit.get(TOTAL_KEY, false, aiProperties.getTotalLimitTime().toMillis(), AtomicInteger::new);
+        return totalCount.incrementAndGet() <= aiProperties.getTotalLimitCount();
+    }
+}

+ 153 - 0
jnpf-boot-common/jnpf-common-ai/target/classes/META-INF/spring-configuration-metadata.json

@@ -0,0 +1,153 @@
+{
+  "groups": [
+    {
+      "name": "spring.cloud.ai.openai",
+      "type": "jnpf.config.AiProperties",
+      "sourceType": "jnpf.config.AiAutoConfiguration$AiEnabledConfiguration",
+      "sourceMethod": "getAiProperties()"
+    },
+    {
+      "name": "spring.cloud.ai.openai.chat",
+      "type": "jnpf.config.AiProperties$ChatOption",
+      "sourceType": "jnpf.config.AiProperties",
+      "sourceMethod": "getChat()"
+    },
+    {
+      "name": "spring.cloud.ai.openai.proxy",
+      "type": "jnpf.config.AiProperties$Proxy",
+      "sourceType": "jnpf.config.AiProperties",
+      "sourceMethod": "getProxy()"
+    }
+  ],
+  "properties": [
+    {
+      "name": "spring.cloud.ai.openai.api-host",
+      "type": "java.lang.String",
+      "description": "openai服务器",
+      "sourceType": "jnpf.config.AiProperties"
+    },
+    {
+      "name": "spring.cloud.ai.openai.api-key",
+      "type": "java.util.List<java.lang.String>",
+      "description": "openai key",
+      "sourceType": "jnpf.config.AiProperties"
+    },
+    {
+      "name": "spring.cloud.ai.openai.chat.enable-search",
+      "type": "java.lang.Boolean",
+      "sourceType": "jnpf.config.AiProperties$ChatOption",
+      "defaultValue": true
+    },
+    {
+      "name": "spring.cloud.ai.openai.chat.max-tokens",
+      "type": "java.lang.Integer",
+      "description": "允许模型生成的最大Token数。",
+      "sourceType": "jnpf.config.AiProperties$ChatOption",
+      "defaultValue": 1500
+    },
+    {
+      "name": "spring.cloud.ai.openai.chat.mode",
+      "type": "java.lang.String",
+      "description": "@see AiConstants.Model",
+      "sourceType": "jnpf.config.AiProperties$ChatOption"
+    },
+    {
+      "name": "spring.cloud.ai.openai.chat.seed",
+      "type": "java.lang.Integer",
+      "description": "设置seed参数会使文本生成过程更具有确定性,通常用于使模型每次运行的结果一致。 在每次模型调用时传入相同的seed值(由您指定),并保持其他参数不变,模型将很可能返回相同的结果。",
+      "sourceType": "jnpf.config.AiProperties$ChatOption",
+      "defaultValue": 1234
+    },
+    {
+      "name": "spring.cloud.ai.openai.chat.temperature",
+      "type": "java.lang.Double",
+      "description": "采样温度,用于控制模型生成文本的多样性。 temperature越高,生成的文本更多样,反之,生成的文本更确定。 由于temperature与top_p均可以控制生成文本的多样性,因此建议您只设置其中一个值。",
+      "sourceType": "jnpf.config.AiProperties$ChatOption",
+      "defaultValue": 0.85
+    },
+    {
+      "name": "spring.cloud.ai.openai.chat.top-p",
+      "type": "java.lang.Double",
+      "description": "核采样的概率阈值,用于控制模型生成文本的多样性。 top_p越高,生成的文本更多样。反之,生成的文本更确定。 由于temperature与top_p均可以控制生成文本的多样性,因此建议您只设置其中一个值。",
+      "sourceType": "jnpf.config.AiProperties$ChatOption",
+      "defaultValue": 0.8
+    },
+    {
+      "name": "spring.cloud.ai.openai.enabled",
+      "type": "java.lang.Boolean",
+      "description": "是否启用",
+      "sourceType": "jnpf.config.AiProperties",
+      "defaultValue": false
+    },
+    {
+      "name": "spring.cloud.ai.openai.proxy.host",
+      "type": "java.lang.String",
+      "description": "代理域名",
+      "sourceType": "jnpf.config.AiProperties$Proxy"
+    },
+    {
+      "name": "spring.cloud.ai.openai.proxy.password",
+      "type": "java.lang.String",
+      "description": "代理密码",
+      "sourceType": "jnpf.config.AiProperties$Proxy"
+    },
+    {
+      "name": "spring.cloud.ai.openai.proxy.port",
+      "type": "java.lang.Integer",
+      "description": "代理端口",
+      "sourceType": "jnpf.config.AiProperties$Proxy"
+    },
+    {
+      "name": "spring.cloud.ai.openai.proxy.type",
+      "type": "java.net.Proxy$Type",
+      "description": "HTTP, SOCKS",
+      "sourceType": "jnpf.config.AiProperties$Proxy",
+      "defaultValue": "http"
+    },
+    {
+      "name": "spring.cloud.ai.openai.proxy.username",
+      "type": "java.lang.String",
+      "description": "代理用户名",
+      "sourceType": "jnpf.config.AiProperties$Proxy"
+    },
+    {
+      "name": "spring.cloud.ai.openai.timeout",
+      "type": "java.lang.Long",
+      "description": "超时时间",
+      "sourceType": "jnpf.config.AiProperties",
+      "defaultValue": 30
+    },
+    {
+      "name": "spring.cloud.ai.openai.total-limit-count",
+      "type": "java.lang.Integer",
+      "description": "全部请求限制时间内的请求次数",
+      "sourceType": "jnpf.config.AiProperties",
+      "defaultValue": 500
+    },
+    {
+      "name": "spring.cloud.ai.openai.total-limit-time",
+      "type": "java.time.Duration",
+      "description": "全部请求限制时间频率",
+      "sourceType": "jnpf.config.AiProperties",
+      "defaultValue": "1m"
+    },
+    {
+      "name": "spring.cloud.ai.openai.user-limit-count",
+      "type": "java.lang.Integer",
+      "description": "每个用户限制时间内的请求次数",
+      "sourceType": "jnpf.config.AiProperties",
+      "defaultValue": 1
+    },
+    {
+      "name": "spring.cloud.ai.openai.user-limit-time",
+      "type": "java.time.Duration",
+      "description": "每个用户限制时间频率",
+      "sourceType": "jnpf.config.AiProperties",
+      "defaultValue": "3s"
+    }
+  ],
+  "hints": [],
+  "ignored": {
+    "properties": []
+  }
+}

BIN
jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/config/AiAutoConfiguration$AiEnabledConfiguration$1.class


BIN
jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/config/AiAutoConfiguration$AiEnabledConfiguration.class


BIN
jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/config/AiAutoConfiguration.class


+ 193 - 0
jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/config/AiAutoConfiguration.java

@@ -0,0 +1,193 @@
+package jnpf.config;
+
+import com.unfbx.chatgpt.OpenAiClient;
+import com.unfbx.chatgpt.function.KeyRandomStrategy;
+import com.unfbx.chatgpt.function.KeyStrategyFunction;
+import com.unfbx.chatgpt.interceptor.DefaultOpenAiAuthInterceptor;
+import com.unfbx.chatgpt.interceptor.OpenAiAuthInterceptor;
+import jnpf.constants.AiConstants;
+import jnpf.service.OpenAiService;
+import jnpf.service.impl.DefaultOpenAiServiceImpl;
+import jnpf.service.impl.DisabledOpenAiServiceImpl;
+import jnpf.util.AiLimitUtil;
+import jnpf.util.StringUtil;
+import okhttp3.Authenticator;
+import okhttp3.OkHttpClient;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.net.InetSocketAddress;
+import java.net.PasswordAuthentication;
+import java.net.Proxy;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * AI客户端 自动配置
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ * @date 2024/10/9 14:03
+ */
+@Configuration(proxyBeanMethods = false)
+public class AiAutoConfiguration {
+
+    @Bean
+    @ConditionalOnMissingBean
+    @ConditionalOnProperty(prefix = AiConstants.CONFIGURATION_PREFIX, name = "enabled", havingValue = "false", matchIfMissing = true)
+    public OpenAiService getDisabledOpenAiService() {
+        return new DisabledOpenAiServiceImpl();
+    }
+
+    @Configuration(proxyBeanMethods = false)
+    @ConditionalOnProperty(prefix = AiConstants.CONFIGURATION_PREFIX, name = "enabled", havingValue = "true")
+    public static class AiEnabledConfiguration{
+
+
+        @Bean
+        @ConfigurationProperties(prefix = AiConstants.CONFIGURATION_PREFIX)
+        public AiProperties getAiProperties() {
+            return new AiProperties();
+        }
+
+        @Bean
+        public OpenAiService getDefaultOpenAiService(OpenAiClient openAiClient, AiProperties aiProperties) {
+            return new DefaultOpenAiServiceImpl(openAiClient, aiProperties);
+        }
+
+        @Bean
+        public AiLimitUtil getAiLimitUtil(AiProperties aiProperties) {
+            return new AiLimitUtil(aiProperties);
+        }
+
+        @Bean
+        @ConditionalOnMissingBean
+        public OpenAiClient getOpenAiClient(@Qualifier(AiConstants.DEFAULT_HTTP_CLIENT_BEAN_NAME) OkHttpClient okHttpClient
+                , KeyStrategyFunction keyStrategyFunction, OpenAiAuthInterceptor authInterceptor, AiProperties aiProperties) {
+            String apiHost = aiProperties.getApiHost();
+            // 需要以 / 结尾
+            if(!apiHost.isEmpty() && !apiHost.endsWith("/")){
+                apiHost +="/";
+            }
+            // 构造openAiClient
+            return OpenAiClient.builder()
+                    .apiHost(apiHost)
+                    .apiKey(aiProperties.getApiKey())
+                    .keyStrategy(keyStrategyFunction)
+                    .authInterceptor(authInterceptor)
+                    .okHttpClient(okHttpClient)
+                    .build();
+        }
+
+    /*@Bean
+    @ConditionalOnMissingBean
+    public OpenAiStreamClient getOpenAiStreamClient(@Qualifier(AiConstants.DEFAULT_HTTP_CLIENT_BEAN_NAME) OkHttpClient okHttpClient
+            , KeyStrategyFunction keyStrategyFunction, OpenAiAuthInterceptor authInterceptor, AiProperties aiProperties) {
+        String apiHost = aiProperties.getApiHost();
+        // 需要以 / 结尾
+        if(!apiHost.isEmpty() && !apiHost.endsWith("/")){
+            apiHost +="/";
+        }
+        // 构造openAiClient
+        return OpenAiStreamClient.builder()
+                .apiHost(apiHost)
+                .apiKey(aiProperties.getApiKey())
+                .keyStrategy(keyStrategyFunction)
+                .authInterceptor(authInterceptor)
+                .okHttpClient(okHttpClient)
+                .build();
+    }*/
+
+
+        /**
+         * 多Key选择策略
+         */
+        @Bean
+        @ConditionalOnMissingBean
+        public KeyStrategyFunction getDefaultOpenAiKeyStrategyFunction() {
+            return new KeyRandomStrategy();
+        }
+
+        /**
+         * 认证拦截器
+         * @see com.unfbx.chatgpt.interceptor.DynamicKeyOpenAiAuthInterceptor 动态移除无效Key
+         */
+        @Bean
+        @ConditionalOnMissingBean
+        public OpenAiAuthInterceptor getDefaultOpenAiAuthInterceptor() {
+            return new DefaultOpenAiAuthInterceptor();
+        }
+
+        /**
+         * 默认okhttpclient
+         */
+        @Bean(name = AiConstants.DEFAULT_HTTP_CLIENT_BEAN_NAME)
+        @ConditionalOnMissingBean(name = AiConstants.DEFAULT_HTTP_CLIENT_BEAN_NAME)
+        public OkHttpClient getDefaultOpenAiOkHttpClient(AiProperties aiProperties) {
+            OkHttpClient.Builder builder = new OkHttpClient
+                    .Builder();
+
+            if(aiProperties.getProxy() != null
+                    && StringUtil.isNotEmpty(aiProperties.getProxy().getHost()) && aiProperties.getProxy().getPort() != null){
+                // 设置代理
+                builder.proxy(new Proxy(aiProperties.getProxy().getType(), new InetSocketAddress(aiProperties.getProxy().getHost(), aiProperties.getProxy().getPort())));
+                // 设置代理认证
+                if(StringUtil.isNotEmpty(aiProperties.getProxy().getUsername()) && StringUtil.isNotEmpty(aiProperties.getProxy().getPassword())){
+                    builder.proxyAuthenticator(Authenticator.JAVA_NET_AUTHENTICATOR);
+                    java.net.Authenticator.setDefault(new java.net.Authenticator() {
+                        @Override
+                        protected PasswordAuthentication getPasswordAuthentication() {
+                            // 返回代理的用户名和密码
+                            return new PasswordAuthentication(aiProperties.getProxy().getUsername(), aiProperties.getProxy().getPassword().toCharArray());
+                        }
+                    });
+                }
+            }
+        /*try {
+            // 创建一个信任所有证书的 TrustManager
+            final TrustManager[] trustAllCerts = new TrustManager[]{
+                    new X509TrustManager() {
+                        @Override
+                        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+                        }
+
+                        @Override
+                        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+                        }
+
+                        @Override
+                        public X509Certificate[] getAcceptedIssuers() {
+                            return new X509Certificate[]{};
+                        }
+                    }
+            };
+
+            // 安装所有信任管理器
+            final SSLContext sslContext = SSLContext.getInstance("SSL");
+            sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
+
+            // 创建 OkHttpClient 并配置 SSL socket factory 和 hostname verifier
+            final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
+
+            // 信任所有证书
+            builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCerts[0]);
+            builder.hostnameVerifier((hostname, session) -> true);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }*/
+
+            return builder
+                    .callTimeout(aiProperties.getTimeout(), TimeUnit.SECONDS)
+                    .connectTimeout(aiProperties.getTimeout(), TimeUnit.SECONDS)
+                    .writeTimeout(aiProperties.getTimeout(), TimeUnit.SECONDS)
+                    .readTimeout(aiProperties.getTimeout(), TimeUnit.SECONDS)
+                    .build();
+        }
+
+
+    }
+
+}

BIN
jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/config/AiProperties$ChatOption.class


BIN
jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/config/AiProperties$Proxy.class


BIN
jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/config/AiProperties.class


+ 134 - 0
jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/config/AiProperties.java

@@ -0,0 +1,134 @@
+package jnpf.config;
+
+import jnpf.constants.AiConstants;
+import lombok.Data;
+
+import java.time.Duration;
+import java.util.List;
+
+/**
+ * AI 配置
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ * @date 2024/10/9 14:03
+ */
+@Data
+public class AiProperties {
+
+    /**
+     * 是否启用
+     */
+    private boolean enabled;
+
+    /**
+     * openai服务器
+     */
+    private String apiHost = AiConstants.OPENAI_HOST;
+
+    /**
+     * openai key
+     */
+    private List<String> apiKey;
+
+    /**
+     * 超时时间
+     */
+    private Long timeout = 30L;
+
+    /**
+     * 每个用户限制时间内的请求次数
+     */
+    private Integer userLimitCount = 1;
+
+    /**
+     * 每个用户限制时间频率
+     */
+    private Duration userLimitTime = Duration.ofSeconds(3);
+
+    /**
+     * 全部请求限制时间内的请求次数
+     */
+    private Integer totalLimitCount = 500;
+
+    /**
+     * 全部请求限制时间频率
+     */
+    private Duration totalLimitTime = Duration.ofMinutes(1L);
+
+    /**
+     * 代理配置
+     */
+    private Proxy proxy = new Proxy();
+
+    /**
+     * 对话配置
+     */
+    private ChatOption chat = new ChatOption();
+
+
+
+
+    @Data
+    public class ChatOption{
+
+        /**
+         * @see AiConstants.Model
+         */
+        private String mode = AiConstants.Model.QWEN_25_3;
+
+        /**
+         * 设置seed参数会使文本生成过程更具有确定性,通常用于使模型每次运行的结果一致。
+         * 在每次模型调用时传入相同的seed值(由您指定),并保持其他参数不变,模型将很可能返回相同的结果。
+         */
+        private Integer seed = 1234;
+
+        /**
+         * 允许模型生成的最大Token数。
+         */
+        private Integer maxTokens = 1500;
+
+        /**
+         * 核采样的概率阈值,用于控制模型生成文本的多样性。
+         * top_p越高,生成的文本更多样。反之,生成的文本更确定。
+         * 由于temperature与top_p均可以控制生成文本的多样性,因此建议您只设置其中一个值。
+         */
+        private Double topP = 0.8;
+
+        /**
+         * 采样温度,用于控制模型生成文本的多样性。
+         * temperature越高,生成的文本更多样,反之,生成的文本更确定。
+         * 由于temperature与top_p均可以控制生成文本的多样性,因此建议您只设置其中一个值。
+         */
+        private Double temperature = 0.85;
+
+        private boolean enableSearch = true;
+
+    }
+
+
+    @Data
+    public class Proxy{
+        /**
+         * HTTP, SOCKS
+         */
+        private java.net.Proxy.Type type = java.net.Proxy.Type.HTTP;
+        /**
+         * 代理域名
+         */
+        private String host;
+        /**
+         * 代理端口
+         */
+        private Integer port;
+        /**
+         * 代理用户名
+         */
+        private String username;
+        /**
+         * 代理密码
+         */
+        private String password;
+    }
+
+}

BIN
jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/constants/AiConstants$Model.class


BIN
jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/constants/AiConstants.class


+ 55 - 0
jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/constants/AiConstants.java

@@ -0,0 +1,55 @@
+package jnpf.constants;
+
+/**
+ * AI 常量
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ * @date 2024/10/9 14:05
+ */
+public class AiConstants {
+
+    public static final String OPENAI_HOST = "https://dashscope.aliyuncs.com/compatible-mode/";
+
+    public static final String CONFIGURATION_PREFIX = "spring.cloud.ai.openai";
+
+    public static final String DEFAULT_HTTP_CLIENT_BEAN_NAME = "defaultOpenAiHttpClient";
+
+    public static final String GEN_MODEL_COMPNENT = "- input - textarea - inputNumber - switch - radio - checkbox - select - datePicker - timePicker - uploadFile - uploadImg - colorPicker - rate - slider - editor - depSelect - posSelect - userSelect - roleSelect - areaSelect - signature - sign - location";
+
+    public static final String GEN_MODEL_QUETION = "根据当前业务需求,设计相应的表单结构。请仅返回JSON数据,不包含其他任何形式的内容。预期结果是一个JSON数组,因涉及不同表单需求,故可能包含多个表单对象。请确保命名规避数据库与编程保留字。\n" +
+            "所需表单应充分利用以下组件列表进行设计: " + GEN_MODEL_COMPNENT + "。\n" +
+            "参考给定的JSON格式,属性包含:中文名(tableTitle)、英文名(tableName)、字段列表(fields);字段列表是一个json数组,包含字段英文名(fieldName)、字段中文名(fieldTitle)等;" +
+            "创建表单结构,示例如下: [ { \"tableTitle\": \"商城订单\", \"tableName\": \"online_order_form\", \"fields\": [ {\"fieldTitle\": \"订单编号\", \"fieldName\": \"order_id\", \"fieldDbType\": \"varchar\", \"fieldComponent\": \"input\"}, {\"fieldTitle\": \"订单状态\", \"fieldName\": \"order_status\", \"fieldDbType\": \"int\", \"fieldComponent\": \"radio\", \"fieldOptions\":[{\"id\":\"1\", \"fullName\":\"未付款\"},{\"id\":\"2\", \"fullName\":\"已付款\"}]}] }, { \"tableTitle\": \"订单商品明细\", \"tableName\": \"order_item_details\", \"fields\": [ {\"fieldTitle\": \"订单ID(外键)\", \"fieldName\": \"order_id_fk\", \"fieldDbType\": \"varchar\", \"fieldComponent\": \"input\"}, {\"fieldTitle\": \"商品名称\", \"fieldName\": \"product_name\", \"fieldDbType\": \"varchar\", \"fieldComponent\": \"input\"}, {\"fieldTitle\": \"商品数量\", \"fieldName\": \"quantity\", \"fieldDbType\": \"int\", \"fieldComponent\": \"inputNumber\"}] } ]\n" +
+            "请依据实际业务逻辑,合理选择组件与字段类型,确保设计的表单既能满足数据收集需求,又便于用户操作。";
+
+    public static final String CHAT_PRE_QUETION = "引迈信息技术有限公司下的低代码产品JNPF介绍";
+
+
+
+    /**
+     * 模型名称
+     * @see com.unfbx.chatgpt.entity.chat.ChatCompletion.Model
+     * <a href="https://help.aliyun.com/zh/model-studio/getting-started/models">阿里官方稳定模型列表</a>
+     */
+    public static class Model{
+
+        /**
+         * 通义千问系列效果最好的模型,适合复杂、多步骤的任务。
+         */
+        public static final String QWEN_MAX = "qwen-max";
+        /**
+         * 通义千问系列速度最快、成本很低的模型,适合简单任务。
+         */
+        public static final String QWEN_TURBO = "qwen-turbo";
+        /**
+         * 通义千问开源版, 可部署参数最高的版本, 云版本收费
+         */
+        public static final String QWEN_25_72 = "qwen2.5-72b-instruct";
+        /**
+         * 通义千问开源版, 官方提供接口免费版本, 云版本限时免费
+         */
+        public static final String QWEN_25_3 = "qwen2.5-3b-instruct";
+
+    }
+}

BIN
jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/service/OpenAiService.class


+ 42 - 0
jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/service/OpenAiService.java

@@ -0,0 +1,42 @@
+package jnpf.service;
+
+import com.unfbx.chatgpt.entity.chat.Message;
+import jnpf.model.ai.AiFormModel;
+
+import java.util.List;
+
+
+/**
+ * AI服务工具
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ * @date 2024/10/9 14:38
+ */
+public interface OpenAiService {
+
+    /**
+     * 简单对话
+     * @param prompt
+     */
+    String completion(String prompt);
+
+    /**
+     * 连续对话
+     * @param messages 历史对话内容
+     */
+    String completion(Message... messages);
+
+    /**
+     * 生成表单
+     * @param businessName 业务名称
+     * @return
+     */
+    String generatorModelStr(String businessName);
+
+    /**
+     * 生成表单
+     * @param prompt
+     * @return
+     */
+    List<AiFormModel> generatorModelVO(String prompt);
+}

BIN
jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/service/impl/DefaultOpenAiServiceImpl$1.class


BIN
jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/service/impl/DefaultOpenAiServiceImpl.class


+ 101 - 0
jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/service/impl/DefaultOpenAiServiceImpl.java

@@ -0,0 +1,101 @@
+package jnpf.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.TypeReference;
+import com.unfbx.chatgpt.OpenAiClient;
+import com.unfbx.chatgpt.entity.chat.ChatCompletion;
+import com.unfbx.chatgpt.entity.chat.ChatCompletionResponse;
+import com.unfbx.chatgpt.entity.chat.Message;
+import jnpf.config.AiProperties;
+import jnpf.constant.MsgCode;
+import jnpf.constants.AiConstants;
+import jnpf.exception.DataException;
+import jnpf.model.ai.AiFormModel;
+import jnpf.service.OpenAiService;
+import jnpf.util.StringUtil;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * OpenAi 实现类
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ * @date 2024/10/9 14:39
+ */
+@Slf4j
+@AllArgsConstructor
+public class DefaultOpenAiServiceImpl implements OpenAiService {
+
+    private OpenAiClient openAiClient;
+    private AiProperties aiProperties;
+
+    @Override
+    public String completion(String prompt) {
+        Message sendMessage = Message.builder().role(Message.Role.USER).content(prompt).build();
+        ChatCompletion chatCompletion;
+        if (StringUtil.isNotEmpty(prompt) && prompt.toLowerCase().contains("jnpf")) {
+            Message sysMessage = Message.builder().role(Message.Role.SYSTEM).content(AiConstants.CHAT_PRE_QUETION).build();
+            chatCompletion = getDefaultChatComletion(sysMessage, sendMessage);
+        } else {
+            chatCompletion = getDefaultChatComletion(sendMessage);
+        }
+        ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(chatCompletion);
+        return chatCompletionResponse.getChoices().stream().map(chatChoice -> chatChoice.getMessage().getContent()).collect(Collectors.joining());
+    }
+
+    @Override
+    public String generatorModelStr(String businessName) {
+        Message sysMessage = Message.builder().role(Message.Role.SYSTEM).content(AiConstants.GEN_MODEL_QUETION).build();
+        String userTemplate = "当前业务需求是:";
+        Message sendMessage = Message.builder().role(Message.Role.USER).content(userTemplate + businessName).build();
+
+        ChatCompletion chatCompletion = getDefaultChatComletion(sysMessage, sendMessage);
+        ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(chatCompletion);
+        return chatCompletionResponse.getChoices().stream().map(chatChoice -> chatChoice.getMessage().getContent()).collect(Collectors.joining());
+    }
+
+    @Override
+    public List<AiFormModel> generatorModelVO(String prompt) {
+        String result = "";
+        List<AiFormModel> aiFormModels;
+        try {
+            result = generatorModelStr(prompt);
+            int startIndex = result.indexOf("[");
+            int endIndex = result.lastIndexOf("]");
+            if (startIndex != -1 && endIndex != -1 && startIndex < endIndex) {
+                result = result.substring(startIndex, endIndex + 1).trim();
+            }
+            aiFormModels = JSON.parseObject(result, new TypeReference<List<AiFormModel>>() {
+            });
+        } catch (Exception e) {
+            log.error("AI表单生成转换失败: {}, {}", result, e.getMessage());
+            throw new DataException(MsgCode.SYS181.get());
+        }
+        return aiFormModels;
+    }
+
+    @Override
+    public String completion(Message... messages) {
+        ChatCompletion chatCompletion = getDefaultChatComletion(messages);
+        ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(chatCompletion);
+        return chatCompletionResponse.getChoices().stream().map(chatChoice -> chatChoice.getMessage().getContent()).collect(Collectors.joining());
+    }
+
+    private ChatCompletion getDefaultChatComletion(Message... messages) {
+        AiProperties.ChatOption chatOption = aiProperties.getChat();
+        return ChatCompletion.builder()
+                .model(chatOption.getMode())
+                .temperature(chatOption.getTemperature())
+                .topP(chatOption.getTopP())
+                .seed(chatOption.getSeed())
+                .maxTokens(chatOption.getMaxTokens())
+                .messages(Arrays.asList(messages)).build();
+    }
+
+
+}

BIN
jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/service/impl/DisabledOpenAiServiceImpl.class


+ 41 - 0
jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/service/impl/DisabledOpenAiServiceImpl.java

@@ -0,0 +1,41 @@
+package jnpf.service.impl;
+
+import com.unfbx.chatgpt.entity.chat.Message;
+import jnpf.constant.MsgCode;
+import jnpf.exception.DataException;
+import jnpf.model.ai.AiFormModel;
+import jnpf.service.OpenAiService;
+
+import java.util.List;
+
+/**
+ * 未启用是空实现
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ * @date 2024/10/15 17:00
+ */
+public class DisabledOpenAiServiceImpl implements OpenAiService {
+    @Override
+    public String completion(String prompt) {
+        return throwError();
+    }
+
+    @Override
+    public String completion(Message... messages) {
+        return throwError();
+    }
+
+    @Override
+    public String generatorModelStr(String businessName) {
+        return throwError();
+    }
+
+    @Override
+    public List<AiFormModel> generatorModelVO(String prompt) {
+        return throwError();
+    }
+
+    public <T> T throwError(){
+        throw new DataException(MsgCode.SYS180.get());
+    }
+}

BIN
jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/util/AiLimitUtil.class


+ 54 - 0
jnpf-boot-common/jnpf-common-ai/target/classes/jnpf/util/AiLimitUtil.java

@@ -0,0 +1,54 @@
+package jnpf.util;
+
+import cn.hutool.cache.CacheUtil;
+import cn.hutool.cache.impl.TimedCache;
+import jnpf.config.AiProperties;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+/**
+ * AI接口限流
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ * @date 2025/3/7 10:00
+ */
+public class AiLimitUtil {
+
+    private static AiProperties aiProperties;
+
+    private static TimedCache<String, AtomicInteger> limit = null;
+
+    private static final String TOTAL_KEY = "ai_limit_total";
+
+    public AiLimitUtil(AiProperties aiProperties) {
+        AiLimitUtil.aiProperties = aiProperties;
+        if (limit == null) {
+            limit = CacheUtil.newTimedCache(aiProperties.getUserLimitTime().toMillis());
+            // 一分钟清理一次无用数据
+            limit.schedulePrune(1000L * 60);
+        }
+    }
+
+    /**
+     * 是否可以请求
+     * @param userId
+     * @return
+     */
+    public static boolean tryAcquire(String userId) {
+        if(limit == null) {
+            return true;
+        }
+        if (StringUtil.isNotEmpty(userId)) {
+            // 按用户限制
+            AtomicInteger userCount = limit.get(userId, false, AtomicInteger::new);
+            if (userCount.incrementAndGet() > aiProperties.getUserLimitCount()) {
+                return false;
+            }
+        }
+        // 所有请求限制
+        AtomicInteger totalCount = limit.get(TOTAL_KEY, false, aiProperties.getTotalLimitTime().toMillis(), AtomicInteger::new);
+        return totalCount.incrementAndGet() <= aiProperties.getTotalLimitCount();
+    }
+}

BIN
jnpf-boot-common/jnpf-common-ai/target/jnpf-common-ai-6.0.0-RELEASE.jar


+ 3 - 0
jnpf-boot-common/jnpf-common-ai/target/maven-archiver/pom.properties

@@ -0,0 +1,3 @@
+artifactId=jnpf-common-ai
+groupId=com.jnpf
+version=6.0.0-RELEASE

+ 14 - 0
jnpf-boot-common/jnpf-common-ai/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst

@@ -0,0 +1,14 @@
+jnpf\config\AiProperties$Proxy.class
+jnpf\config\AiAutoConfiguration$AiEnabledConfiguration$1.class
+jnpf\constants\AiConstants$Model.class
+jnpf\config\AiAutoConfiguration.class
+jnpf\util\AiLimitUtil.class
+jnpf\service\impl\DisabledOpenAiServiceImpl.class
+jnpf\config\AiAutoConfiguration$AiEnabledConfiguration.class
+jnpf\constants\AiConstants.class
+META-INF\spring-configuration-metadata.json
+jnpf\service\impl\DefaultOpenAiServiceImpl.class
+jnpf\config\AiProperties$ChatOption.class
+jnpf\service\impl\DefaultOpenAiServiceImpl$1.class
+jnpf\service\OpenAiService.class
+jnpf\config\AiProperties.class

+ 7 - 0
jnpf-boot-common/jnpf-common-ai/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst

@@ -0,0 +1,7 @@
+C:\Users\zhaojinyu\Desktop\USKY\jnpf6.0\jnpf-common-v6x\jnpf-boot-common\jnpf-common-ai\src\main\java\jnpf\config\AiAutoConfiguration.java
+C:\Users\zhaojinyu\Desktop\USKY\jnpf6.0\jnpf-common-v6x\jnpf-boot-common\jnpf-common-ai\src\main\java\jnpf\config\AiProperties.java
+C:\Users\zhaojinyu\Desktop\USKY\jnpf6.0\jnpf-common-v6x\jnpf-boot-common\jnpf-common-ai\src\main\java\jnpf\constants\AiConstants.java
+C:\Users\zhaojinyu\Desktop\USKY\jnpf6.0\jnpf-common-v6x\jnpf-boot-common\jnpf-common-ai\src\main\java\jnpf\service\impl\DefaultOpenAiServiceImpl.java
+C:\Users\zhaojinyu\Desktop\USKY\jnpf6.0\jnpf-common-v6x\jnpf-boot-common\jnpf-common-ai\src\main\java\jnpf\service\impl\DisabledOpenAiServiceImpl.java
+C:\Users\zhaojinyu\Desktop\USKY\jnpf6.0\jnpf-common-v6x\jnpf-boot-common\jnpf-common-ai\src\main\java\jnpf\service\OpenAiService.java
+C:\Users\zhaojinyu\Desktop\USKY\jnpf6.0\jnpf-common-v6x\jnpf-boot-common\jnpf-common-ai\src\main\java\jnpf\util\AiLimitUtil.java

+ 71 - 0
jnpf-boot-common/jnpf-common-auth/pom.xml

@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>jnpf-boot-common</artifactId>
+        <groupId>com.jnpf</groupId>
+        <version>6.0.0-RELEASE</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>jnpf-common-auth</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.jnpf</groupId>
+            <artifactId>jnpf-common-redis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-jwt</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>cn.hutool</groupId>
+                    <artifactId>hutool-jwt</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-redis-jackson</artifactId>
+            <!--<exclusions>
+                <exclusion>
+                    <artifactId>spring-boot-starter-data-redis</artifactId>
+                    <groupId>org.springframework.boot</groupId>
+                </exclusion>
+            </exclusions>-->
+        </dependency>
+    </dependencies>
+
+
+    <profiles>
+        <profile>
+            <id>boot3</id>
+            <activation>
+                <jdk>[17,)</jdk>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>cn.dev33</groupId>
+                    <artifactId>sa-token-spring-boot3-starter</artifactId>
+                </dependency>
+            </dependencies>
+        </profile>
+        <profile>
+            <id>boot2</id>
+            <activation>
+                <jdk>(,17)</jdk>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>cn.dev33</groupId>
+                    <artifactId>sa-token-spring-boot-starter</artifactId>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+
+</project>

+ 148 - 0
jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/config/AsyncConfig.java

@@ -0,0 +1,148 @@
+package jnpf.config;
+
+import jnpf.base.UserInfo;
+import jnpf.model.tenant.TenantVO;
+import jnpf.util.TenantHolder;
+import jnpf.util.UserProvider;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.scheduling.annotation.AsyncConfigurer;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * 提供一个全局的Spring线程池对象
+ *
+ * @author JNPF开发平台组
+ * @version V3.1.0
+ * @copyright 引迈信息技术有限公司(https://www.jnpfsoft.com)
+ * @date 2021-12-10
+ */
+@Slf4j
+@Configuration
+@EnableAsync(proxyTargetClass = true)
+@AllArgsConstructor
+public class AsyncConfig implements AsyncConfigurer {
+
+
+//    private final Map<Object, Integer> asyncTaskCount = new HashMap<>();
+
+
+
+    @Primary
+    @Bean("threadPoolTaskExecutor")
+    @Override
+    public Executor getAsyncExecutor() {
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+        // 设置线程池核心容量
+        executor.setCorePoolSize(10);
+        // 设置线程池最大容量
+        executor.setMaxPoolSize(50);
+        // 设置任务队列长度
+        executor.setQueueCapacity(2000);
+        // 设置线程超时时间
+        executor.setKeepAliveSeconds(30);
+        // 设置线程名称前缀
+        executor.setThreadNamePrefix("sysTaskExecutor");
+        // 设置任务丢弃后的处理策略
+        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+        executor.setTaskDecorator( r ->{
+            //实现线程上下文穿透, 异步线程内无法获取之前的Request,租户信息等, 如有新的上下文对象在此处添加
+            //此方法在请求结束后在无法获取request, 下方完整异步Servlet请求
+//            RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
+            TenantVO tenantVO = TenantHolder.getLocalTenantCache();
+            UserInfo userInfo = UserProvider.getUser();
+            return () -> {
+                try {
+//                    if(attributes!= null) {
+//                        RequestContextHolder.setRequestAttributes(attributes);
+//                    }
+                    if(tenantVO != null){
+                        TenantHolder.setLocalTenantCache(tenantVO);
+                    }
+                    UserProvider.setLocalLoginUser(userInfo);
+                    r.run();
+                } finally {
+                    UserProvider.clearLocalUser();
+                    RequestContextHolder.resetRequestAttributes();
+                    TenantHolder.clearLocalTenantCache();
+                }
+            };
+        });
+
+        /*
+        //Tomcat异步Servlet只能增加吞吐量, 不能减少响应时间
+        executor.setTaskDecorator( r ->{
+            //实现线程上下文穿透, 异步线程内无法获取之前的Request,租户信息等, 如有新的上下文对象在此处添加
+            RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
+            String dataSourceId = DataSourceContextHolder.getDatasourceId();
+            String dataSourceName = DataSourceContextHolder.getDatasourceName();
+            final AsyncContext asyncContext = (AsyncContext) Optional.ofNullable(null).orElseGet(()->{
+                if(attributes!= null) {
+                    HttpServletRequest request = ((ServletRequestAttributes) attributes).getRequest();
+                    HttpServletResponse response = ((ServletRequestAttributes) attributes).getResponse();
+                    synchronized (AsyncConfig.class) {
+                        //开启多个异步
+                        AsyncContext tmpAsyncContext = request.isAsyncStarted() ? request.getAsyncContext() : request.startAsync(request, response);
+                        asyncTaskCount.put(tmpAsyncContext, asyncTaskCount.getOrDefault(tmpAsyncContext, 0) +1);
+                        return tmpAsyncContext;
+                    }
+                }
+                return null;
+            });
+            return () -> {
+                if (asyncContext != null) {
+                    asyncContext.start(() -> {
+                        if (dataSourceId != null || dataSourceName != null) {
+                            DataSourceContextHolder.setDatasource(dataSourceId, dataSourceName);
+                        }
+                        RequestContextHolder.setRequestAttributes(attributes);
+                        try {
+                            r.run();
+                        }finally{
+                            synchronized (AsyncConfig.class){
+                                //多个异步 最后一个执行关闭
+                                int count = asyncTaskCount.get(asyncContext)-1;
+                                if(count > 0){
+                                    asyncTaskCount.put(asyncContext, count);
+                                }else{
+                                    asyncTaskCount.remove(asyncContext);
+                                    asyncContext.complete();
+                                }
+                            }
+                            RequestContextHolder.resetRequestAttributes();
+                            DataSourceContextHolder.clearDatasourceType();
+                        }
+                    });
+                } else {
+                    if (dataSourceId != null || dataSourceName != null) {
+                        DataSourceContextHolder.setDatasource(dataSourceId, dataSourceName);
+                    }
+                    try {
+                        r.run();
+                    }finally{
+                        DataSourceContextHolder.clearDatasourceType();
+                    }
+                }
+            };
+        });
+         */
+        return executor;
+    }
+
+
+    @Bean("defaultExecutor")
+    public ThreadPoolTaskExecutor getAsyncExecutorDef(@Qualifier("threadPoolTaskExecutor") Executor executor) {
+        return (ThreadPoolTaskExecutor) executor;
+    }
+
+}

+ 48 - 0
jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/config/AuthAutoConfigration.java

@@ -0,0 +1,48 @@
+package jnpf.config;
+
+import cn.dev33.satoken.config.SaTokenConfig;
+import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
+import cn.dev33.satoken.stp.StpLogic;
+import jnpf.consts.AuthConsts;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+
+
+/**
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ */
+@Configuration
+public class AuthAutoConfigration {
+
+
+    @Primary
+    @Bean
+    @ConfigurationProperties(prefix = "oauth.login")
+    public SaTokenConfig getJnpfTokenConfig() {
+        return new JnpfTokenConfig();
+    }
+
+
+    @Bean
+    @ConditionalOnMissingBean
+    @ConfigurationProperties(prefix = JnpfOauthConfig.PREFIX)
+    public JnpfOauthConfig getJnpfOauthConfig(){
+        return new JnpfOauthConfig();
+    }
+
+    @Primary
+    @Bean(AuthConsts.ACCOUNT_LOGIC_BEAN_DEFAULT)
+    public StpLogic getJnpfTokenJwtLogic() {
+        return new StpLogicJwtForSimple(AuthConsts.ACCOUNT_TYPE_DEFAULT);
+    }
+
+    @Bean(AuthConsts.ACCOUNT_LOGIC_BEAN_TENANT)
+    public StpLogic getJnpfTenantTokenJwtLogic() {
+        return new StpLogicJwtForSimple(AuthConsts.ACCOUNT_TYPE_TENANT);
+    }
+}

+ 61 - 0
jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/config/JnpfOauthConfig.java

@@ -0,0 +1,61 @@
+package jnpf.config;
+import lombok.Data;
+import org.springframework.beans.factory.annotation.Value;
+
+
+/**
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ */
+@Data
+public class JnpfOauthConfig {
+
+    public static final String PREFIX = "oauth";
+
+    /**
+     * 服务器域名
+     * @see ConfigValueUtil#getApiDomain()
+     */
+    @Deprecated
+    @Value("${config.ApiDomain:}")
+    private String jnpfDomain;
+
+
+    /**
+     * 开启单点登录, 需额外代码支持
+     */
+    private Boolean ssoEnabled = false;
+
+    /**
+     * 后端登录完整路径路径
+     */
+    private String loginPath;
+
+    /**
+     * 默认发起的登录协议
+     */
+    private String defaultSSO = "cas";
+
+    /**
+     * 轮询Ticket有效期, 秒
+     */
+    private long ticketTimeout = 60;
+
+    /**
+     * pc端服务器域名
+     * @see ConfigValueUtil#getFrontDomain()
+     */
+    @Deprecated
+    @Value("${config.FrontDomain:}")
+    private String jnpfFrontDomain;
+
+    /**
+     * app端服务器域名
+     * @see ConfigValueUtil#getAppDomain()
+     */
+    @Deprecated
+    @Value("${config.AppDomain:}")
+    private String jnpfAppDomain;
+
+}

+ 104 - 0
jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/config/JnpfTokenConfig.java

@@ -0,0 +1,104 @@
+package jnpf.config;
+
+import cn.dev33.satoken.config.SaTokenConfig;
+import jnpf.consts.AuthConsts;
+import jnpf.model.BaseSystemInfo;
+import jnpf.util.Constants;
+import jnpf.util.TenantProvider;
+
+import java.util.Optional;
+
+
+/**
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ */
+public class JnpfTokenConfig extends SaTokenConfig {
+
+
+    @Override
+    public long getTimeout() {
+        BaseSystemInfo baseSystemInfo = getSycConfig();
+        if(baseSystemInfo == null){
+            return super.getTimeout();
+        }else {
+            return Long.parseLong(getSycConfig().getTokenTimeout()) * 60L;
+        }
+    }
+
+    @Override
+    public Boolean getIsConcurrent() {
+        BaseSystemInfo baseSystemInfo = getSycConfig();
+        if(baseSystemInfo == null){
+            return super.getIsConcurrent();
+        }else {
+            return Optional.ofNullable(getSycConfig().getSingleLogin()).orElse(1)==2;
+        }
+    }
+
+    @Override
+    public String getJwtSecretKey() {
+        String secrekey = super.getJwtSecretKey();
+        if(secrekey == null){
+            return AuthConsts.JWT_SECRET;
+        }
+        return secrekey;
+    }
+
+    @Override
+    public String getCurrDomain() {
+        return super.getCurrDomain();
+    }
+
+    @Override
+    public String getTokenPrefix() {
+        return AuthConsts.TOKEN_PREFIX;
+    }
+
+    @Override
+    public Boolean getTokenSessionCheckLogin() {
+        return false;
+    }
+
+    @Override
+    public Boolean getIsPrint() {
+        return false;
+    }
+
+    @Override
+    public Boolean getIsShare() {
+        return false;
+    }
+
+    @Override
+    public String getTokenName() {
+        return Constants.AUTHORIZATION;
+    }
+
+    @Override
+    public Boolean getIsReadCookie() {
+        return false;
+    }
+
+    @Override
+    public Boolean getIsReadBody() {
+        return false;
+    }
+
+    @Override
+    public Boolean getIsReadHeader() {
+        return true;
+    }
+
+    @Override
+    public int getMaxLoginCount() {
+        return -1;
+    }
+
+    private BaseSystemInfo getSycConfig(){
+        return TenantProvider.getBaseSystemInfo();
+    }
+
+
+}

+ 83 - 0
jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/consts/AuthConsts.java

@@ -0,0 +1,83 @@
+package jnpf.consts;
+
+
+import cn.dev33.satoken.same.SaSameUtil;
+import jnpf.service.UserDetailService;
+
+/**
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ */
+public class AuthConsts {
+
+    public static final String DEF_TENANT_ID = "";
+    public static final String DEF_TENANT_DB = "";
+
+    public static final String ACCOUNT_TYPE_DEFAULT = "login";
+    public static final String ACCOUNT_TYPE_TENANT = "tenant";
+    public static final String ACCOUNT_LOGIC_BEAN_DEFAULT = "defaultStpLogic";
+    public static final String ACCOUNT_LOGIC_BEAN_TENANT = "tenantStpLogic";
+
+    public static final String PAR_GRANT_TYPE = "grant_type";
+
+    public static final String SYSTEM_INFO = "system_info";
+
+    /**
+     * 跨服务调用验证KEY
+     */
+    public static final String INNER_TOKEN_KEY = SaSameUtil.SAME_TOKEN;
+
+    /**
+     * 网关调用验证KEY
+     */
+    public static final String INNER_GATEWAY_TOKEN_KEY = INNER_TOKEN_KEY + "_GATEWAY";
+
+    public static final String TENANT_SESSION = "tenant:";
+
+    public static final String TOKEN_PREFIX = "bearer";
+    public static final String TOKEN_PREFIX_SP = TOKEN_PREFIX + " ";
+
+    public static final String PARAMS_JNPF_TICKET = "jnpf_ticket";
+    public static final String PARAMS_SSO_LOGOUT_TICKET = "ticket";
+
+    public static final Integer REDIRECT_PAGETYPE_LOGIN = 1;
+    public static final Integer REDIRECT_PAGETYPE_LOGOUT = 2;
+
+    public static final Integer TMP_TOKEN_UNLOGIN = -1;
+    public static final Integer TMP_TOKEN_ERRLOGIN = -2;
+
+    public static final String ONLINE_TICKET_KEY = "online_ticket:";
+    public static final String ONLINE_TICKET_TOKEN = "online_token";
+
+    public static final String JWT_SECRET = "WviMjFNC72VKwGqm5LPoheQo5XN9iN4d";
+
+    /**
+     * clientId
+     */
+    public static final String Client_Id = "Client_Id";
+
+
+    /**
+     * 用户信息获取方式 account
+     */
+    public static final String USERDETAIL_ACCOUNT = UserDetailService.USER_DETAIL_PREFIX + "UserAccount";
+    /**
+     * 用户信息获取方式 user_id
+     */
+    public static final String USERDETAIL_USER_ID = UserDetailService.USER_DETAIL_PREFIX + "UserId";
+
+    /**
+     * 认证方式 常规账号密码
+     */
+    public static final String GRANT_TYPE_PASSWORD = "password";
+    /**
+     * 认证方式 单点 CAS
+     */
+    public static final String GRANT_TYPE_CAS = "cas";
+    /**
+     * 认证方式 单点 OAUTH
+     */
+    public static final String GRANT_TYPE_OAUTH = "auth2";
+
+}

+ 40 - 0
jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/consts/DeviceType.java

@@ -0,0 +1,40 @@
+package jnpf.consts;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+
+/**
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ */
+@Getter
+@AllArgsConstructor
+public enum DeviceType {
+
+    /**
+     * pc端
+     */
+    PC("PC"),
+
+    /**
+     * app端 手机都归为移动 自行扩展
+     */
+    APP("APP"),
+
+    /**
+     * 程序运行中使用的无限制临时用户
+     */
+    TEMPUSER("TEMPUSER"),
+
+
+    /**
+     * 程序运行中使用的限制临时用户, 不可访问主系统, CurrentUser接口报错
+     */
+    TEMPUSERLIMITED("TEMPUSERLIMITED");
+
+
+    private final String device;
+
+}

+ 46 - 0
jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/consts/LoginTicketStatus.java

@@ -0,0 +1,46 @@
+package jnpf.consts;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+
+/**
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ */
+@Getter
+@AllArgsConstructor
+public enum LoginTicketStatus {
+
+    /**
+     * 登录成功
+     */
+    Success(1),
+    /**
+     * 未登录
+     */
+    UnLogin(2),
+    /**
+     * 登录失败
+     */
+    ErrLogin(3),
+    /**
+     * 未绑定
+     */
+    UnBind(4),
+    /**
+     * 失效
+     */
+    Invalid(5),
+    /**
+     * 多租户
+     */
+    Multitenancy(6),
+    /**
+     * 第三方账号未绑定账号,请绑定后重试
+     */
+    UnBindMes(7),;
+
+    private int status;
+}

+ 29 - 0
jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/consts/ScanCodeTicketStatus.java

@@ -0,0 +1,29 @@
+package jnpf.consts;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum ScanCodeTicketStatus {
+
+    /**
+     * 已失效
+     */
+    Invalid(-1),
+    /**
+     * 未扫码
+     */
+    UnScanCode(0),
+    /**
+     * 已扫码
+     */
+    ScanCode(1),
+    /**
+     * 登录成功
+     */
+    Success(2);
+
+
+    private int status;
+}

+ 286 - 0
jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/granter/AbstractTokenGranter.java

@@ -0,0 +1,286 @@
+package jnpf.granter;
+
+import cn.dev33.satoken.context.SaHolder;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+import jnpf.base.ActionResult;
+import jnpf.base.UserInfo;
+import jnpf.config.ConfigValueUtil;
+import jnpf.constant.MsgCode;
+import jnpf.consts.AuthConsts;
+import jnpf.consts.DeviceType;
+import jnpf.consts.LoginTicketStatus;
+import jnpf.exception.LoginException;
+import jnpf.exception.TenantDatabaseException;
+import jnpf.model.BaseSystemInfo;
+import jnpf.model.LoginTicketModel;
+import jnpf.model.logout.LogoutResultModel;
+import jnpf.model.tenant.TenantVO;
+import jnpf.service.LoginService;
+import jnpf.util.RedisUtil;
+import jnpf.util.TenantProvider;
+import jnpf.util.TicketUtil;
+import jnpf.util.UserProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.Ordered;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.PathMatcher;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static jnpf.consts.AuthConsts.DEF_TENANT_DB;
+import static jnpf.consts.AuthConsts.DEF_TENANT_ID;
+
+
+/**
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ */
+public abstract class AbstractTokenGranter implements TokenGranter, Ordered {
+
+
+    @Autowired(required = false)
+    protected LoginService loginService;
+    @Autowired
+    protected ConfigValueUtil configValueUtil;
+    @Autowired
+    protected RedisUtil redisUtil;
+
+    protected static PathMatcher pathMatcher = new AntPathMatcher();
+
+    private String authenticationUrl;
+
+
+    public AbstractTokenGranter(String authenticationUrl){
+        this.authenticationUrl = authenticationUrl;
+    }
+
+
+    /**
+     * 最终登录用户
+     * @param userInfo 包含账户名, 登录方式
+     * @return
+     */
+    protected String loginAccount(UserInfo userInfo, BaseSystemInfo baseSystemInfo) throws LoginException {
+        try{
+            //获取用户实现类接口名称
+            userInfo.setUserDetailKey(getUserDetailKey());
+            //获取登录信息
+            userInfo = getUserInfo(userInfo, baseSystemInfo);
+            //预登陆
+            preLogin(userInfo, baseSystemInfo);
+            //登录
+            login(userInfo, baseSystemInfo);
+        }catch(Exception e){
+            try {
+                loginFailure(userInfo, baseSystemInfo, e);
+            }catch (Exception e1){
+                throw e1;
+            }
+            throw e;
+        }
+        loginSuccess(userInfo, baseSystemInfo);
+        //返回token信息
+        return userInfo.getToken();
+    }
+
+    /**
+     * 切换多租户
+     * @param userInfo
+     * @return userAccount, tenantId, tenandDb
+     * @throws LoginException
+     */
+    protected UserInfo switchTenant(UserInfo userInfo) throws LoginException {
+        if (configValueUtil.isMultiTenancy()) {
+            userInfo = loginService.getTenantAccount(userInfo);
+            return userInfo;
+
+        }
+        userInfo.setTenantId(DEF_TENANT_ID);
+        userInfo.setTenantDbConnectionString(DEF_TENANT_DB);
+        userInfo.setTenantDbType(TenantVO.NONE);
+        return userInfo;
+    }
+
+    /**
+     * 获取系统配置
+     * @param userInfo
+     * @return
+     */
+    protected BaseSystemInfo getSysconfig(UserInfo userInfo) throws LoginException {
+        BaseSystemInfo baseSystemInfo = loginService.getBaseSystemConfig(userInfo.getTenantId());
+        if(baseSystemInfo != null && baseSystemInfo.getSingleLogin() != null){
+            TenantProvider.setBaseSystemInfo(baseSystemInfo);
+        }else{
+            throw new TenantDatabaseException().setLogMsg(MsgCode.LOG110.get());
+        }
+        return baseSystemInfo;
+    }
+
+    /**
+     * 获取登录设备
+     * @return
+     */
+    protected DeviceType getDeviceType(){
+        return UserProvider.getDeviceForAgent();
+    }
+
+
+    /**
+     * 生成登录用户信息
+     * @param userInfo
+     * @return
+     */
+    protected UserInfo getUserInfo(UserInfo userInfo, BaseSystemInfo sysConfigInfo) throws LoginException {
+        userInfo.setGrantType(getGrantType());
+        userInfo = loginService.userInfo(userInfo, sysConfigInfo);
+        return userInfo;
+    }
+
+
+    /**
+     * 登录前执行
+     * @param userInfo
+     * @param baseSystemInfo
+     */
+    protected void preLogin(UserInfo userInfo, BaseSystemInfo baseSystemInfo) throws LoginException {
+
+    }
+
+    /**
+     * 登录操作
+     * @param userInfo
+     * @param baseSystemInfo
+     */
+    protected void login(UserInfo userInfo, BaseSystemInfo baseSystemInfo) throws LoginException {
+        UserProvider.login(userInfo, getLoginModel(userInfo, baseSystemInfo));
+    }
+
+    /**
+     * 登录成功触发
+     * @param userInfo
+     * @param baseSystemInfo
+     */
+    protected void loginSuccess(UserInfo userInfo, BaseSystemInfo baseSystemInfo){
+
+    }
+
+    /**
+     * 登录失败触发
+     * @param baseSystemInfo
+     */
+    protected void loginFailure(UserInfo userInfo, BaseSystemInfo baseSystemInfo, Exception e){
+
+    }
+
+    protected abstract String getUserDetailKey();
+
+    protected String createToken(UserInfo userInfo, BaseSystemInfo baseSystemInfo){
+        //登录
+        UserProvider.login(userInfo, getLoginModel(userInfo, baseSystemInfo));
+        return StpUtil.getTokenValueNotCut();
+    }
+
+    /**
+     * 更新轮询结果为成功
+     */
+    protected void updateTicketSuccess(UserInfo userInfo){
+        String ticket = getJnpfTicket();
+        if(!ticket.isEmpty()) {
+            LoginTicketModel loginTicketModel = new LoginTicketModel()
+                            .setStatus(LoginTicketStatus.Success.getStatus())
+                            .setValue(StpUtil.getTokenValueNotCut())
+                            .setTheme(userInfo.getTheme());
+            TicketUtil.updateTicket(ticket, loginTicketModel, null);
+        }
+    }
+
+    /**
+     * 更新轮询结果为失败
+     */
+    protected void updateTicketError(String msg){
+        String ticket = getJnpfTicket();
+        if(!ticket.isEmpty()) {
+            LoginTicketModel loginTicketModel = new LoginTicketModel()
+                            .setStatus(LoginTicketStatus.ErrLogin.getStatus())
+                            .setValue(msg);
+            TicketUtil.updateTicket(ticket, loginTicketModel, null);
+        }
+    }
+
+
+    /**
+     * 获取轮询ticket
+     * @return
+     */
+    protected String getJnpfTicket(){
+        return SaHolder.getRequest().getParam(AuthConsts.PARAMS_JNPF_TICKET, "");
+    }
+
+    protected boolean isValidJnpfTicket(){
+        String jnpfTicket = getJnpfTicket();
+        if(!jnpfTicket.isEmpty()){
+            LoginTicketModel loginTicketModel = TicketUtil.parseTicket(jnpfTicket);
+            if(loginTicketModel == null){
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * 获取登录参数
+     * @param userInfo
+     * @param baseSystemInfo
+     * @return
+     */
+    protected SaLoginParameter getLoginModel(UserInfo userInfo, BaseSystemInfo baseSystemInfo){
+        SaLoginParameter loginModel = new SaLoginParameter();
+        loginModel.setTimeout(userInfo.getTokenTimeout() * 60L);
+        loginModel.setExtraData(getTokenExtraData(userInfo, baseSystemInfo));
+        if(userInfo.getLoginDevice() == null) {
+            loginModel.setDevice(getDeviceType().getDevice());
+            userInfo.setLoginDevice(loginModel.getDeviceType());
+        }else{
+            loginModel.setDevice(userInfo.getLoginDevice());
+        }
+        return loginModel;
+    }
+
+    /**
+     * 获取额外的JWT内容
+     * @param userInfo
+     * @param baseSystemInfo
+     * @return
+     */
+    protected Map<String, Object> getTokenExtraData(UserInfo userInfo, BaseSystemInfo baseSystemInfo){
+        Map<String, Object> tokenInfo = new HashMap<>();
+//        tokenInfo.put("token", StpUtil.getTokenValue());
+        tokenInfo.put("singleLogin", baseSystemInfo == null ? null : baseSystemInfo.getSingleLogin());
+        tokenInfo.put("user_name", userInfo.getUserAccount());
+        tokenInfo.put("user_id", userInfo.getUserId());
+        tokenInfo.put("exp", userInfo.getOverdueTime().getTime());
+        tokenInfo.put("token", userInfo.getId());
+        return tokenInfo;
+    }
+
+    @Override
+    public ActionResult<LogoutResultModel> logout() {
+        UserProvider.logout();
+        return ActionResult.success();
+    }
+
+    protected abstract String getGrantType();
+
+
+    @Override
+    public boolean requiresAuthentication() {
+        String path = SaHolder.getRequest().getRequestPath();
+        if(path != null && path.startsWith("/api/oauth")){
+            path = path.replace("/api/oauth", "");
+        }
+        return pathMatcher.match(authenticationUrl, path);
+    }
+}

+ 24 - 0
jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/granter/TokenGranter.java

@@ -0,0 +1,24 @@
+package jnpf.granter;
+
+import jnpf.base.ActionResult;
+import jnpf.exception.LoginException;
+import jnpf.model.logout.LogoutResultModel;
+
+import java.util.Map;
+
+
+/**
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ */
+public interface TokenGranter {
+
+    ActionResult granter(Map<String, String> loginParameters) throws LoginException;
+
+
+    ActionResult<LogoutResultModel> logout();
+
+    boolean requiresAuthentication();
+
+}

+ 83 - 0
jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/granter/TokenGranterBuilder.java

@@ -0,0 +1,83 @@
+package jnpf.granter;
+
+import jnpf.base.UserInfo;
+import jnpf.config.JnpfOauthConfig;
+import jnpf.constant.MsgCode;
+import jnpf.consts.AuthConsts;
+import jnpf.exception.LoginException;
+import jnpf.util.UserProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+/**
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ */
+@Component
+public class TokenGranterBuilder {
+
+    @Autowired
+    private JnpfOauthConfig oauthConfig;
+
+    private final Map<String, TokenGranter> granterPool = new ConcurrentHashMap<>();
+
+    public TokenGranterBuilder(Map<String, TokenGranter> granterPool) {
+        granterPool.forEach(this.granterPool::put);
+    }
+
+    /**
+     * 获取TokenGranter
+     *
+     * @param grantType 授权类型
+     * @return ITokenGranter
+     */
+    public TokenGranter getGranter(String grantType) throws LoginException {
+        TokenGranter tokenGranter = null;
+        // 内部登录需要放行
+        if(!oauthConfig.getSsoEnabled() || "tempuser".equalsIgnoreCase(grantType)) {
+            tokenGranter = granterPool.get(grantType);
+        }
+        if(tokenGranter == null){
+            //URL匹配
+            for (TokenGranter value : granterPool.values()) {
+                if(value.requiresAuthentication()){
+                    tokenGranter = value;
+                    break;
+                }
+            }
+        }
+        if(tokenGranter == null){
+            if(oauthConfig.getSsoEnabled()) {
+                throw new LoginException(MsgCode.LOG111.get());
+            }else{
+                throw new LoginException(MsgCode.LOG112.get());
+            }
+        }
+        return tokenGranter;
+    }
+
+
+    /**
+     * 获取当前登录用户的TokenGranter
+     * @return
+     * @throws LoginException
+     */
+    public TokenGranter getGranterByLogin(String grandType) {
+        if(grandType == null || grandType.isEmpty()){
+            UserInfo userInfo = UserProvider.getUser();
+            if(userInfo.getGrantType() != null){
+                grandType = userInfo.getGrantType();
+            }else {
+                grandType = AuthConsts.GRANT_TYPE_PASSWORD;
+            }
+        }
+        return granterPool.get(grandType);
+    }
+
+
+}

+ 38 - 0
jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/granter/UserDetailsServiceBuilder.java

@@ -0,0 +1,38 @@
+package jnpf.granter;
+
+import jnpf.consts.AuthConsts;
+import jnpf.service.UserDetailService;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ */
+@Component
+public class UserDetailsServiceBuilder {
+
+
+    private final Map<String, UserDetailService> userDetailServices = new ConcurrentHashMap<>();
+
+    public UserDetailsServiceBuilder(Map<String, UserDetailService> userDetailServices) {
+        userDetailServices.forEach(this.userDetailServices::put);
+    }
+
+
+    /**
+     * 根据类型获取合适的UserDetailService
+     * @param detailType
+     * @return
+     */
+    public UserDetailService getUserDetailService(String detailType){
+        if(detailType == null){
+            detailType = AuthConsts.USERDETAIL_ACCOUNT;
+        }
+        return userDetailServices.get(detailType);
+    }
+
+}

+ 37 - 0
jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/model/LoginTicketModel.java

@@ -0,0 +1,37 @@
+package jnpf.model;
+
+import jnpf.consts.LoginTicketStatus;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.NonNull;
+import lombok.experimental.Accessors;
+
+/**
+ * 轮询登录模型
+ */
+@Data
+@Accessors(chain = true)
+public class LoginTicketModel {
+
+    /**
+     * 状态
+     * @see LoginTicketStatus
+     */
+    @NonNull
+    private int status = LoginTicketStatus.UnLogin.getStatus();
+
+    /**
+     * 额外的值, 登录Token、第三方登录的ID
+     */
+    private String value;
+
+    /**
+     * 前端主题
+     */
+    private String theme;
+    /**
+     * 票据有效期, 时间戳
+     */
+    private Long ticketTimeout;
+
+}

+ 25 - 0
jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/model/ScanCodeLoginConfigModel.java

@@ -0,0 +1,25 @@
+package jnpf.model;
+
+import jnpf.consts.ScanCodeTicketStatus;
+import lombok.Data;
+import lombok.NonNull;
+
+@Data
+public class ScanCodeLoginConfigModel {
+
+    /**
+     * 状态
+     * @see ScanCodeTicketStatus
+     */
+    @NonNull
+    private int status = ScanCodeTicketStatus.UnScanCode.getStatus();
+
+    /**
+     * 额外的值, 登录Token、第三方登录的ID
+     */
+    private String value;
+    /**
+     * 票据有效期, 时间戳
+     */
+    private Long ticketTimeout;
+}

+ 58 - 0
jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/service/LoginService.java

@@ -0,0 +1,58 @@
+package jnpf.service;
+
+import jnpf.base.UserInfo;
+import jnpf.exception.LoginException;
+import jnpf.model.BaseSystemInfo;
+import jnpf.model.login.PcUserVO;
+
+/**
+ * 登陆业务层
+ *
+ * @author JNPF开发平台组
+ * @version V3.1.0
+ * @copyright 引迈信息技术有限公司(https://www.jnpfsoft.com)
+ * @date 2021-03-23
+ */
+public interface LoginService {
+
+    /**
+     * 租戶登录验证
+     *
+     * @param userInfo
+     * @return userAccount, tenantId, tenandDb
+     * @throws LoginException
+     */
+    UserInfo getTenantAccount(UserInfo userInfo) throws LoginException;
+
+    /**
+     * 生成用户登录信息
+     *
+     * @param userInfo      账户信息
+     * @param sysConfigInfo 系统配置
+     * @return
+     * @throws LoginException
+     */
+    UserInfo userInfo(UserInfo userInfo, BaseSystemInfo sysConfigInfo) throws LoginException;
+
+    /**
+     * 获取用户登陆信息
+     *
+     * @return
+     */
+    PcUserVO getCurrentUser(String type, String systemCode, Integer isBackend);
+
+    /**
+     * 修改密码信息发送
+     *
+     * @return
+     */
+    void updatePasswordMessage();
+
+    /**
+     * @param tenantId
+     * @return
+     */
+    BaseSystemInfo getBaseSystemConfig(String tenantId);
+
+
+}

+ 19 - 0
jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/service/UserDetailService.java

@@ -0,0 +1,19 @@
+package jnpf.service;
+
+import jnpf.base.UserInfo;
+import jnpf.exception.LoginException;
+import org.springframework.core.Ordered;
+
+public interface UserDetailService extends Ordered {
+
+    static final String USER_DETAIL_PREFIX = "USERDETAIL_";
+
+    /**
+     * 获取用户信息
+     * @param userInfo
+     * @return UserEntity
+     * @param <T>
+     */
+    <T> T loadUserEntity(UserInfo userInfo) throws LoginException;
+
+}

+ 121 - 0
jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/util/TenantProvider.java

@@ -0,0 +1,121 @@
+package jnpf.util;
+
+import cn.dev33.satoken.session.SaSession;
+import cn.dev33.satoken.session.SaSessionCustomUtil;
+import jnpf.model.BaseSystemInfo;
+import lombok.extern.slf4j.Slf4j;
+
+import static jnpf.consts.AuthConsts.DEF_TENANT_ID;
+import static jnpf.consts.AuthConsts.TENANT_SESSION;
+
+
+/**
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ */
+@Slf4j
+public class TenantProvider {
+
+
+    private static final long tenantTimeout = 60 * 60 * 24 * 30L;
+
+    /**
+     * 获取租户Redis存储对象
+     *
+     * @param tenantId
+     * @return
+     */
+    public static SaSession getTenantSession(String tenantId) {
+        if (tenantId == null) {
+//            tenantId = TenantHolder.getDatasourceId();
+//            if (tenantId == null) {
+                tenantId = DEF_TENANT_ID;
+//            }
+        }
+        SaSession saSession = SaSessionCustomUtil.getSessionById(TENANT_SESSION + tenantId);
+        if (saSession != null && !saSession.get("init", false)) {
+            saSession.set("init", true);
+            saSession.updateTimeout(tenantTimeout);
+        }
+        return saSession;
+    }
+
+    /**
+     * 存入租户缓存空间
+     *
+     * @param tenantId
+     * @param key
+     * @param value
+     */
+    public static void putTenantCache(String tenantId, String key, Object value) {
+        SaSession saSession = getTenantSession(tenantId);
+        if (saSession != null) {
+            saSession.set(key, value).updateTimeout(tenantTimeout);
+        }
+    }
+
+    /**
+     * 获取租户缓存数据
+     *
+     * @param tenantId
+     * @param key
+     * @param <T>
+     * @return
+     */
+    public static <T> T getTenantCache(String tenantId, String key) {
+        SaSession saSession = getTenantSession(tenantId);
+        if (saSession != null) {
+            return (T) saSession.get(key);
+        }
+        return null;
+    }
+
+    /**
+     * 删除租户缓存数据
+     *
+     * @param tenantId
+     * @param key
+     */
+    public static void delTenantCache(String tenantId, String key) {
+        SaSession saSession = getTenantSession(tenantId);
+        if (saSession != null) {
+            saSession.delete(key);
+        }
+    }
+
+    public static void renewTimeout(String tenantId, long timeout) {
+        if (tenantId == null) {
+            tenantId = DEF_TENANT_ID;
+        }
+        SaSession saSession = getTenantSession(tenantId);
+        if (saSession != null) {
+            saSession.updateTimeout(timeout);
+        }
+    }
+
+
+    private static ThreadLocal<BaseSystemInfo> systemInfoThreadLocal = new ThreadLocal<>();
+
+    /**
+     * 获取系统设置信息
+     *
+     * @return
+     */
+    public static BaseSystemInfo getBaseSystemInfo() {
+        BaseSystemInfo systemInfo = systemInfoThreadLocal.get();
+        return systemInfo;
+    }
+
+
+    public static void setBaseSystemInfo(BaseSystemInfo baseSystemInfo) {
+        systemInfoThreadLocal.set(baseSystemInfo);
+    }
+
+
+    public static void clearBaseSystemIfo() {
+        systemInfoThreadLocal.remove();
+    }
+
+
+
+}

+ 68 - 0
jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/util/TicketUtil.java

@@ -0,0 +1,68 @@
+package jnpf.util;
+
+import cn.dev33.satoken.SaManager;
+import cn.dev33.satoken.temp.SaTempUtil;
+import cn.dev33.satoken.util.SaTokenConsts;
+import org.springframework.stereotype.Component;
+
+
+/**
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ */
+@Component
+public class TicketUtil {
+
+
+    /**
+     * 创建临时TOKEN
+     * @param value 值
+     * @param timeout 有效时间, 秒
+     * @return
+     */
+    public static String createTicket(Object value, long timeout){
+        return SaTempUtil.createToken(value, timeout);
+    }
+
+    /**
+     * 获取临时TOKEN内的数据
+     * @see #createTicket(Object, long)
+     * @param ticket 票据
+     * @return
+     * @param <T>
+     */
+    public static <T> T parseTicket(String ticket){
+        return (T) SaTempUtil.parseToken(ticket);
+    }
+
+    /**
+     * 移除临时Token
+     * @param ticket
+     */
+    public static void deleteTicket(String ticket){
+        SaTempUtil.deleteToken(ticket);
+    }
+
+    /**
+     * 更新Ticket内的内容
+     * @see #createTicket(Object, long)
+     * @param ticket 票据
+     * @param value 新值
+     * @param timeout 超时时间, 秒, 可空为不更新
+     */
+    public static void updateTicket(String ticket, Object value, Long timeout){
+        Object obj = parseTicket(ticket);
+        if(obj == null) return;
+        String key = getTicketKey(ticket);
+        if(timeout != null){
+            SaManager.getSaTokenDao().setObject(key, value, timeout);
+        }else{
+            SaManager.getSaTokenDao().updateObject(key, value);
+        }
+    }
+
+    private static String getTicketKey(String ticket){
+        return SaManager.getSaTempTemplate().splicingTempTokenSaveKey(ticket);
+    }
+}

+ 549 - 0
jnpf-boot-common/jnpf-common-auth/src/main/java/jnpf/util/UserProvider.java

@@ -0,0 +1,549 @@
+package jnpf.util;
+
+import cn.dev33.satoken.same.SaSameUtil;
+import cn.dev33.satoken.session.SaSession;
+import cn.dev33.satoken.session.SaTerminalInfo;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+import cn.hutool.core.text.StrPool;
+import jnpf.base.UserInfo;
+import jnpf.consts.DeviceType;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ObjectUtils;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static jnpf.consts.AuthConsts.TOKEN_PREFIX_SP;
+
+
+/**
+ * @author JNPF开发平台组
+ * @version V3.1.0
+ * @copyright 引迈信息技术有限公司
+ * @date 2021/3/16 10:57
+ */
+@Slf4j
+@Component
+public class UserProvider {
+
+    private static RedisUtil redisUtil;
+    private static CacheKeyUtil cacheKeyUtil;
+
+    public static final String USER_INFO_KEY = "userInfo";
+
+    private static final ThreadLocal<UserInfo> USER_CACHE = new ThreadLocal<>();
+
+    public UserProvider(RedisUtil redisUtil, CacheKeyUtil cacheKeyUtil) {
+        UserProvider.redisUtil = redisUtil;
+        UserProvider.cacheKeyUtil = cacheKeyUtil;
+    }
+
+
+
+    // =================== 登录相关操作 ===================
+
+    /**
+     * 登录系统 适用于Request环境
+     *
+     * @param userInfo 登录用户信息
+     */
+    public static void login(UserInfo userInfo) {
+        setLocalLoginUser(userInfo);
+        StpUtil.login(splicingLoginId(userInfo.getUserId()));
+        userInfo.setToken(StpUtil.getTokenValueNotCut());
+        userInfo.setSecurityKey(DesUtil.aesOrDecode(TicketUtil.createTicket(userInfo.getToken(), 10*24*60*60000L), true, true));
+        setLoginUser(userInfo);
+    }
+
+    /**
+     * 登录系统 适用于Request环境
+     *
+     * @param userInfo   用户信息
+     * @param loginModel 登录参数
+     */
+    public static void login(UserInfo userInfo, SaLoginParameter loginModel) {
+        setLocalLoginUser(userInfo);
+        StpUtil.login(splicingLoginId(userInfo.getUserId()), loginModel);
+        userInfo.setToken(StpUtil.getTokenValueNotCut());
+        userInfo.setSecurityKey(DesUtil.aesOrDecode(TicketUtil.createTicket(userInfo.getToken(), 10*24*60*60000L), true, true));
+        setLoginUser(userInfo);
+    }
+
+    /**
+     * 适用于非Request环境
+     * @param userInfo
+     * @param loginModel
+     */
+    public static void loginNoRequest(UserInfo userInfo, SaLoginParameter loginModel) {
+        setLocalLoginUser(userInfo);
+        String token = StpUtil.createLoginSession(splicingLoginId(userInfo.getUserId()), loginModel);
+        userInfo.setToken(TOKEN_PREFIX_SP + token);
+        setLoginUser(userInfo);
+    }
+
+
+    // =================== 登录用户ID相关操作 ===================
+
+    /**
+     * 获取指定TOKEN用户ID
+     *
+     * @param token
+     * @return
+     */
+    public static String getLoginUserId(String token) {
+        String loginId = (String) StpUtil.getLoginIdByToken(token);
+        return parseLoginId(loginId);
+    }
+
+    /**
+     * 获取当前用户ID, 包含临时切换用户ID
+     *
+     * @return
+     */
+    public static String getLoginUserId() {
+        String loginId = getUser().getUserId();
+        return parseLoginId(loginId);
+    }
+
+
+
+    // =================== 用户ID拼接相关操作 ===================
+
+
+    /**
+     * 拼接租户下的用户ID
+     *
+     * @param userId
+     * @return
+     */
+    public static String splicingLoginId(String userId) {
+        return splicingLoginId(userId, null);
+    }
+
+    /**
+     * 拼接租户下的用户ID
+     * @param userId
+     * @param tenantId
+     * @return
+     */
+    private static String splicingLoginId(String userId, String tenantId) {
+        if(StringUtil.isEmpty(tenantId)){
+            tenantId = TenantHolder.getDatasourceId();
+        }
+        if (!StringUtil.isEmpty(tenantId)) {
+            return tenantId + StrPool.COLON + userId;
+        }
+        return userId;
+    }
+
+    /**
+     * 解析租户下的登录ID
+     * @param loginId
+     * @return
+     */
+    private static String parseLoginId(String loginId) {
+        if (loginId != null && loginId.contains(StrPool.COLON)) {
+            loginId = loginId.substring(loginId.indexOf(StrPool.COLON) + 1);
+        }
+        return loginId;
+    }
+
+    /**
+     * Token是否有效
+     *
+     * @param token
+     * @return
+     */
+    public static Boolean isValidToken(String token) {
+        UserInfo userInfo = getUser(token);
+        return userInfo.getUserId() != null;
+    }
+
+
+    // =================== UserInfo缓存相关操作 ===================
+
+
+    /**
+     * 设置Redis用户数据
+     */
+    public static void setLoginUser(UserInfo userInfo) {
+        StpUtil.getTokenSessionByToken(cutToken(userInfo.getToken())).set(USER_INFO_KEY, userInfo);
+    }
+
+    /**
+     * 设置本地用户数据
+     */
+    public static void setLocalLoginUser(UserInfo userInfo) {
+        USER_CACHE.set(userInfo);
+    }
+
+    /**
+     * 获取本地用户数据
+     */
+    public static UserInfo getLocalLoginUser() {
+        return USER_CACHE.get();
+    }
+
+    /**
+     * 清空本地用户数据
+     */
+    public static void clearLocalUser() {
+        USER_CACHE.remove();
+    }
+
+
+
+
+    /**
+     * 获取用户缓存
+     * 保留旧方法
+     *
+     * @param token
+     * @return
+     */
+    public UserInfo get(String token) {
+        return UserProvider.getUser(token);
+    }
+
+    /**
+     * 获取用户缓存
+     *
+     * @return
+     */
+    public UserInfo get() {
+        return UserProvider.getUser();
+    }
+
+
+    /**
+     * 根据用户ID, 租户ID获取随机获取一个UserInfo
+     * @param userId
+     * @param tenantId
+     * @return
+     */
+    public static UserInfo getUser(String userId, String tenantId){
+        return getUser(userId, tenantId, null, null);
+    }
+
+    /**
+     * 根据用户ID, 租户ID, 设备类型获取随机获取一个UserInfo
+     * @param userId
+     * @param tenantId
+     * @param includeDevice 指定的设备类型中查找
+     * @param excludeDevice 排除指定设备类型
+     * @return
+     */
+    public static UserInfo getUser(String userId, String tenantId, List<String> includeDevice, List<String> excludeDevice){
+        SaSession session = StpUtil.getSessionByLoginId(splicingLoginId(userId, tenantId), false);
+        if (session != null) {
+            List<SaTerminalInfo> tokenSignList = session.getTerminalList();
+            if (!tokenSignList.isEmpty()) {
+                tokenSignList = tokenSignList.stream().filter(tokenSign -> {
+                    if(!ObjectUtils.isEmpty(excludeDevice)){
+                        if(excludeDevice.contains(tokenSign.getDeviceType())){
+                            return false;
+                        }
+                    }
+                    if(!ObjectUtils.isEmpty(includeDevice)){
+                        if(!includeDevice.contains(tokenSign.getDeviceType())){
+                            return false;
+                        }
+                    }
+                    return true;
+                }).collect(Collectors.toList());
+                if(!tokenSignList.isEmpty()){
+                    return getUser(tokenSignList.get(0).getTokenValue());
+                }
+            }
+        }
+        return new UserInfo();
+    }
+
+    /**
+     * 获取用户缓存
+     *
+     * @param token
+     * @return
+     */
+    public static UserInfo getUser(String token) {
+        UserInfo userInfo = null;
+        String tokens = null;
+        if (token != null) {
+            tokens = cutToken(token);
+        } else {
+            try {
+                //处理非Web环境报错
+                tokens = StpUtil.getTokenValue();
+            } catch (Exception e) {
+            }
+        }
+        if (tokens != null) {
+            if (StpUtil.getLoginIdByToken(tokens) != null) {
+                userInfo = (UserInfo) StpUtil.getTokenSessionByToken(tokens).get(USER_INFO_KEY);
+            }
+        }
+        if (userInfo == null) {
+            userInfo = new UserInfo();
+        }
+        return userInfo;
+    }
+
+    /**
+     * 获取用户缓存
+     *
+     * @return
+     */
+    public static UserInfo getUser() {
+//        if(StpUtil.getTokenValue() == null){
+//            return  new UserInfo();
+//        }
+        UserInfo userInfo = USER_CACHE.get();
+        if (userInfo != null) {
+            return userInfo;
+        }
+        userInfo = UserProvider.getUser(null);
+        if (userInfo.getUserId() != null) {
+            USER_CACHE.set(userInfo);
+        }
+        return userInfo;
+    }
+
+    // =================== Token相关操作 ===================
+
+    /**
+     * 去除Token前缀
+     *
+     * @param token
+     * @return
+     */
+    public static String cutToken(String token) {
+        if (token != null && token.startsWith(TOKEN_PREFIX_SP)) {
+            token = token.substring(TOKEN_PREFIX_SP.length());
+        }
+        return token;
+    }
+
+    /**
+     * 获取token
+     */
+    public static String getToken() {
+        String toke = getAuthorize();
+        return toke;
+    }
+
+
+    /**
+     * 获取Authorize
+     */
+    public static String getAuthorize() {
+        String authorize = ServletUtil.getHeader(Constants.AUTHORIZATION);
+        return authorize;
+    }
+
+
+    /**
+     * TOKEN续期
+     */
+    public static void renewTimeout(String token) {
+        if (StpUtil.getTokenValue() != null) {
+            UserInfo userInfo = UserProvider.getUser(token);
+            if(userInfo.getUserId() == null || userInfo.getTokenTimeout() == null) {
+                //避免请求过网关之后TOKEN失效(携带TOKEN调用登录接口之后账号被顶替)
+                return;
+            }
+            StpUtil.renewTimeout(userInfo.getTokenTimeout() * 60L);
+            if(userInfo.getIsAdministrator()) {
+                SaSession saSession = StpUtil.getSessionByLoginId(splicingLoginId(Constants.ADMIN_KEY), false);
+                if (saSession != null) {
+                    saSession.updateTimeout(userInfo.getTokenTimeout() * 60L);
+                }
+            }
+        }
+    }
+
+    /**
+     * 获取所有Token记录
+     * 包含无效状态的用户、临时用户
+     *
+     * @return
+     */
+    public static List<String> getLoginUserListToken() {
+        return StpUtil.searchTokenValue("", -1, -1, true).stream().map(token -> token.replace(StpUtil.stpLogic.splicingKeyTokenValue(""), "")).collect(Collectors.toList());
+    }
+
+
+    // =================== 临时Token相关操作 ===================
+
+
+    /**
+     * 获取内部服务传递验证TOKEN
+     *
+     * @return
+     */
+    public static String getInnerAuthToken() {
+        return SaSameUtil.getToken();
+    }
+
+    /**
+     * 验证内部传递Token是否有效 抛出异常
+     * @param token
+     */
+    public static void checkInnerToken(String token){
+        SaSameUtil.checkToken(token);
+    }
+
+    /**
+     * 验证内部传递Token是否有效
+     * @param token
+     */
+    public static boolean isValidInnerToken(String token){
+        return SaSameUtil.isValid(token);
+    }
+
+
+    // =================== 退出相关操作 ===================
+
+
+    /**
+     * 根据用户ID踢出全部用户
+     * @param userId
+     */
+    public static void kickoutByUserId(String userId, String tenantId) {
+        StpUtil.kickout(splicingLoginId(userId, tenantId));
+    }
+
+    /**
+     * 根据Token踢出指定会话
+     * @param tokens
+     */
+    public static void kickoutByToken(String... tokens) {
+        for (String token : tokens) {
+            StpUtil.kickoutByTokenValue(token);
+        }
+    }
+
+    /**
+     * 退出当前Token, 不清除用户其他系统缓存
+     */
+    public static void logout() {
+        StpUtil.logout();
+
+    }
+
+    /**
+     * 退出指定Token, 不清除用户其他系统缓存
+     *
+     * @param token
+     */
+    public static void logoutByToken(String token) {
+        if (token == null) {
+            logout();
+        } else {
+            StpUtil.logoutByTokenValue(cutToken(token));
+        }
+    }
+
+    /**
+     * 退出指定设备类型的用户的全部登录信息, 不清除用户其他系统缓存
+     *
+     * @param userId
+     * @param deviceType
+     */
+    public static void logoutByUserId(String userId, DeviceType deviceType) {
+        StpUtil.logout(splicingLoginId(userId), deviceType.getDevice());
+    }
+
+    /**
+     * 退出指定用户的全部登录信息, 清除相关缓存
+     *
+     * @param userId
+     */
+    public static void logoutByUserId(String userId) {
+        StpUtil.logout(splicingLoginId(userId));
+        removeOtherCache(userId);
+
+    }
+
+    // =================== 用户权限 ===================
+
+    /**
+     * 获取当前用户拥有的权限列表(菜单编码列表、功能ID列表)
+     * @return
+     */
+    public static List<String> getPermissionList(){
+        return StpUtil.getPermissionList();
+    }
+
+    /**
+     * 获取当前用户拥有的角色列表
+     * @return
+     */
+    public static List<String> getRoleList(){
+        return StpUtil.getRoleList();
+    }
+
+
+
+
+
+    // =================== 其他缓存相关操作 ===================
+
+    /**
+     * 移除
+     */
+    public static void removeOtherCache(String userId) {
+        redisUtil.remove(cacheKeyUtil.getUserAuthorize() + userId);
+        redisUtil.remove(cacheKeyUtil.getSystemInfo());
+    }
+
+    /**
+     * 是否在线
+     */
+    public boolean isOnLine(String userId) {
+        return StpUtil.getTokenValueByLoginId(splicingLoginId(userId), getDeviceForAgent().getDevice()) != null;
+    }
+
+
+    /**
+     * 是否登陆
+     */
+    public static boolean isLogined() {
+        return StpUtil.isLogin();
+    }
+
+    /**
+     * 指定Token是否有效
+     * @param token
+     * @return
+     */
+    public static boolean isValid(String token) {
+        return StpUtil.getLoginIdByToken(token) != null;
+    }
+
+
+    public static DeviceType getDeviceForAgent() {
+        if (ServletUtil.getIsMobileDevice()) {
+            return DeviceType.APP;
+        } else {
+            return DeviceType.PC;
+        }
+    }
+
+    /**
+     * 判断用户是否是临时用户
+     * @param userInfo
+     * @return
+     */
+    public static boolean isTempUser(UserInfo userInfo){
+        if(userInfo == null){
+            userInfo = getUser();
+        }
+        return DeviceType.TEMPUSER.getDevice().equals(userInfo.getLoginDevice())
+                || DeviceType.TEMPUSERLIMITED.getDevice().equals(userInfo.getLoginDevice());
+    }
+
+
+
+}

+ 272 - 0
jnpf-boot-common/jnpf-common-auth/target/classes/META-INF/spring-configuration-metadata.json

@@ -0,0 +1,272 @@
+{
+  "groups": [
+    {
+      "name": "oauth",
+      "type": "jnpf.config.JnpfOauthConfig",
+      "sourceType": "jnpf.config.AuthAutoConfigration",
+      "sourceMethod": "getJnpfOauthConfig()"
+    },
+    {
+      "name": "oauth.login",
+      "type": "cn.dev33.satoken.config.SaTokenConfig",
+      "sourceType": "jnpf.config.AuthAutoConfigration",
+      "sourceMethod": "getJnpfTokenConfig()"
+    }
+  ],
+  "properties": [
+    {
+      "name": "oauth.default-s-s-o",
+      "type": "java.lang.String",
+      "description": "默认发起的登录协议",
+      "sourceType": "jnpf.config.JnpfOauthConfig",
+      "defaultValue": "cas"
+    },
+    {
+      "name": "oauth.login-path",
+      "type": "java.lang.String",
+      "description": "后端登录完整路径路径",
+      "sourceType": "jnpf.config.JnpfOauthConfig"
+    },
+    {
+      "name": "oauth.login.active-timeout",
+      "type": "java.lang.Long",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.auto-renew",
+      "type": "java.lang.Boolean",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.check-same-token",
+      "type": "java.lang.Boolean",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.cookie",
+      "type": "cn.dev33.satoken.config.SaCookieConfig",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.cookie-auto-fill-prefix",
+      "type": "java.lang.Boolean",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.curr-domain",
+      "type": "java.lang.String",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.data-refresh-period",
+      "type": "java.lang.Integer",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.dynamic-active-timeout",
+      "type": "java.lang.Boolean",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.http-basic",
+      "type": "java.lang.String",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.http-digest",
+      "type": "java.lang.String",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.is-color-log",
+      "type": "java.lang.Boolean",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.is-concurrent",
+      "type": "java.lang.Boolean",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.is-lasting-cookie",
+      "type": "java.lang.Boolean",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.is-log",
+      "type": "java.lang.Boolean",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.is-logout-keep-freeze-ops",
+      "type": "java.lang.Boolean",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.is-logout-keep-token-session",
+      "type": "java.lang.Boolean",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.is-print",
+      "type": "java.lang.Boolean",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.is-read-body",
+      "type": "java.lang.Boolean",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.is-read-cookie",
+      "type": "java.lang.Boolean",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.is-read-header",
+      "type": "java.lang.Boolean",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.is-share",
+      "type": "java.lang.Boolean",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.is-write-header",
+      "type": "java.lang.Boolean",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.jwt-secret-key",
+      "type": "java.lang.String",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.log-level",
+      "type": "java.lang.String",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.log-level-int",
+      "type": "java.lang.Integer",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.logout-range",
+      "type": "cn.dev33.satoken.stp.parameter.enums.SaLogoutRange",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.max-login-count",
+      "type": "java.lang.Integer",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.max-try-times",
+      "type": "java.lang.Integer",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.overflow-logout-mode",
+      "type": "cn.dev33.satoken.stp.parameter.enums.SaLogoutMode",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.replaced-range",
+      "type": "cn.dev33.satoken.stp.parameter.enums.SaReplacedRange",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.right-now-create-token-session",
+      "type": "java.lang.Boolean",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.same-token-timeout",
+      "type": "java.lang.Long",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.timeout",
+      "type": "java.lang.Long",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.token-name",
+      "type": "java.lang.String",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.token-prefix",
+      "type": "java.lang.String",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.token-session-check-login",
+      "type": "java.lang.Boolean",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.login.token-style",
+      "type": "java.lang.String",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig"
+    },
+    {
+      "name": "oauth.sso-enabled",
+      "type": "java.lang.Boolean",
+      "description": "开启单点登录, 需额外代码支持",
+      "sourceType": "jnpf.config.JnpfOauthConfig",
+      "defaultValue": false
+    },
+    {
+      "name": "oauth.ticket-timeout",
+      "type": "java.lang.Long",
+      "description": "轮询Ticket有效期, 秒",
+      "sourceType": "jnpf.config.JnpfOauthConfig",
+      "defaultValue": 60
+    },
+    {
+      "name": "oauth.jnpf-app-domain",
+      "type": "java.lang.String",
+      "description": "app端服务器域名 @see ConfigValueUtil#getAppDomain()",
+      "sourceType": "jnpf.config.JnpfOauthConfig",
+      "deprecated": true,
+      "deprecation": {}
+    },
+    {
+      "name": "oauth.jnpf-domain",
+      "type": "java.lang.String",
+      "description": "服务器域名 @see ConfigValueUtil#getApiDomain()",
+      "sourceType": "jnpf.config.JnpfOauthConfig",
+      "deprecated": true,
+      "deprecation": {}
+    },
+    {
+      "name": "oauth.jnpf-front-domain",
+      "type": "java.lang.String",
+      "description": "pc端服务器域名 @see ConfigValueUtil#getFrontDomain()",
+      "sourceType": "jnpf.config.JnpfOauthConfig",
+      "deprecated": true,
+      "deprecation": {}
+    },
+    {
+      "name": "oauth.login.activity-timeout",
+      "type": "java.lang.Long",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig",
+      "deprecated": true,
+      "deprecation": {}
+    },
+    {
+      "name": "oauth.login.basic",
+      "type": "java.lang.String",
+      "sourceType": "cn.dev33.satoken.config.SaTokenConfig",
+      "deprecated": true,
+      "deprecation": {}
+    }
+  ],
+  "hints": [],
+  "ignored": {
+    "properties": []
+  }
+}

BIN
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/config/AsyncConfig.class


+ 148 - 0
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/config/AsyncConfig.java

@@ -0,0 +1,148 @@
+package jnpf.config;
+
+import jnpf.base.UserInfo;
+import jnpf.model.tenant.TenantVO;
+import jnpf.util.TenantHolder;
+import jnpf.util.UserProvider;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.scheduling.annotation.AsyncConfigurer;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * 提供一个全局的Spring线程池对象
+ *
+ * @author JNPF开发平台组
+ * @version V3.1.0
+ * @copyright 引迈信息技术有限公司(https://www.jnpfsoft.com)
+ * @date 2021-12-10
+ */
+@Slf4j
+@Configuration
+@EnableAsync(proxyTargetClass = true)
+@AllArgsConstructor
+public class AsyncConfig implements AsyncConfigurer {
+
+
+//    private final Map<Object, Integer> asyncTaskCount = new HashMap<>();
+
+
+
+    @Primary
+    @Bean("threadPoolTaskExecutor")
+    @Override
+    public Executor getAsyncExecutor() {
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+        // 设置线程池核心容量
+        executor.setCorePoolSize(10);
+        // 设置线程池最大容量
+        executor.setMaxPoolSize(50);
+        // 设置任务队列长度
+        executor.setQueueCapacity(2000);
+        // 设置线程超时时间
+        executor.setKeepAliveSeconds(30);
+        // 设置线程名称前缀
+        executor.setThreadNamePrefix("sysTaskExecutor");
+        // 设置任务丢弃后的处理策略
+        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+        executor.setTaskDecorator( r ->{
+            //实现线程上下文穿透, 异步线程内无法获取之前的Request,租户信息等, 如有新的上下文对象在此处添加
+            //此方法在请求结束后在无法获取request, 下方完整异步Servlet请求
+//            RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
+            TenantVO tenantVO = TenantHolder.getLocalTenantCache();
+            UserInfo userInfo = UserProvider.getUser();
+            return () -> {
+                try {
+//                    if(attributes!= null) {
+//                        RequestContextHolder.setRequestAttributes(attributes);
+//                    }
+                    if(tenantVO != null){
+                        TenantHolder.setLocalTenantCache(tenantVO);
+                    }
+                    UserProvider.setLocalLoginUser(userInfo);
+                    r.run();
+                } finally {
+                    UserProvider.clearLocalUser();
+                    RequestContextHolder.resetRequestAttributes();
+                    TenantHolder.clearLocalTenantCache();
+                }
+            };
+        });
+
+        /*
+        //Tomcat异步Servlet只能增加吞吐量, 不能减少响应时间
+        executor.setTaskDecorator( r ->{
+            //实现线程上下文穿透, 异步线程内无法获取之前的Request,租户信息等, 如有新的上下文对象在此处添加
+            RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
+            String dataSourceId = DataSourceContextHolder.getDatasourceId();
+            String dataSourceName = DataSourceContextHolder.getDatasourceName();
+            final AsyncContext asyncContext = (AsyncContext) Optional.ofNullable(null).orElseGet(()->{
+                if(attributes!= null) {
+                    HttpServletRequest request = ((ServletRequestAttributes) attributes).getRequest();
+                    HttpServletResponse response = ((ServletRequestAttributes) attributes).getResponse();
+                    synchronized (AsyncConfig.class) {
+                        //开启多个异步
+                        AsyncContext tmpAsyncContext = request.isAsyncStarted() ? request.getAsyncContext() : request.startAsync(request, response);
+                        asyncTaskCount.put(tmpAsyncContext, asyncTaskCount.getOrDefault(tmpAsyncContext, 0) +1);
+                        return tmpAsyncContext;
+                    }
+                }
+                return null;
+            });
+            return () -> {
+                if (asyncContext != null) {
+                    asyncContext.start(() -> {
+                        if (dataSourceId != null || dataSourceName != null) {
+                            DataSourceContextHolder.setDatasource(dataSourceId, dataSourceName);
+                        }
+                        RequestContextHolder.setRequestAttributes(attributes);
+                        try {
+                            r.run();
+                        }finally{
+                            synchronized (AsyncConfig.class){
+                                //多个异步 最后一个执行关闭
+                                int count = asyncTaskCount.get(asyncContext)-1;
+                                if(count > 0){
+                                    asyncTaskCount.put(asyncContext, count);
+                                }else{
+                                    asyncTaskCount.remove(asyncContext);
+                                    asyncContext.complete();
+                                }
+                            }
+                            RequestContextHolder.resetRequestAttributes();
+                            DataSourceContextHolder.clearDatasourceType();
+                        }
+                    });
+                } else {
+                    if (dataSourceId != null || dataSourceName != null) {
+                        DataSourceContextHolder.setDatasource(dataSourceId, dataSourceName);
+                    }
+                    try {
+                        r.run();
+                    }finally{
+                        DataSourceContextHolder.clearDatasourceType();
+                    }
+                }
+            };
+        });
+         */
+        return executor;
+    }
+
+
+    @Bean("defaultExecutor")
+    public ThreadPoolTaskExecutor getAsyncExecutorDef(@Qualifier("threadPoolTaskExecutor") Executor executor) {
+        return (ThreadPoolTaskExecutor) executor;
+    }
+
+}

BIN
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/config/AuthAutoConfigration.class


+ 48 - 0
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/config/AuthAutoConfigration.java

@@ -0,0 +1,48 @@
+package jnpf.config;
+
+import cn.dev33.satoken.config.SaTokenConfig;
+import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
+import cn.dev33.satoken.stp.StpLogic;
+import jnpf.consts.AuthConsts;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+
+
+/**
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ */
+@Configuration
+public class AuthAutoConfigration {
+
+
+    @Primary
+    @Bean
+    @ConfigurationProperties(prefix = "oauth.login")
+    public SaTokenConfig getJnpfTokenConfig() {
+        return new JnpfTokenConfig();
+    }
+
+
+    @Bean
+    @ConditionalOnMissingBean
+    @ConfigurationProperties(prefix = JnpfOauthConfig.PREFIX)
+    public JnpfOauthConfig getJnpfOauthConfig(){
+        return new JnpfOauthConfig();
+    }
+
+    @Primary
+    @Bean(AuthConsts.ACCOUNT_LOGIC_BEAN_DEFAULT)
+    public StpLogic getJnpfTokenJwtLogic() {
+        return new StpLogicJwtForSimple(AuthConsts.ACCOUNT_TYPE_DEFAULT);
+    }
+
+    @Bean(AuthConsts.ACCOUNT_LOGIC_BEAN_TENANT)
+    public StpLogic getJnpfTenantTokenJwtLogic() {
+        return new StpLogicJwtForSimple(AuthConsts.ACCOUNT_TYPE_TENANT);
+    }
+}

BIN
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/config/JnpfOauthConfig.class


+ 61 - 0
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/config/JnpfOauthConfig.java

@@ -0,0 +1,61 @@
+package jnpf.config;
+import lombok.Data;
+import org.springframework.beans.factory.annotation.Value;
+
+
+/**
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ */
+@Data
+public class JnpfOauthConfig {
+
+    public static final String PREFIX = "oauth";
+
+    /**
+     * 服务器域名
+     * @see ConfigValueUtil#getApiDomain()
+     */
+    @Deprecated
+    @Value("${config.ApiDomain:}")
+    private String jnpfDomain;
+
+
+    /**
+     * 开启单点登录, 需额外代码支持
+     */
+    private Boolean ssoEnabled = false;
+
+    /**
+     * 后端登录完整路径路径
+     */
+    private String loginPath;
+
+    /**
+     * 默认发起的登录协议
+     */
+    private String defaultSSO = "cas";
+
+    /**
+     * 轮询Ticket有效期, 秒
+     */
+    private long ticketTimeout = 60;
+
+    /**
+     * pc端服务器域名
+     * @see ConfigValueUtil#getFrontDomain()
+     */
+    @Deprecated
+    @Value("${config.FrontDomain:}")
+    private String jnpfFrontDomain;
+
+    /**
+     * app端服务器域名
+     * @see ConfigValueUtil#getAppDomain()
+     */
+    @Deprecated
+    @Value("${config.AppDomain:}")
+    private String jnpfAppDomain;
+
+}

BIN
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/config/JnpfTokenConfig.class


+ 104 - 0
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/config/JnpfTokenConfig.java

@@ -0,0 +1,104 @@
+package jnpf.config;
+
+import cn.dev33.satoken.config.SaTokenConfig;
+import jnpf.consts.AuthConsts;
+import jnpf.model.BaseSystemInfo;
+import jnpf.util.Constants;
+import jnpf.util.TenantProvider;
+
+import java.util.Optional;
+
+
+/**
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ */
+public class JnpfTokenConfig extends SaTokenConfig {
+
+
+    @Override
+    public long getTimeout() {
+        BaseSystemInfo baseSystemInfo = getSycConfig();
+        if(baseSystemInfo == null){
+            return super.getTimeout();
+        }else {
+            return Long.parseLong(getSycConfig().getTokenTimeout()) * 60L;
+        }
+    }
+
+    @Override
+    public Boolean getIsConcurrent() {
+        BaseSystemInfo baseSystemInfo = getSycConfig();
+        if(baseSystemInfo == null){
+            return super.getIsConcurrent();
+        }else {
+            return Optional.ofNullable(getSycConfig().getSingleLogin()).orElse(1)==2;
+        }
+    }
+
+    @Override
+    public String getJwtSecretKey() {
+        String secrekey = super.getJwtSecretKey();
+        if(secrekey == null){
+            return AuthConsts.JWT_SECRET;
+        }
+        return secrekey;
+    }
+
+    @Override
+    public String getCurrDomain() {
+        return super.getCurrDomain();
+    }
+
+    @Override
+    public String getTokenPrefix() {
+        return AuthConsts.TOKEN_PREFIX;
+    }
+
+    @Override
+    public Boolean getTokenSessionCheckLogin() {
+        return false;
+    }
+
+    @Override
+    public Boolean getIsPrint() {
+        return false;
+    }
+
+    @Override
+    public Boolean getIsShare() {
+        return false;
+    }
+
+    @Override
+    public String getTokenName() {
+        return Constants.AUTHORIZATION;
+    }
+
+    @Override
+    public Boolean getIsReadCookie() {
+        return false;
+    }
+
+    @Override
+    public Boolean getIsReadBody() {
+        return false;
+    }
+
+    @Override
+    public Boolean getIsReadHeader() {
+        return true;
+    }
+
+    @Override
+    public int getMaxLoginCount() {
+        return -1;
+    }
+
+    private BaseSystemInfo getSycConfig(){
+        return TenantProvider.getBaseSystemInfo();
+    }
+
+
+}

BIN
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/consts/AuthConsts.class


+ 83 - 0
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/consts/AuthConsts.java

@@ -0,0 +1,83 @@
+package jnpf.consts;
+
+
+import cn.dev33.satoken.same.SaSameUtil;
+import jnpf.service.UserDetailService;
+
+/**
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ */
+public class AuthConsts {
+
+    public static final String DEF_TENANT_ID = "";
+    public static final String DEF_TENANT_DB = "";
+
+    public static final String ACCOUNT_TYPE_DEFAULT = "login";
+    public static final String ACCOUNT_TYPE_TENANT = "tenant";
+    public static final String ACCOUNT_LOGIC_BEAN_DEFAULT = "defaultStpLogic";
+    public static final String ACCOUNT_LOGIC_BEAN_TENANT = "tenantStpLogic";
+
+    public static final String PAR_GRANT_TYPE = "grant_type";
+
+    public static final String SYSTEM_INFO = "system_info";
+
+    /**
+     * 跨服务调用验证KEY
+     */
+    public static final String INNER_TOKEN_KEY = SaSameUtil.SAME_TOKEN;
+
+    /**
+     * 网关调用验证KEY
+     */
+    public static final String INNER_GATEWAY_TOKEN_KEY = INNER_TOKEN_KEY + "_GATEWAY";
+
+    public static final String TENANT_SESSION = "tenant:";
+
+    public static final String TOKEN_PREFIX = "bearer";
+    public static final String TOKEN_PREFIX_SP = TOKEN_PREFIX + " ";
+
+    public static final String PARAMS_JNPF_TICKET = "jnpf_ticket";
+    public static final String PARAMS_SSO_LOGOUT_TICKET = "ticket";
+
+    public static final Integer REDIRECT_PAGETYPE_LOGIN = 1;
+    public static final Integer REDIRECT_PAGETYPE_LOGOUT = 2;
+
+    public static final Integer TMP_TOKEN_UNLOGIN = -1;
+    public static final Integer TMP_TOKEN_ERRLOGIN = -2;
+
+    public static final String ONLINE_TICKET_KEY = "online_ticket:";
+    public static final String ONLINE_TICKET_TOKEN = "online_token";
+
+    public static final String JWT_SECRET = "WviMjFNC72VKwGqm5LPoheQo5XN9iN4d";
+
+    /**
+     * clientId
+     */
+    public static final String Client_Id = "Client_Id";
+
+
+    /**
+     * 用户信息获取方式 account
+     */
+    public static final String USERDETAIL_ACCOUNT = UserDetailService.USER_DETAIL_PREFIX + "UserAccount";
+    /**
+     * 用户信息获取方式 user_id
+     */
+    public static final String USERDETAIL_USER_ID = UserDetailService.USER_DETAIL_PREFIX + "UserId";
+
+    /**
+     * 认证方式 常规账号密码
+     */
+    public static final String GRANT_TYPE_PASSWORD = "password";
+    /**
+     * 认证方式 单点 CAS
+     */
+    public static final String GRANT_TYPE_CAS = "cas";
+    /**
+     * 认证方式 单点 OAUTH
+     */
+    public static final String GRANT_TYPE_OAUTH = "auth2";
+
+}

BIN
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/consts/DeviceType.class


+ 40 - 0
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/consts/DeviceType.java

@@ -0,0 +1,40 @@
+package jnpf.consts;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+
+/**
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ */
+@Getter
+@AllArgsConstructor
+public enum DeviceType {
+
+    /**
+     * pc端
+     */
+    PC("PC"),
+
+    /**
+     * app端 手机都归为移动 自行扩展
+     */
+    APP("APP"),
+
+    /**
+     * 程序运行中使用的无限制临时用户
+     */
+    TEMPUSER("TEMPUSER"),
+
+
+    /**
+     * 程序运行中使用的限制临时用户, 不可访问主系统, CurrentUser接口报错
+     */
+    TEMPUSERLIMITED("TEMPUSERLIMITED");
+
+
+    private final String device;
+
+}

BIN
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/consts/LoginTicketStatus.class


+ 46 - 0
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/consts/LoginTicketStatus.java

@@ -0,0 +1,46 @@
+package jnpf.consts;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+
+/**
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ */
+@Getter
+@AllArgsConstructor
+public enum LoginTicketStatus {
+
+    /**
+     * 登录成功
+     */
+    Success(1),
+    /**
+     * 未登录
+     */
+    UnLogin(2),
+    /**
+     * 登录失败
+     */
+    ErrLogin(3),
+    /**
+     * 未绑定
+     */
+    UnBind(4),
+    /**
+     * 失效
+     */
+    Invalid(5),
+    /**
+     * 多租户
+     */
+    Multitenancy(6),
+    /**
+     * 第三方账号未绑定账号,请绑定后重试
+     */
+    UnBindMes(7),;
+
+    private int status;
+}

BIN
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/consts/ScanCodeTicketStatus.class


+ 29 - 0
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/consts/ScanCodeTicketStatus.java

@@ -0,0 +1,29 @@
+package jnpf.consts;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum ScanCodeTicketStatus {
+
+    /**
+     * 已失效
+     */
+    Invalid(-1),
+    /**
+     * 未扫码
+     */
+    UnScanCode(0),
+    /**
+     * 已扫码
+     */
+    ScanCode(1),
+    /**
+     * 登录成功
+     */
+    Success(2);
+
+
+    private int status;
+}

BIN
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/granter/AbstractTokenGranter.class


+ 286 - 0
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/granter/AbstractTokenGranter.java

@@ -0,0 +1,286 @@
+package jnpf.granter;
+
+import cn.dev33.satoken.context.SaHolder;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+import jnpf.base.ActionResult;
+import jnpf.base.UserInfo;
+import jnpf.config.ConfigValueUtil;
+import jnpf.constant.MsgCode;
+import jnpf.consts.AuthConsts;
+import jnpf.consts.DeviceType;
+import jnpf.consts.LoginTicketStatus;
+import jnpf.exception.LoginException;
+import jnpf.exception.TenantDatabaseException;
+import jnpf.model.BaseSystemInfo;
+import jnpf.model.LoginTicketModel;
+import jnpf.model.logout.LogoutResultModel;
+import jnpf.model.tenant.TenantVO;
+import jnpf.service.LoginService;
+import jnpf.util.RedisUtil;
+import jnpf.util.TenantProvider;
+import jnpf.util.TicketUtil;
+import jnpf.util.UserProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.Ordered;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.PathMatcher;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static jnpf.consts.AuthConsts.DEF_TENANT_DB;
+import static jnpf.consts.AuthConsts.DEF_TENANT_ID;
+
+
+/**
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ */
+public abstract class AbstractTokenGranter implements TokenGranter, Ordered {
+
+
+    @Autowired(required = false)
+    protected LoginService loginService;
+    @Autowired
+    protected ConfigValueUtil configValueUtil;
+    @Autowired
+    protected RedisUtil redisUtil;
+
+    protected static PathMatcher pathMatcher = new AntPathMatcher();
+
+    private String authenticationUrl;
+
+
+    public AbstractTokenGranter(String authenticationUrl){
+        this.authenticationUrl = authenticationUrl;
+    }
+
+
+    /**
+     * 最终登录用户
+     * @param userInfo 包含账户名, 登录方式
+     * @return
+     */
+    protected String loginAccount(UserInfo userInfo, BaseSystemInfo baseSystemInfo) throws LoginException {
+        try{
+            //获取用户实现类接口名称
+            userInfo.setUserDetailKey(getUserDetailKey());
+            //获取登录信息
+            userInfo = getUserInfo(userInfo, baseSystemInfo);
+            //预登陆
+            preLogin(userInfo, baseSystemInfo);
+            //登录
+            login(userInfo, baseSystemInfo);
+        }catch(Exception e){
+            try {
+                loginFailure(userInfo, baseSystemInfo, e);
+            }catch (Exception e1){
+                throw e1;
+            }
+            throw e;
+        }
+        loginSuccess(userInfo, baseSystemInfo);
+        //返回token信息
+        return userInfo.getToken();
+    }
+
+    /**
+     * 切换多租户
+     * @param userInfo
+     * @return userAccount, tenantId, tenandDb
+     * @throws LoginException
+     */
+    protected UserInfo switchTenant(UserInfo userInfo) throws LoginException {
+        if (configValueUtil.isMultiTenancy()) {
+            userInfo = loginService.getTenantAccount(userInfo);
+            return userInfo;
+
+        }
+        userInfo.setTenantId(DEF_TENANT_ID);
+        userInfo.setTenantDbConnectionString(DEF_TENANT_DB);
+        userInfo.setTenantDbType(TenantVO.NONE);
+        return userInfo;
+    }
+
+    /**
+     * 获取系统配置
+     * @param userInfo
+     * @return
+     */
+    protected BaseSystemInfo getSysconfig(UserInfo userInfo) throws LoginException {
+        BaseSystemInfo baseSystemInfo = loginService.getBaseSystemConfig(userInfo.getTenantId());
+        if(baseSystemInfo != null && baseSystemInfo.getSingleLogin() != null){
+            TenantProvider.setBaseSystemInfo(baseSystemInfo);
+        }else{
+            throw new TenantDatabaseException().setLogMsg(MsgCode.LOG110.get());
+        }
+        return baseSystemInfo;
+    }
+
+    /**
+     * 获取登录设备
+     * @return
+     */
+    protected DeviceType getDeviceType(){
+        return UserProvider.getDeviceForAgent();
+    }
+
+
+    /**
+     * 生成登录用户信息
+     * @param userInfo
+     * @return
+     */
+    protected UserInfo getUserInfo(UserInfo userInfo, BaseSystemInfo sysConfigInfo) throws LoginException {
+        userInfo.setGrantType(getGrantType());
+        userInfo = loginService.userInfo(userInfo, sysConfigInfo);
+        return userInfo;
+    }
+
+
+    /**
+     * 登录前执行
+     * @param userInfo
+     * @param baseSystemInfo
+     */
+    protected void preLogin(UserInfo userInfo, BaseSystemInfo baseSystemInfo) throws LoginException {
+
+    }
+
+    /**
+     * 登录操作
+     * @param userInfo
+     * @param baseSystemInfo
+     */
+    protected void login(UserInfo userInfo, BaseSystemInfo baseSystemInfo) throws LoginException {
+        UserProvider.login(userInfo, getLoginModel(userInfo, baseSystemInfo));
+    }
+
+    /**
+     * 登录成功触发
+     * @param userInfo
+     * @param baseSystemInfo
+     */
+    protected void loginSuccess(UserInfo userInfo, BaseSystemInfo baseSystemInfo){
+
+    }
+
+    /**
+     * 登录失败触发
+     * @param baseSystemInfo
+     */
+    protected void loginFailure(UserInfo userInfo, BaseSystemInfo baseSystemInfo, Exception e){
+
+    }
+
+    protected abstract String getUserDetailKey();
+
+    protected String createToken(UserInfo userInfo, BaseSystemInfo baseSystemInfo){
+        //登录
+        UserProvider.login(userInfo, getLoginModel(userInfo, baseSystemInfo));
+        return StpUtil.getTokenValueNotCut();
+    }
+
+    /**
+     * 更新轮询结果为成功
+     */
+    protected void updateTicketSuccess(UserInfo userInfo){
+        String ticket = getJnpfTicket();
+        if(!ticket.isEmpty()) {
+            LoginTicketModel loginTicketModel = new LoginTicketModel()
+                            .setStatus(LoginTicketStatus.Success.getStatus())
+                            .setValue(StpUtil.getTokenValueNotCut())
+                            .setTheme(userInfo.getTheme());
+            TicketUtil.updateTicket(ticket, loginTicketModel, null);
+        }
+    }
+
+    /**
+     * 更新轮询结果为失败
+     */
+    protected void updateTicketError(String msg){
+        String ticket = getJnpfTicket();
+        if(!ticket.isEmpty()) {
+            LoginTicketModel loginTicketModel = new LoginTicketModel()
+                            .setStatus(LoginTicketStatus.ErrLogin.getStatus())
+                            .setValue(msg);
+            TicketUtil.updateTicket(ticket, loginTicketModel, null);
+        }
+    }
+
+
+    /**
+     * 获取轮询ticket
+     * @return
+     */
+    protected String getJnpfTicket(){
+        return SaHolder.getRequest().getParam(AuthConsts.PARAMS_JNPF_TICKET, "");
+    }
+
+    protected boolean isValidJnpfTicket(){
+        String jnpfTicket = getJnpfTicket();
+        if(!jnpfTicket.isEmpty()){
+            LoginTicketModel loginTicketModel = TicketUtil.parseTicket(jnpfTicket);
+            if(loginTicketModel == null){
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * 获取登录参数
+     * @param userInfo
+     * @param baseSystemInfo
+     * @return
+     */
+    protected SaLoginParameter getLoginModel(UserInfo userInfo, BaseSystemInfo baseSystemInfo){
+        SaLoginParameter loginModel = new SaLoginParameter();
+        loginModel.setTimeout(userInfo.getTokenTimeout() * 60L);
+        loginModel.setExtraData(getTokenExtraData(userInfo, baseSystemInfo));
+        if(userInfo.getLoginDevice() == null) {
+            loginModel.setDevice(getDeviceType().getDevice());
+            userInfo.setLoginDevice(loginModel.getDeviceType());
+        }else{
+            loginModel.setDevice(userInfo.getLoginDevice());
+        }
+        return loginModel;
+    }
+
+    /**
+     * 获取额外的JWT内容
+     * @param userInfo
+     * @param baseSystemInfo
+     * @return
+     */
+    protected Map<String, Object> getTokenExtraData(UserInfo userInfo, BaseSystemInfo baseSystemInfo){
+        Map<String, Object> tokenInfo = new HashMap<>();
+//        tokenInfo.put("token", StpUtil.getTokenValue());
+        tokenInfo.put("singleLogin", baseSystemInfo == null ? null : baseSystemInfo.getSingleLogin());
+        tokenInfo.put("user_name", userInfo.getUserAccount());
+        tokenInfo.put("user_id", userInfo.getUserId());
+        tokenInfo.put("exp", userInfo.getOverdueTime().getTime());
+        tokenInfo.put("token", userInfo.getId());
+        return tokenInfo;
+    }
+
+    @Override
+    public ActionResult<LogoutResultModel> logout() {
+        UserProvider.logout();
+        return ActionResult.success();
+    }
+
+    protected abstract String getGrantType();
+
+
+    @Override
+    public boolean requiresAuthentication() {
+        String path = SaHolder.getRequest().getRequestPath();
+        if(path != null && path.startsWith("/api/oauth")){
+            path = path.replace("/api/oauth", "");
+        }
+        return pathMatcher.match(authenticationUrl, path);
+    }
+}

BIN
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/granter/TokenGranter.class


+ 24 - 0
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/granter/TokenGranter.java

@@ -0,0 +1,24 @@
+package jnpf.granter;
+
+import jnpf.base.ActionResult;
+import jnpf.exception.LoginException;
+import jnpf.model.logout.LogoutResultModel;
+
+import java.util.Map;
+
+
+/**
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ */
+public interface TokenGranter {
+
+    ActionResult granter(Map<String, String> loginParameters) throws LoginException;
+
+
+    ActionResult<LogoutResultModel> logout();
+
+    boolean requiresAuthentication();
+
+}

BIN
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/granter/TokenGranterBuilder.class


+ 83 - 0
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/granter/TokenGranterBuilder.java

@@ -0,0 +1,83 @@
+package jnpf.granter;
+
+import jnpf.base.UserInfo;
+import jnpf.config.JnpfOauthConfig;
+import jnpf.constant.MsgCode;
+import jnpf.consts.AuthConsts;
+import jnpf.exception.LoginException;
+import jnpf.util.UserProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+/**
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ */
+@Component
+public class TokenGranterBuilder {
+
+    @Autowired
+    private JnpfOauthConfig oauthConfig;
+
+    private final Map<String, TokenGranter> granterPool = new ConcurrentHashMap<>();
+
+    public TokenGranterBuilder(Map<String, TokenGranter> granterPool) {
+        granterPool.forEach(this.granterPool::put);
+    }
+
+    /**
+     * 获取TokenGranter
+     *
+     * @param grantType 授权类型
+     * @return ITokenGranter
+     */
+    public TokenGranter getGranter(String grantType) throws LoginException {
+        TokenGranter tokenGranter = null;
+        // 内部登录需要放行
+        if(!oauthConfig.getSsoEnabled() || "tempuser".equalsIgnoreCase(grantType)) {
+            tokenGranter = granterPool.get(grantType);
+        }
+        if(tokenGranter == null){
+            //URL匹配
+            for (TokenGranter value : granterPool.values()) {
+                if(value.requiresAuthentication()){
+                    tokenGranter = value;
+                    break;
+                }
+            }
+        }
+        if(tokenGranter == null){
+            if(oauthConfig.getSsoEnabled()) {
+                throw new LoginException(MsgCode.LOG111.get());
+            }else{
+                throw new LoginException(MsgCode.LOG112.get());
+            }
+        }
+        return tokenGranter;
+    }
+
+
+    /**
+     * 获取当前登录用户的TokenGranter
+     * @return
+     * @throws LoginException
+     */
+    public TokenGranter getGranterByLogin(String grandType) {
+        if(grandType == null || grandType.isEmpty()){
+            UserInfo userInfo = UserProvider.getUser();
+            if(userInfo.getGrantType() != null){
+                grandType = userInfo.getGrantType();
+            }else {
+                grandType = AuthConsts.GRANT_TYPE_PASSWORD;
+            }
+        }
+        return granterPool.get(grandType);
+    }
+
+
+}

BIN
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/granter/UserDetailsServiceBuilder.class


+ 38 - 0
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/granter/UserDetailsServiceBuilder.java

@@ -0,0 +1,38 @@
+package jnpf.granter;
+
+import jnpf.consts.AuthConsts;
+import jnpf.service.UserDetailService;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ */
+@Component
+public class UserDetailsServiceBuilder {
+
+
+    private final Map<String, UserDetailService> userDetailServices = new ConcurrentHashMap<>();
+
+    public UserDetailsServiceBuilder(Map<String, UserDetailService> userDetailServices) {
+        userDetailServices.forEach(this.userDetailServices::put);
+    }
+
+
+    /**
+     * 根据类型获取合适的UserDetailService
+     * @param detailType
+     * @return
+     */
+    public UserDetailService getUserDetailService(String detailType){
+        if(detailType == null){
+            detailType = AuthConsts.USERDETAIL_ACCOUNT;
+        }
+        return userDetailServices.get(detailType);
+    }
+
+}

BIN
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/model/LoginTicketModel.class


+ 37 - 0
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/model/LoginTicketModel.java

@@ -0,0 +1,37 @@
+package jnpf.model;
+
+import jnpf.consts.LoginTicketStatus;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.NonNull;
+import lombok.experimental.Accessors;
+
+/**
+ * 轮询登录模型
+ */
+@Data
+@Accessors(chain = true)
+public class LoginTicketModel {
+
+    /**
+     * 状态
+     * @see LoginTicketStatus
+     */
+    @NonNull
+    private int status = LoginTicketStatus.UnLogin.getStatus();
+
+    /**
+     * 额外的值, 登录Token、第三方登录的ID
+     */
+    private String value;
+
+    /**
+     * 前端主题
+     */
+    private String theme;
+    /**
+     * 票据有效期, 时间戳
+     */
+    private Long ticketTimeout;
+
+}

BIN
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/model/ScanCodeLoginConfigModel.class


+ 25 - 0
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/model/ScanCodeLoginConfigModel.java

@@ -0,0 +1,25 @@
+package jnpf.model;
+
+import jnpf.consts.ScanCodeTicketStatus;
+import lombok.Data;
+import lombok.NonNull;
+
+@Data
+public class ScanCodeLoginConfigModel {
+
+    /**
+     * 状态
+     * @see ScanCodeTicketStatus
+     */
+    @NonNull
+    private int status = ScanCodeTicketStatus.UnScanCode.getStatus();
+
+    /**
+     * 额外的值, 登录Token、第三方登录的ID
+     */
+    private String value;
+    /**
+     * 票据有效期, 时间戳
+     */
+    private Long ticketTimeout;
+}

BIN
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/service/LoginService.class


+ 58 - 0
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/service/LoginService.java

@@ -0,0 +1,58 @@
+package jnpf.service;
+
+import jnpf.base.UserInfo;
+import jnpf.exception.LoginException;
+import jnpf.model.BaseSystemInfo;
+import jnpf.model.login.PcUserVO;
+
+/**
+ * 登陆业务层
+ *
+ * @author JNPF开发平台组
+ * @version V3.1.0
+ * @copyright 引迈信息技术有限公司(https://www.jnpfsoft.com)
+ * @date 2021-03-23
+ */
+public interface LoginService {
+
+    /**
+     * 租戶登录验证
+     *
+     * @param userInfo
+     * @return userAccount, tenantId, tenandDb
+     * @throws LoginException
+     */
+    UserInfo getTenantAccount(UserInfo userInfo) throws LoginException;
+
+    /**
+     * 生成用户登录信息
+     *
+     * @param userInfo      账户信息
+     * @param sysConfigInfo 系统配置
+     * @return
+     * @throws LoginException
+     */
+    UserInfo userInfo(UserInfo userInfo, BaseSystemInfo sysConfigInfo) throws LoginException;
+
+    /**
+     * 获取用户登陆信息
+     *
+     * @return
+     */
+    PcUserVO getCurrentUser(String type, String systemCode, Integer isBackend);
+
+    /**
+     * 修改密码信息发送
+     *
+     * @return
+     */
+    void updatePasswordMessage();
+
+    /**
+     * @param tenantId
+     * @return
+     */
+    BaseSystemInfo getBaseSystemConfig(String tenantId);
+
+
+}

BIN
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/service/UserDetailService.class


+ 19 - 0
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/service/UserDetailService.java

@@ -0,0 +1,19 @@
+package jnpf.service;
+
+import jnpf.base.UserInfo;
+import jnpf.exception.LoginException;
+import org.springframework.core.Ordered;
+
+public interface UserDetailService extends Ordered {
+
+    static final String USER_DETAIL_PREFIX = "USERDETAIL_";
+
+    /**
+     * 获取用户信息
+     * @param userInfo
+     * @return UserEntity
+     * @param <T>
+     */
+    <T> T loadUserEntity(UserInfo userInfo) throws LoginException;
+
+}

BIN
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/util/TenantProvider.class


+ 121 - 0
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/util/TenantProvider.java

@@ -0,0 +1,121 @@
+package jnpf.util;
+
+import cn.dev33.satoken.session.SaSession;
+import cn.dev33.satoken.session.SaSessionCustomUtil;
+import jnpf.model.BaseSystemInfo;
+import lombok.extern.slf4j.Slf4j;
+
+import static jnpf.consts.AuthConsts.DEF_TENANT_ID;
+import static jnpf.consts.AuthConsts.TENANT_SESSION;
+
+
+/**
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ */
+@Slf4j
+public class TenantProvider {
+
+
+    private static final long tenantTimeout = 60 * 60 * 24 * 30L;
+
+    /**
+     * 获取租户Redis存储对象
+     *
+     * @param tenantId
+     * @return
+     */
+    public static SaSession getTenantSession(String tenantId) {
+        if (tenantId == null) {
+//            tenantId = TenantHolder.getDatasourceId();
+//            if (tenantId == null) {
+                tenantId = DEF_TENANT_ID;
+//            }
+        }
+        SaSession saSession = SaSessionCustomUtil.getSessionById(TENANT_SESSION + tenantId);
+        if (saSession != null && !saSession.get("init", false)) {
+            saSession.set("init", true);
+            saSession.updateTimeout(tenantTimeout);
+        }
+        return saSession;
+    }
+
+    /**
+     * 存入租户缓存空间
+     *
+     * @param tenantId
+     * @param key
+     * @param value
+     */
+    public static void putTenantCache(String tenantId, String key, Object value) {
+        SaSession saSession = getTenantSession(tenantId);
+        if (saSession != null) {
+            saSession.set(key, value).updateTimeout(tenantTimeout);
+        }
+    }
+
+    /**
+     * 获取租户缓存数据
+     *
+     * @param tenantId
+     * @param key
+     * @param <T>
+     * @return
+     */
+    public static <T> T getTenantCache(String tenantId, String key) {
+        SaSession saSession = getTenantSession(tenantId);
+        if (saSession != null) {
+            return (T) saSession.get(key);
+        }
+        return null;
+    }
+
+    /**
+     * 删除租户缓存数据
+     *
+     * @param tenantId
+     * @param key
+     */
+    public static void delTenantCache(String tenantId, String key) {
+        SaSession saSession = getTenantSession(tenantId);
+        if (saSession != null) {
+            saSession.delete(key);
+        }
+    }
+
+    public static void renewTimeout(String tenantId, long timeout) {
+        if (tenantId == null) {
+            tenantId = DEF_TENANT_ID;
+        }
+        SaSession saSession = getTenantSession(tenantId);
+        if (saSession != null) {
+            saSession.updateTimeout(timeout);
+        }
+    }
+
+
+    private static ThreadLocal<BaseSystemInfo> systemInfoThreadLocal = new ThreadLocal<>();
+
+    /**
+     * 获取系统设置信息
+     *
+     * @return
+     */
+    public static BaseSystemInfo getBaseSystemInfo() {
+        BaseSystemInfo systemInfo = systemInfoThreadLocal.get();
+        return systemInfo;
+    }
+
+
+    public static void setBaseSystemInfo(BaseSystemInfo baseSystemInfo) {
+        systemInfoThreadLocal.set(baseSystemInfo);
+    }
+
+
+    public static void clearBaseSystemIfo() {
+        systemInfoThreadLocal.remove();
+    }
+
+
+
+}

BIN
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/util/TicketUtil.class


+ 68 - 0
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/util/TicketUtil.java

@@ -0,0 +1,68 @@
+package jnpf.util;
+
+import cn.dev33.satoken.SaManager;
+import cn.dev33.satoken.temp.SaTempUtil;
+import cn.dev33.satoken.util.SaTokenConsts;
+import org.springframework.stereotype.Component;
+
+
+/**
+ *
+ * @author JNPF开发平台组
+ * @copyright 引迈信息技术有限公司
+ */
+@Component
+public class TicketUtil {
+
+
+    /**
+     * 创建临时TOKEN
+     * @param value 值
+     * @param timeout 有效时间, 秒
+     * @return
+     */
+    public static String createTicket(Object value, long timeout){
+        return SaTempUtil.createToken(value, timeout);
+    }
+
+    /**
+     * 获取临时TOKEN内的数据
+     * @see #createTicket(Object, long)
+     * @param ticket 票据
+     * @return
+     * @param <T>
+     */
+    public static <T> T parseTicket(String ticket){
+        return (T) SaTempUtil.parseToken(ticket);
+    }
+
+    /**
+     * 移除临时Token
+     * @param ticket
+     */
+    public static void deleteTicket(String ticket){
+        SaTempUtil.deleteToken(ticket);
+    }
+
+    /**
+     * 更新Ticket内的内容
+     * @see #createTicket(Object, long)
+     * @param ticket 票据
+     * @param value 新值
+     * @param timeout 超时时间, 秒, 可空为不更新
+     */
+    public static void updateTicket(String ticket, Object value, Long timeout){
+        Object obj = parseTicket(ticket);
+        if(obj == null) return;
+        String key = getTicketKey(ticket);
+        if(timeout != null){
+            SaManager.getSaTokenDao().setObject(key, value, timeout);
+        }else{
+            SaManager.getSaTokenDao().updateObject(key, value);
+        }
+    }
+
+    private static String getTicketKey(String ticket){
+        return SaManager.getSaTempTemplate().splicingTempTokenSaveKey(ticket);
+    }
+}

BIN
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/util/UserProvider.class


+ 549 - 0
jnpf-boot-common/jnpf-common-auth/target/classes/jnpf/util/UserProvider.java

@@ -0,0 +1,549 @@
+package jnpf.util;
+
+import cn.dev33.satoken.same.SaSameUtil;
+import cn.dev33.satoken.session.SaSession;
+import cn.dev33.satoken.session.SaTerminalInfo;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+import cn.hutool.core.text.StrPool;
+import jnpf.base.UserInfo;
+import jnpf.consts.DeviceType;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.util.ObjectUtils;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static jnpf.consts.AuthConsts.TOKEN_PREFIX_SP;
+
+
+/**
+ * @author JNPF开发平台组
+ * @version V3.1.0
+ * @copyright 引迈信息技术有限公司
+ * @date 2021/3/16 10:57
+ */
+@Slf4j
+@Component
+public class UserProvider {
+
+    private static RedisUtil redisUtil;
+    private static CacheKeyUtil cacheKeyUtil;
+
+    public static final String USER_INFO_KEY = "userInfo";
+
+    private static final ThreadLocal<UserInfo> USER_CACHE = new ThreadLocal<>();
+
+    public UserProvider(RedisUtil redisUtil, CacheKeyUtil cacheKeyUtil) {
+        UserProvider.redisUtil = redisUtil;
+        UserProvider.cacheKeyUtil = cacheKeyUtil;
+    }
+
+
+
+    // =================== 登录相关操作 ===================
+
+    /**
+     * 登录系统 适用于Request环境
+     *
+     * @param userInfo 登录用户信息
+     */
+    public static void login(UserInfo userInfo) {
+        setLocalLoginUser(userInfo);
+        StpUtil.login(splicingLoginId(userInfo.getUserId()));
+        userInfo.setToken(StpUtil.getTokenValueNotCut());
+        userInfo.setSecurityKey(DesUtil.aesOrDecode(TicketUtil.createTicket(userInfo.getToken(), 10*24*60*60000L), true, true));
+        setLoginUser(userInfo);
+    }
+
+    /**
+     * 登录系统 适用于Request环境
+     *
+     * @param userInfo   用户信息
+     * @param loginModel 登录参数
+     */
+    public static void login(UserInfo userInfo, SaLoginParameter loginModel) {
+        setLocalLoginUser(userInfo);
+        StpUtil.login(splicingLoginId(userInfo.getUserId()), loginModel);
+        userInfo.setToken(StpUtil.getTokenValueNotCut());
+        userInfo.setSecurityKey(DesUtil.aesOrDecode(TicketUtil.createTicket(userInfo.getToken(), 10*24*60*60000L), true, true));
+        setLoginUser(userInfo);
+    }
+
+    /**
+     * 适用于非Request环境
+     * @param userInfo
+     * @param loginModel
+     */
+    public static void loginNoRequest(UserInfo userInfo, SaLoginParameter loginModel) {
+        setLocalLoginUser(userInfo);
+        String token = StpUtil.createLoginSession(splicingLoginId(userInfo.getUserId()), loginModel);
+        userInfo.setToken(TOKEN_PREFIX_SP + token);
+        setLoginUser(userInfo);
+    }
+
+
+    // =================== 登录用户ID相关操作 ===================
+
+    /**
+     * 获取指定TOKEN用户ID
+     *
+     * @param token
+     * @return
+     */
+    public static String getLoginUserId(String token) {
+        String loginId = (String) StpUtil.getLoginIdByToken(token);
+        return parseLoginId(loginId);
+    }
+
+    /**
+     * 获取当前用户ID, 包含临时切换用户ID
+     *
+     * @return
+     */
+    public static String getLoginUserId() {
+        String loginId = getUser().getUserId();
+        return parseLoginId(loginId);
+    }
+
+
+
+    // =================== 用户ID拼接相关操作 ===================
+
+
+    /**
+     * 拼接租户下的用户ID
+     *
+     * @param userId
+     * @return
+     */
+    public static String splicingLoginId(String userId) {
+        return splicingLoginId(userId, null);
+    }
+
+    /**
+     * 拼接租户下的用户ID
+     * @param userId
+     * @param tenantId
+     * @return
+     */
+    private static String splicingLoginId(String userId, String tenantId) {
+        if(StringUtil.isEmpty(tenantId)){
+            tenantId = TenantHolder.getDatasourceId();
+        }
+        if (!StringUtil.isEmpty(tenantId)) {
+            return tenantId + StrPool.COLON + userId;
+        }
+        return userId;
+    }
+
+    /**
+     * 解析租户下的登录ID
+     * @param loginId
+     * @return
+     */
+    private static String parseLoginId(String loginId) {
+        if (loginId != null && loginId.contains(StrPool.COLON)) {
+            loginId = loginId.substring(loginId.indexOf(StrPool.COLON) + 1);
+        }
+        return loginId;
+    }
+
+    /**
+     * Token是否有效
+     *
+     * @param token
+     * @return
+     */
+    public static Boolean isValidToken(String token) {
+        UserInfo userInfo = getUser(token);
+        return userInfo.getUserId() != null;
+    }
+
+
+    // =================== UserInfo缓存相关操作 ===================
+
+
+    /**
+     * 设置Redis用户数据
+     */
+    public static void setLoginUser(UserInfo userInfo) {
+        StpUtil.getTokenSessionByToken(cutToken(userInfo.getToken())).set(USER_INFO_KEY, userInfo);
+    }
+
+    /**
+     * 设置本地用户数据
+     */
+    public static void setLocalLoginUser(UserInfo userInfo) {
+        USER_CACHE.set(userInfo);
+    }
+
+    /**
+     * 获取本地用户数据
+     */
+    public static UserInfo getLocalLoginUser() {
+        return USER_CACHE.get();
+    }
+
+    /**
+     * 清空本地用户数据
+     */
+    public static void clearLocalUser() {
+        USER_CACHE.remove();
+    }
+
+
+
+
+    /**
+     * 获取用户缓存
+     * 保留旧方法
+     *
+     * @param token
+     * @return
+     */
+    public UserInfo get(String token) {
+        return UserProvider.getUser(token);
+    }
+
+    /**
+     * 获取用户缓存
+     *
+     * @return
+     */
+    public UserInfo get() {
+        return UserProvider.getUser();
+    }
+
+
+    /**
+     * 根据用户ID, 租户ID获取随机获取一个UserInfo
+     * @param userId
+     * @param tenantId
+     * @return
+     */
+    public static UserInfo getUser(String userId, String tenantId){
+        return getUser(userId, tenantId, null, null);
+    }
+
+    /**
+     * 根据用户ID, 租户ID, 设备类型获取随机获取一个UserInfo
+     * @param userId
+     * @param tenantId
+     * @param includeDevice 指定的设备类型中查找
+     * @param excludeDevice 排除指定设备类型
+     * @return
+     */
+    public static UserInfo getUser(String userId, String tenantId, List<String> includeDevice, List<String> excludeDevice){
+        SaSession session = StpUtil.getSessionByLoginId(splicingLoginId(userId, tenantId), false);
+        if (session != null) {
+            List<SaTerminalInfo> tokenSignList = session.getTerminalList();
+            if (!tokenSignList.isEmpty()) {
+                tokenSignList = tokenSignList.stream().filter(tokenSign -> {
+                    if(!ObjectUtils.isEmpty(excludeDevice)){
+                        if(excludeDevice.contains(tokenSign.getDeviceType())){
+                            return false;
+                        }
+                    }
+                    if(!ObjectUtils.isEmpty(includeDevice)){
+                        if(!includeDevice.contains(tokenSign.getDeviceType())){
+                            return false;
+                        }
+                    }
+                    return true;
+                }).collect(Collectors.toList());
+                if(!tokenSignList.isEmpty()){
+                    return getUser(tokenSignList.get(0).getTokenValue());
+                }
+            }
+        }
+        return new UserInfo();
+    }
+
+    /**
+     * 获取用户缓存
+     *
+     * @param token
+     * @return
+     */
+    public static UserInfo getUser(String token) {
+        UserInfo userInfo = null;
+        String tokens = null;
+        if (token != null) {
+            tokens = cutToken(token);
+        } else {
+            try {
+                //处理非Web环境报错
+                tokens = StpUtil.getTokenValue();
+            } catch (Exception e) {
+            }
+        }
+        if (tokens != null) {
+            if (StpUtil.getLoginIdByToken(tokens) != null) {
+                userInfo = (UserInfo) StpUtil.getTokenSessionByToken(tokens).get(USER_INFO_KEY);
+            }
+        }
+        if (userInfo == null) {
+            userInfo = new UserInfo();
+        }
+        return userInfo;
+    }
+
+    /**
+     * 获取用户缓存
+     *
+     * @return
+     */
+    public static UserInfo getUser() {
+//        if(StpUtil.getTokenValue() == null){
+//            return  new UserInfo();
+//        }
+        UserInfo userInfo = USER_CACHE.get();
+        if (userInfo != null) {
+            return userInfo;
+        }
+        userInfo = UserProvider.getUser(null);
+        if (userInfo.getUserId() != null) {
+            USER_CACHE.set(userInfo);
+        }
+        return userInfo;
+    }
+
+    // =================== Token相关操作 ===================
+
+    /**
+     * 去除Token前缀
+     *
+     * @param token
+     * @return
+     */
+    public static String cutToken(String token) {
+        if (token != null && token.startsWith(TOKEN_PREFIX_SP)) {
+            token = token.substring(TOKEN_PREFIX_SP.length());
+        }
+        return token;
+    }
+
+    /**
+     * 获取token
+     */
+    public static String getToken() {
+        String toke = getAuthorize();
+        return toke;
+    }
+
+
+    /**
+     * 获取Authorize
+     */
+    public static String getAuthorize() {
+        String authorize = ServletUtil.getHeader(Constants.AUTHORIZATION);
+        return authorize;
+    }
+
+
+    /**
+     * TOKEN续期
+     */
+    public static void renewTimeout(String token) {
+        if (StpUtil.getTokenValue() != null) {
+            UserInfo userInfo = UserProvider.getUser(token);
+            if(userInfo.getUserId() == null || userInfo.getTokenTimeout() == null) {
+                //避免请求过网关之后TOKEN失效(携带TOKEN调用登录接口之后账号被顶替)
+                return;
+            }
+            StpUtil.renewTimeout(userInfo.getTokenTimeout() * 60L);
+            if(userInfo.getIsAdministrator()) {
+                SaSession saSession = StpUtil.getSessionByLoginId(splicingLoginId(Constants.ADMIN_KEY), false);
+                if (saSession != null) {
+                    saSession.updateTimeout(userInfo.getTokenTimeout() * 60L);
+                }
+            }
+        }
+    }
+
+    /**
+     * 获取所有Token记录
+     * 包含无效状态的用户、临时用户
+     *
+     * @return
+     */
+    public static List<String> getLoginUserListToken() {
+        return StpUtil.searchTokenValue("", -1, -1, true).stream().map(token -> token.replace(StpUtil.stpLogic.splicingKeyTokenValue(""), "")).collect(Collectors.toList());
+    }
+
+
+    // =================== 临时Token相关操作 ===================
+
+
+    /**
+     * 获取内部服务传递验证TOKEN
+     *
+     * @return
+     */
+    public static String getInnerAuthToken() {
+        return SaSameUtil.getToken();
+    }
+
+    /**
+     * 验证内部传递Token是否有效 抛出异常
+     * @param token
+     */
+    public static void checkInnerToken(String token){
+        SaSameUtil.checkToken(token);
+    }
+
+    /**
+     * 验证内部传递Token是否有效
+     * @param token
+     */
+    public static boolean isValidInnerToken(String token){
+        return SaSameUtil.isValid(token);
+    }
+
+
+    // =================== 退出相关操作 ===================
+
+
+    /**
+     * 根据用户ID踢出全部用户
+     * @param userId
+     */
+    public static void kickoutByUserId(String userId, String tenantId) {
+        StpUtil.kickout(splicingLoginId(userId, tenantId));
+    }
+
+    /**
+     * 根据Token踢出指定会话
+     * @param tokens
+     */
+    public static void kickoutByToken(String... tokens) {
+        for (String token : tokens) {
+            StpUtil.kickoutByTokenValue(token);
+        }
+    }
+
+    /**
+     * 退出当前Token, 不清除用户其他系统缓存
+     */
+    public static void logout() {
+        StpUtil.logout();
+
+    }
+
+    /**
+     * 退出指定Token, 不清除用户其他系统缓存
+     *
+     * @param token
+     */
+    public static void logoutByToken(String token) {
+        if (token == null) {
+            logout();
+        } else {
+            StpUtil.logoutByTokenValue(cutToken(token));
+        }
+    }
+
+    /**
+     * 退出指定设备类型的用户的全部登录信息, 不清除用户其他系统缓存
+     *
+     * @param userId
+     * @param deviceType
+     */
+    public static void logoutByUserId(String userId, DeviceType deviceType) {
+        StpUtil.logout(splicingLoginId(userId), deviceType.getDevice());
+    }
+
+    /**
+     * 退出指定用户的全部登录信息, 清除相关缓存
+     *
+     * @param userId
+     */
+    public static void logoutByUserId(String userId) {
+        StpUtil.logout(splicingLoginId(userId));
+        removeOtherCache(userId);
+
+    }
+
+    // =================== 用户权限 ===================
+
+    /**
+     * 获取当前用户拥有的权限列表(菜单编码列表、功能ID列表)
+     * @return
+     */
+    public static List<String> getPermissionList(){
+        return StpUtil.getPermissionList();
+    }
+
+    /**
+     * 获取当前用户拥有的角色列表
+     * @return
+     */
+    public static List<String> getRoleList(){
+        return StpUtil.getRoleList();
+    }
+
+
+
+
+
+    // =================== 其他缓存相关操作 ===================
+
+    /**
+     * 移除
+     */
+    public static void removeOtherCache(String userId) {
+        redisUtil.remove(cacheKeyUtil.getUserAuthorize() + userId);
+        redisUtil.remove(cacheKeyUtil.getSystemInfo());
+    }
+
+    /**
+     * 是否在线
+     */
+    public boolean isOnLine(String userId) {
+        return StpUtil.getTokenValueByLoginId(splicingLoginId(userId), getDeviceForAgent().getDevice()) != null;
+    }
+
+
+    /**
+     * 是否登陆
+     */
+    public static boolean isLogined() {
+        return StpUtil.isLogin();
+    }
+
+    /**
+     * 指定Token是否有效
+     * @param token
+     * @return
+     */
+    public static boolean isValid(String token) {
+        return StpUtil.getLoginIdByToken(token) != null;
+    }
+
+
+    public static DeviceType getDeviceForAgent() {
+        if (ServletUtil.getIsMobileDevice()) {
+            return DeviceType.APP;
+        } else {
+            return DeviceType.PC;
+        }
+    }
+
+    /**
+     * 判断用户是否是临时用户
+     * @param userInfo
+     * @return
+     */
+    public static boolean isTempUser(UserInfo userInfo){
+        if(userInfo == null){
+            userInfo = getUser();
+        }
+        return DeviceType.TEMPUSER.getDevice().equals(userInfo.getLoginDevice())
+                || DeviceType.TEMPUSERLIMITED.getDevice().equals(userInfo.getLoginDevice());
+    }
+
+
+
+}

BIN
jnpf-boot-common/jnpf-common-auth/target/jnpf-common-auth-6.0.0-RELEASE.jar


Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott