1811872455@163.com 1 month ago
commit
e099e91f53
100 changed files with 10996 additions and 0 deletions
  1. 45 0
      .idea/compiler.xml
  2. 20 0
      .idea/encodings.xml
  3. 40 0
      .idea/jarRepositories.xml
  4. 16 0
      .idea/misc.xml
  5. 6 0
      .idea/vcs.xml
  6. 142 0
      .idea/workspace.xml
  7. 131 0
      pom.xml
  8. 120 0
      storlead-ai-api/pom.xml
  9. 77 0
      storlead-ai-api/src/main/java/com/storlead/ai/AiPlatformApplication.java
  10. 154 0
      storlead-ai-api/src/main/java/com/storlead/ai/config/properties/OpenAiProperties.java
  11. 200 0
      storlead-ai-api/src/main/java/com/storlead/ai/controller/AiChatController.java
  12. 58 0
      storlead-ai-api/src/main/java/com/storlead/ai/core/AiProviderType.java
  13. 58 0
      storlead-ai-api/src/main/java/com/storlead/ai/core/AiService.java
  14. 129 0
      storlead-ai-api/src/main/java/com/storlead/ai/core/ChatRequest.java
  15. 220 0
      storlead-ai-api/src/main/java/com/storlead/ai/core/ChatResponse.java
  16. 116 0
      storlead-ai-api/src/main/java/com/storlead/ai/exception/AiServiceException.java
  17. 50 0
      storlead-ai-api/src/main/java/com/storlead/ai/factory/AiServiceFactory.java
  18. 181 0
      storlead-ai-api/src/main/java/com/storlead/ai/factory/AiServiceFactoryManager.java
  19. 33 0
      storlead-ai-api/src/main/java/com/storlead/ai/factory/impl/OpenAiServiceFactory.java
  20. 229 0
      storlead-ai-api/src/main/java/com/storlead/ai/service/AiChatService.java
  21. 320 0
      storlead-ai-api/src/main/java/com/storlead/ai/service/impl/OpenAiService.java
  22. 204 0
      storlead-ai-api/src/main/java/com/storlead/ai/util/HttpClientUtil.java
  23. 276 0
      storlead-ai-api/src/main/resources/application-dev.yml
  24. 223 0
      storlead-ai-api/src/main/resources/application-prod.yml
  25. 235 0
      storlead-ai-api/src/main/resources/application-test.yml
  26. 207 0
      storlead-ai-api/src/main/resources/application-uat.yml
  27. 86 0
      storlead-ai-api/src/main/resources/application.yml
  28. 72 0
      storlead-ai-api/target/classes/META-INF/spring-configuration-metadata.json
  29. 276 0
      storlead-ai-api/target/classes/application-dev.yml
  30. 223 0
      storlead-ai-api/target/classes/application-prod.yml
  31. 235 0
      storlead-ai-api/target/classes/application-test.yml
  32. 207 0
      storlead-ai-api/target/classes/application-uat.yml
  33. 86 0
      storlead-ai-api/target/classes/application.yml
  34. BIN
      storlead-ai-api/target/classes/com/storlead/ai/AiPlatformApplication.class
  35. BIN
      storlead-ai-api/target/classes/com/storlead/ai/config/properties/OpenAiProperties.class
  36. BIN
      storlead-ai-api/target/classes/com/storlead/ai/controller/AiChatController.class
  37. BIN
      storlead-ai-api/target/classes/com/storlead/ai/core/AiProviderType.class
  38. BIN
      storlead-ai-api/target/classes/com/storlead/ai/core/AiService.class
  39. BIN
      storlead-ai-api/target/classes/com/storlead/ai/core/ChatRequest$Builder.class
  40. BIN
      storlead-ai-api/target/classes/com/storlead/ai/core/ChatRequest.class
  41. BIN
      storlead-ai-api/target/classes/com/storlead/ai/core/ChatResponse$Builder.class
  42. BIN
      storlead-ai-api/target/classes/com/storlead/ai/core/ChatResponse.class
  43. BIN
      storlead-ai-api/target/classes/com/storlead/ai/exception/AiServiceException.class
  44. BIN
      storlead-ai-api/target/classes/com/storlead/ai/factory/AiServiceFactory.class
  45. BIN
      storlead-ai-api/target/classes/com/storlead/ai/factory/AiServiceFactoryManager.class
  46. BIN
      storlead-ai-api/target/classes/com/storlead/ai/factory/impl/OpenAiServiceFactory.class
  47. BIN
      storlead-ai-api/target/classes/com/storlead/ai/service/AiChatService.class
  48. BIN
      storlead-ai-api/target/classes/com/storlead/ai/service/impl/OpenAiService.class
  49. BIN
      storlead-ai-api/target/classes/com/storlead/ai/util/HttpClientUtil.class
  50. 386 0
      storlead-dependencies/pom.xml
  51. 24 0
      storlead-framework/pom.xml
  52. 31 0
      storlead-framework/storlead-auth/pom.xml
  53. 133 0
      storlead-framework/storlead-auth/src/main/java/com/storlead/framework/auth/jwt/JwtUtil.java
  54. 18 0
      storlead-framework/storlead-auth/src/main/java/com/storlead/framework/auth/login/CurrentEmployeeInfo.java
  55. 41 0
      storlead-framework/storlead-auth/src/main/java/com/storlead/framework/auth/login/LoginEmployee.java
  56. 29 0
      storlead-framework/storlead-auth/src/main/java/com/storlead/framework/auth/vo/LoginUser.java
  57. 151 0
      storlead-framework/storlead-common/pom.xml
  58. 32 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/CacheConstant.java
  59. 41 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/CodeGenerateInterface.java
  60. 81 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/CommonConstant.java
  61. 30 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/DSConstants.java
  62. 60 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/DataBaseConstant.java
  63. 12 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/DefContants.java
  64. 14 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/LongToStringSerializer.java
  65. 11 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/SystemConstant.java
  66. 24 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/UserCacheKeyConstants.java
  67. 26 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/enums/DataTypeEnum.java
  68. 6 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/package-info.java
  69. 40 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/property/YamlPropertyResourceFactory.java
  70. 44 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/system/query/MatchTypeEnum.java
  71. 55 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/system/query/QueryCondition.java
  72. 71 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/system/query/QueryRuleEnum.java
  73. 619 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/ConvertUtils.java
  74. 905 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/DateUtil.java
  75. 895 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/DateUtils.java
  76. 150 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/HtmlUtils.java
  77. 529 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/LocalDateUtils.java
  78. 47 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/MD5Util.java
  79. 100 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/RandomCodeUtil.java
  80. 26 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/RandomGenerateHelper.java
  81. 202 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/RsaUtils.java
  82. 106 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/SnowFlake.java
  83. 95 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/SpringContextUtils.java
  84. 103 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/SqlInjectionUtil.java
  85. 75 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/StringUtil.java
  86. 29 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/SystemUtils.java
  87. 219 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/WordUtilsTest.java
  88. 211 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/ZipUtil.java
  89. 87 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/encryptor/AccessKeyEncryptor.java
  90. 78 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/generate/CodeGenerate.java
  91. 29 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/generate/DefaultIdentifierGenerator.java
  92. 36 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/generate/IdWorker.java
  93. 10 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/generate/IdentifierGenerator.java
  94. 126 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/generate/Sequence.java
  95. 20 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/validation/NeContains.java
  96. 31 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/validation/NeContainsValidator.java
  97. 28 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/validation/NotContains.java
  98. 31 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/validation/NotContainsValidator.java
  99. 55 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/vo/FeignResult.java
  100. 119 0
      storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/vo/UserVo.java

+ 45 - 0
.idea/compiler.xml

@@ -0,0 +1,45 @@
+<?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" />
+      </profile>
+      <profile name="Annotation profile for storlead-platform" enabled="true">
+        <sourceOutputDir name="target/generated-sources/annotations" />
+        <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
+        <outputRelativeToContentRoot value="true" />
+        <processorPath useClasspath="false">
+          <entry name="$USER_HOME$/.m2/storlead.ai.repository/org/springframework/boot/spring-boot-configuration-processor/2.7.0/spring-boot-configuration-processor-2.7.0.jar" />
+          <entry name="$USER_HOME$/.m2/storlead.ai.repository/org/projectlombok/lombok/1.18.36/lombok-1.18.36.jar" />
+          <entry name="$USER_HOME$/.m2/storlead.ai.repository/org/mapstruct/mapstruct-processor/1.6.3/mapstruct-processor-1.6.3.jar" />
+          <entry name="$USER_HOME$/.m2/storlead.ai.repository/org/mapstruct/mapstruct/1.6.3/mapstruct-1.6.3.jar" />
+        </processorPath>
+        <module name="storlead-ai-api" />
+        <module name="storlead-auth" />
+        <module name="storlead-common" />
+        <module name="storlead-core" />
+        <module name="storlead-redis" />
+        <module name="storlead-web" />
+        <module name="storlead-mybatis" />
+      </profile>
+    </annotationProcessing>
+    <bytecodeTargetLevel>
+      <module name="daydayup-platform" target="1.8" />
+      <module name="storlead-ai-platform" target="1.8" />
+    </bytecodeTargetLevel>
+  </component>
+  <component name="JavacSettings">
+    <option name="ADDITIONAL_OPTIONS_OVERRIDE">
+      <module name="storlead-ai-api" options="-parameters" />
+      <module name="storlead-auth" options="-parameters" />
+      <module name="storlead-common" options="-parameters" />
+      <module name="storlead-core" options="-parameters" />
+      <module name="storlead-mybatis" options="-parameters" />
+      <module name="storlead-redis" options="-parameters" />
+      <module name="storlead-web" options="-parameters" />
+    </option>
+  </component>
+</project>

+ 20 - 0
.idea/encodings.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Encoding">
+    <file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-ai-api/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-dependencies/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-dependencies/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-framework/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-framework/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-framework/storlead-auth/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-framework/storlead-common/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-framework/storlead-core/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-framework/storlead-core/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-framework/storlead-mybatis/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-framework/storlead-redis/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-framework/storlead-redis/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/storlead-framework/storlead-web/src/main/java" charset="UTF-8" />
+  </component>
+</project>

+ 40 - 0
.idea/jarRepositories.xml

@@ -0,0 +1,40 @@
+<?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="https://repo.maven.apache.org/maven2" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="aliyunmaven" />
+      <option name="name" value="aliyun" />
+      <option name="url" value="https://maven.aliyun.com/repository/public" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="spring-milestones" />
+      <option name="name" value="Spring Milestones" />
+      <option name="url" value="https://repo.spring.io/milestone" />
+    </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="spring-snapshots" />
+      <option name="name" value="Spring Snapshots" />
+      <option name="url" value="https://repo.spring.io/snapshot" />
+    </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>
+    <remote-repository>
+      <option name="id" value="huaweicloud" />
+      <option name="name" value="huawei" />
+      <option name="url" value="https://mirrors.huaweicloud.com/repository/maven/" />
+    </remote-repository>
+  </component>
+</project>

+ 16 - 0
.idea/misc.xml

@@ -0,0 +1,16 @@
+<?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$/storlead-framework/pom.xml" />
+        <option value="$PROJECT_DIR$/storlead-dependencies/pom.xml" />
+        <option value="$PROJECT_DIR$/pom.xml" />
+      </list>
+    </option>
+  </component>
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="ms-17" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/out" />
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="Git" />
+  </component>
+</project>

+ 142 - 0
.idea/workspace.xml

@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="AutoImportSettings">
+    <option name="autoReloadType" value="SELECTIVE" />
+  </component>
+  <component name="ChangeListManager">
+    <list default="true" id="2dc641fc-1465-479e-874d-97069c194ded" name="Changes" comment="">
+      <change afterPath="$PROJECT_DIR$/pom.xml" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/storlead-ai-api/pom.xml" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/AiPlatformApplication.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/config/properties/OpenAiProperties.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/controller/AiChatController.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/core/AiProviderType.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/core/AiService.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/core/ChatRequest.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/core/ChatResponse.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/exception/AiServiceException.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/factory/AiServiceFactory.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/factory/AiServiceFactoryManager.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/factory/impl/OpenAiServiceFactory.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/service/AiChatService.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/service/impl/OpenAiService.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/util/HttpClientUtil.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/resources/application-dev.yml" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/resources/application-prod.yml" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/resources/application-test.yml" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/resources/application-uat.yml" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/resources/application.yml" afterDir="false" />
+    </list>
+    <option name="SHOW_DIALOG" value="false" />
+    <option name="HIGHLIGHT_CONFLICTS" value="true" />
+    <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
+    <option name="LAST_RESOLUTION" value="IGNORE" />
+  </component>
+  <component name="FileTemplateManagerImpl">
+    <option name="RECENT_TEMPLATES">
+      <list>
+        <option value="Class" />
+      </list>
+    </option>
+  </component>
+  <component name="Git.Settings">
+    <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
+  </component>
+  <component name="MavenImportPreferences">
+    <option name="generalSettings">
+      <MavenGeneralSettings>
+        <option name="localRepository" value="C:\Users\admin\.m2\storlead.ai.repository" />
+        <option name="useMavenConfig" value="true" />
+      </MavenGeneralSettings>
+    </option>
+  </component>
+  <component name="PerforceDirect.Settings">
+    <option name="ENABLED" value="false" />
+  </component>
+  <component name="ProjectId" id="31o2yGnp8VN6gRySo4r3MG99n0s" />
+  <component name="ProjectViewState">
+    <option name="showLibraryContents" value="true" />
+  </component>
+  <component name="PropertiesComponent"><![CDATA[{
+  "keyToString": {
+    "RequestMappingsPanelOrder0": "0",
+    "RequestMappingsPanelOrder1": "1",
+    "RequestMappingsPanelWidth0": "75",
+    "RequestMappingsPanelWidth1": "75",
+    "RunOnceActivity.CodyAccountHistoryMigration": "true",
+    "RunOnceActivity.CodyConvertUrlToCodebaseName": "true",
+    "RunOnceActivity.CodyHistoryLlmMigration": "true",
+    "RunOnceActivity.CodyProjectSettingsMigration": "true",
+    "RunOnceActivity.OpenProjectViewOnStart": "true",
+    "RunOnceActivity.ShowReadmeOnStart": "true",
+    "WebServerToolWindowFactoryState": "false",
+    "jdk.selected.JAVA_MODULE": "ms-17",
+    "last_opened_file_path": "D:/chenkq-work/git/storlead-ai-platform/storlead-ai-api/src/main/resources",
+    "node.js.detected.package.eslint": "true",
+    "node.js.detected.package.tslint": "true",
+    "node.js.selected.package.eslint": "(autodetect)",
+    "node.js.selected.package.tslint": "(autodetect)",
+    "project.structure.last.edited": "Modules",
+    "project.structure.proportion": "0.0",
+    "project.structure.side.proportion": "0.0",
+    "settings.editor.selected.configurable": "MavenSettings",
+    "spring.configuration.checksum": "1259999022d84b755f5d094d936c15a0",
+    "vue.rearranger.settings.migration": "true"
+  }
+}]]></component>
+  <component name="ReactorSettings">
+    <option name="notificationShown" value="true" />
+  </component>
+  <component name="RecentsManager">
+    <key name="CopyFile.RECENT_KEYS">
+      <recent name="D:\chenkq-work\git\storlead-ai-platform\storlead-ai-api\src\main\resources" />
+      <recent name="D:\chenkq-work\git\storlead-ai-platform" />
+    </key>
+  </component>
+  <component name="RunManager">
+    <configuration default="true" type="JetRunConfigurationType">
+      <module name="storlead-ai-platform" />
+      <method v="2">
+        <option name="Make" enabled="true" />
+      </method>
+    </configuration>
+    <configuration default="true" type="KotlinStandaloneScriptRunConfigurationType">
+      <module name="storlead-ai-platform" />
+      <option name="filePath" />
+      <method v="2">
+        <option name="Make" enabled="true" />
+      </method>
+    </configuration>
+    <configuration name="App" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot">
+      <option name="ALTERNATIVE_JRE_PATH" value="ms-17 (2)" />
+      <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
+      <module name="storlead-ai-api" />
+      <option name="SPRING_BOOT_MAIN_CLASS" value="com.storlead.ai.AiPlatformApplication" />
+      <method v="2">
+        <option name="Make" enabled="true" />
+      </method>
+    </configuration>
+  </component>
+  <component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
+  <component name="TaskManager">
+    <task active="true" id="Default" summary="Default task">
+      <changelist id="2dc641fc-1465-479e-874d-97069c194ded" name="Changes" comment="" />
+      <created>1756176357996</created>
+      <option name="number" value="Default" />
+      <option name="presentableId" value="Default" />
+      <updated>1756176357996</updated>
+      <workItem from="1756176360247" duration="6014000" />
+      <workItem from="1756347439468" duration="3161000" />
+      <workItem from="1757642133544" duration="17020000" />
+      <workItem from="1758250810392" duration="10129000" />
+    </task>
+    <servers />
+  </component>
+  <component name="TypeScriptGeneratedFilesManager">
+    <option name="version" value="3" />
+  </component>
+  <component name="XSLT-Support.FileAssociations.UIState">
+    <expand />
+    <select />
+  </component>
+</project>

+ 131 - 0
pom.xml

@@ -0,0 +1,131 @@
+<?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>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.7.0</version>
+        <relativePath/>
+    </parent>
+
+    <groupId>com.storlead.boot</groupId>
+    <artifactId>storlead-platform</artifactId>
+    <version>${revision}</version>
+    <packaging>pom</packaging>
+    <name>${project.artifactId}</name>
+
+    <modules>
+        <module>storlead-dependencies</module>
+        <module>storlead-framework</module>
+        <module>storlead-ai-api</module>
+    </modules>
+
+    <properties>
+        <revision>2.7.0-jdk11-SNAPSHOT</revision>
+        <java.version>17</java.version>
+        <maven.compiler.source>${java.version}</maven.compiler.source>
+        <maven.compiler.target>${java.version}</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <!--全局配置项目版本号-->
+        <jwt.version>0.9.1</jwt.version>
+        <commons.version>2.6</commons.version>
+        <!-- 表示打包时跳过mvn test -->
+        <maven.test.skip>true</maven.test.skip>
+        <aviator.version>5.2.7</aviator.version>
+        <io.jsonwebtoken.version>0.9.1</io.jsonwebtoken.version>
+        <logstash-logback.version>6.6</logstash-logback.version>
+
+        <maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version>
+        <maven-compiler-plugin.version>3.13.0</maven-compiler-plugin.version>
+        <flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
+        <lombok.version>1.18.36</lombok.version>
+        <spring.boot.version>2.7.0</spring.boot.version>
+        <mapstruct.version>1.6.3</mapstruct.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>com.storlead.boot</groupId>
+                <artifactId>storlead-dependencies</artifactId>
+                <version>${revision}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <build>
+        <pluginManagement>
+            <plugins>
+                <!-- maven-surefire-plugin 插件,用于运行单元测试。 -->
+                <!-- 注意,需要使用 3.0.X+,因为要支持 Junit 5 版本 -->
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-surefire-plugin</artifactId>
+                    <version>${maven-surefire-plugin.version}</version>
+                </plugin>
+                <!-- maven-compiler-plugin 插件,解决 spring-boot-configuration-processor + Lombok + MapStruct 组合 -->
+                <!-- https://stackoverflow.com/questions/33483697/re-run-spring-boot-configuration-annotation-processor-to-update-generated-metada -->
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-compiler-plugin</artifactId>
+                    <version>${maven-compiler-plugin.version}</version>
+                    <configuration>
+                        <annotationProcessorPaths>
+                            <path>
+                                <groupId>org.springframework.boot</groupId>
+                                <artifactId>spring-boot-configuration-processor</artifactId>
+                                <version>${spring.boot.version}</version>
+                            </path>
+                            <path>
+                                <groupId>org.projectlombok</groupId>
+                                <artifactId>lombok</artifactId>
+                                <version>${lombok.version}</version>
+                            </path>
+                            <path>
+                                <groupId>org.mapstruct</groupId>
+                                <artifactId>mapstruct-processor</artifactId>
+                                <version>${mapstruct.version}</version>
+                            </path>
+                        </annotationProcessorPaths>
+                    </configuration>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+    </build>
+
+    <!-- 使用 huawei / aliyun 的 Maven 源,提升下载速度 -->
+    <repositories>
+        <repository>
+            <id>huaweicloud</id>
+            <name>huawei</name>
+            <url>https://mirrors.huaweicloud.com/repository/maven/</url>
+        </repository>
+        <repository>
+            <id>aliyunmaven</id>
+            <name>aliyun</name>
+            <url>https://maven.aliyun.com/repository/public</url>
+        </repository>
+
+        <repository>
+            <id>spring-milestones</id>
+            <name>Spring Milestones</name>
+            <url>https://repo.spring.io/milestone</url>
+            <snapshots>
+                <enabled>false</enabled>
+            </snapshots>
+        </repository>
+        <repository>
+            <id>spring-snapshots</id>
+            <name>Spring Snapshots</name>
+            <url>https://repo.spring.io/snapshot</url>
+            <releases>
+                <enabled>false</enabled>
+            </releases>
+        </repository>
+    </repositories>
+</project>

+ 120 - 0
storlead-ai-api/pom.xml

@@ -0,0 +1,120 @@
+<?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.storlead.boot</groupId>
+        <artifactId>storlead-platform</artifactId>
+        <version>2.7.0-jdk11-SNAPSHOT</version>
+    </parent>
+
+    <groupId>org.jeecgframework.boot</groupId>
+    <artifactId>storlead-ai-api</artifactId>
+
+    <properties>
+        <maven.compiler.source>17</maven.compiler.source>
+        <maven.compiler.target>17</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-common</artifactId>
+        </dependency>
+
+
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+        </dependency>
+
+
+        <!-- 参数校验 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-ui</artifactId>
+        </dependency>
+
+        <!-- Apache HttpClient (用于HTTP客户端连接池) -->
+        <dependency>
+            <groupId>org.apache.httpcomponents.client5</groupId>
+            <artifactId>httpclient5</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.projectreactor</groupId>
+            <artifactId>reactor-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- Spring Boot WebFlux (响应式编程) -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-webflux</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+        </dependency>
+
+<!--        <dependency>-->
+<!--            <groupId>org.java-websocket</groupId>-->
+<!--            <artifactId>Java-WebSocket</artifactId>-->
+<!--        </dependency>-->
+<!--        -->
+<!--        <dependency>-->
+<!--            <groupId>com.squareup.okhttp3</groupId>-->
+<!--            <artifactId>okhttp-sse</artifactId>-->
+<!--        </dependency>-->
+<!--        <dependency>-->
+<!--            <groupId>org.apache.httpcomponents.client5</groupId>-->
+<!--            <artifactId>httpclient5</artifactId>-->
+<!--        </dependency>-->
+
+<!--        &lt;!&ndash; https://mvnrepository.com/artifact/org.apache.httpcomponents.core5/httpcore5 &ndash;&gt;-->
+<!--        <dependency>-->
+<!--            <groupId>org.apache.httpcomponents.core5</groupId>-->
+<!--            <artifactId>httpcore5</artifactId>-->
+<!--        </dependency>-->
+
+<!--        <dependency>-->
+<!--            <groupId>org.apache.httpcomponents</groupId>-->
+<!--            <artifactId>httpasyncclient</artifactId>-->
+<!--        </dependency>-->
+
+
+<!--        &lt;!&ndash; https://mvnrepository.com/artifact/org.apache.httpcomponents.core5/httpcore5-h2 &ndash;&gt;-->
+<!--        <dependency>-->
+<!--            <groupId>org.apache.httpcomponents.core5</groupId>-->
+<!--            <artifactId>httpcore5-h2</artifactId>-->
+<!--        </dependency>-->
+
+<!--        <dependency>-->
+<!--            <groupId>org.springframework.boot</groupId>-->
+<!--            <artifactId>spring-boot-starter-websocket</artifactId>-->
+<!--            <version>2.7.2</version>-->
+<!--        </dependency>-->
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 77 - 0
storlead-ai-api/src/main/java/com/storlead/ai/AiPlatformApplication.java

@@ -0,0 +1,77 @@
+package com.storlead.ai;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.env.Environment;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * @program: storlead-ai-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2025-09-12 16:09
+ */
+@Slf4j
+@EnableAsync
+@EnableCaching
+@EnableSwagger2
+@EnableScheduling
+@SpringBootApplication(scanBasePackages = "com.storlead")
+public class AiPlatformApplication {
+    public static void main(String[] args) throws UnknownHostException {
+        ConfigurableApplicationContext application = SpringApplication.run(AiPlatformApplication.class, args);
+        Environment env = application.getEnvironment();
+        String ip = InetAddress.getLocalHost().getHostAddress();
+        String port = env.getProperty("server.port");
+        String path = env.getProperty("server.servlet.context-path");
+        log.info("\n----------------------------------------------------------\n\t" +
+                "Application lingcun is running! Access URLs:\n\t" +
+                "Local: \t\thttp://localhost:" + port + path + "/\n\t" +
+                "External: \thttp://" + ip + ":" + port + path + "/\n\t" +
+                "swagger-ui: \t\thttp://" + ip + ":" + port + path + "/doc.html\n" +
+                "----------------------------------------------------------");
+
+        disableDruidWaring();
+
+//      disableWarning(); //禁用警告
+    }
+
+    private static void disableDruidWaring() {
+        // 解决druid 日志报错:discard long time none received connection:xxx https://juejin.cn/post/6956349355041259557
+        System.setProperty("druid.mysql.usePingMethod","false");
+    }
+
+    @Bean
+    public CorsFilter corsFilter() {
+        //1.添加CORS配置信息
+        CorsConfiguration config = new CorsConfiguration();
+        //1) 允许的域,不要写*,否则cookie就无法使用了
+        config.addAllowedOrigin("*");
+        //3) 允许的请求方式
+        config.addAllowedMethod("OPTIONS");
+        config.addAllowedMethod("POST");
+        config.addAllowedMethod("GET");
+        // 4)允许的头信息
+        config.addAllowedHeader("*");
+        config.setAllowCredentials(true);
+        //初始化Cors配置源
+        UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
+        //2.添加映射路径,我们拦截一切请求
+        configSource.registerCorsConfiguration("/**", config);
+
+        //3.返回CorsFilter实例.参数:cors配置源
+        return new CorsFilter(configSource);
+    }
+}

+ 154 - 0
storlead-ai-api/src/main/java/com/storlead/ai/config/properties/OpenAiProperties.java

@@ -0,0 +1,154 @@
+package com.storlead.ai.config.properties;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * OpenAI配置属性
+ *
+ * @author your-name
+ * @since 1.0.0
+ */
+@Component
+@ConfigurationProperties(prefix = "ai.providers.openai")
+public class OpenAiProperties {
+
+    /**
+     * 是否启用OpenAI
+     */
+    private boolean enabled = false;
+
+    /**
+     * API密钥
+     */
+    private String apiKey;
+
+    /**
+     * API基础URL
+     */
+    private String baseUrl = "https://api.openai.com";
+
+    /**
+     * 默认模型
+     */
+    private String defaultModel = "gpt-3.5-turbo";
+
+    /**
+     * 支持的模型列表
+     */
+    private List<String> supportedModels = List.of(
+            "gpt-3.5-turbo",
+            "gpt-3.5-turbo-16k",
+            "gpt-4",
+            "gpt-4-32k",
+            "gpt-4-turbo-preview"
+    );
+
+    /**
+     * 请求超时时间(秒)
+     */
+    private int timeoutSeconds = 30;
+
+    /**
+     * 最大重试次数
+     */
+    private int maxRetries = 3;
+
+    /**
+     * 默认温度参数
+     */
+    private double defaultTemperature = 0.7;
+
+    /**
+     * 默认最大令牌数
+     */
+    private int defaultMaxTokens = 1000;
+
+    /**
+     * 是否启用流式响应
+     */
+    private boolean streamEnabled = true;
+
+    // Getters and Setters
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public String getApiKey() {
+        return apiKey;
+    }
+
+    public void setApiKey(String apiKey) {
+        this.apiKey = apiKey;
+    }
+
+    public String getBaseUrl() {
+        return baseUrl;
+    }
+
+    public void setBaseUrl(String baseUrl) {
+        this.baseUrl = baseUrl;
+    }
+
+    public String getDefaultModel() {
+        return defaultModel;
+    }
+
+    public void setDefaultModel(String defaultModel) {
+        this.defaultModel = defaultModel;
+    }
+
+    public List<String> getSupportedModels() {
+        return supportedModels;
+    }
+
+    public void setSupportedModels(List<String> supportedModels) {
+        this.supportedModels = supportedModels;
+    }
+
+    public int getTimeoutSeconds() {
+        return timeoutSeconds;
+    }
+
+    public void setTimeoutSeconds(int timeoutSeconds) {
+        this.timeoutSeconds = timeoutSeconds;
+    }
+
+    public int getMaxRetries() {
+        return maxRetries;
+    }
+
+    public void setMaxRetries(int maxRetries) {
+        this.maxRetries = maxRetries;
+    }
+
+    public double getDefaultTemperature() {
+        return defaultTemperature;
+    }
+
+    public void setDefaultTemperature(double defaultTemperature) {
+        this.defaultTemperature = defaultTemperature;
+    }
+
+    public int getDefaultMaxTokens() {
+        return defaultMaxTokens;
+    }
+
+    public void setDefaultMaxTokens(int defaultMaxTokens) {
+        this.defaultMaxTokens = defaultMaxTokens;
+    }
+
+    public boolean isStreamEnabled() {
+        return streamEnabled;
+    }
+
+    public void setStreamEnabled(boolean streamEnabled) {
+        this.streamEnabled = streamEnabled;
+    }
+}

+ 200 - 0
storlead-ai-api/src/main/java/com/storlead/ai/controller/AiChatController.java

@@ -0,0 +1,200 @@
+package com.storlead.ai.controller;
+
+import com.storlead.ai.core.AiProviderType;
+import com.storlead.ai.core.ChatRequest;
+import com.storlead.ai.core.ChatResponse;
+import com.storlead.ai.service.AiChatService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import javax.validation.Valid;
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * AI聊天REST接口控制器
+ * 提供同步和流式聊天接口
+ *
+ * @author: chenkq
+ * @create: 2025-09-12 15:21
+ */
+@RestController
+@RequestMapping("/v1")
+@CrossOrigin(origins = "*")
+@Tag(name = "AI聊天接口", description = "支持多AI提供商的聊天服务")
+public class AiChatController {
+
+    private static final Logger logger = LoggerFactory.getLogger(AiChatController.class);
+
+    @Autowired
+    private AiChatService aiChatService;
+
+    /**
+     * 同步聊天接口 - 指定AI提供商
+     */
+    @GetMapping("/chat")
+    @Operation(summary = "同步聊天", description = "使用指定的AI提供商进行一次性聊天")
+    public Mono<ChatResponse> chat(@RequestBody ChatRequest request) {
+        String provider = "openai";
+        return aiChatService.chat(provider, request);
+    }
+
+    /**
+     * 流式聊天接口 - 指定AI提供商
+     */
+    @GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
+    @Operation(summary = "流式聊天", description = "使用指定的AI提供商进行流式聊天")
+    public Flux<String> chatStream(@RequestBody ChatRequest request) {
+        String provider = "openai";
+        logger.info("收到流式聊天请求 - 提供商: {}", provider);
+        return aiChatService.chatStream(provider, request)
+                .delayElements(Duration.ofMillis(50)); // 添加小延迟以改善流式体验
+    }
+
+    /**
+     * 自动选择提供商聊天
+     */
+    @PostMapping("/chat/auto")
+    @Operation(summary = "自动聊天", description = "自动选择可用的AI提供商进行聊天")
+    public Mono<ChatResponse> chatAuto(
+            @Parameter(description = "聊天请求")
+            @Valid @RequestBody ChatRequest request) {
+
+        logger.info("收到自动聊天请求");
+        return aiChatService.chatWithAutoProvider(request);
+    }
+
+    /**
+     * 批量聊天 - 同时使用多个AI提供商
+     */
+    @PostMapping("/chat/batch")
+    @Operation(summary = "批量聊天", description = "同时使用多个AI提供商进行聊天")
+    public Flux<ChatResponse> chatBatch(
+            @Parameter(description = "AI提供商代码列表")
+            @RequestParam List<String> providers,
+            @Parameter(description = "聊天请求")
+            @Valid @RequestBody ChatRequest request) {
+
+        logger.info("收到批量聊天请求 - 提供商: {}", providers);
+        return Flux.fromIterable(providers)
+                .flatMap(provider -> aiChatService.chat(provider, request))
+                .onErrorContinue((error, item) -> {
+                    logger.warn("批量聊天中某个提供商失败: {}", error.getMessage());
+                });
+    }
+
+    /**
+     * 获取支持的AI提供商列表
+     */
+    @GetMapping("/providers")
+    @Operation(summary = "获取支持的AI提供商", description = "获取所有支持的AI提供商列表")
+    public Mono<Map<String, Object>> getSupportedProviders() {
+        Set<AiProviderType> providers = aiChatService.getSupportedProviders();
+
+        Map<String, Object> response = Map.of(
+                "count", providers.size(),
+                "providers", providers.stream()
+                        .map(type -> Map.of(
+                                "code", type.getCode(),
+                                "displayName", type.getDisplayName(),
+                                "available", aiChatService.isProviderAvailable(type)
+                        ))
+                        .toList()
+        );
+
+        return Mono.just(response);
+    }
+
+    /**
+     * 获取指定提供商支持的模型
+     */
+    @GetMapping("/providers/{provider}/models")
+    @Operation(summary = "获取支持的模型", description = "获取指定AI提供商支持的模型列表")
+    public Mono<Map<String, Object>> getSupportedModels(
+            @Parameter(description = "AI提供商代码")
+            @PathVariable String provider) {
+
+        try {
+            AiProviderType providerType = AiProviderType.fromCode(provider);
+            List<String> models = aiChatService.getSupportedModels(providerType);
+
+            return Mono.just(Map.of(
+                    "provider", provider,
+                    "models", models
+            ));
+        } catch (IllegalArgumentException e) {
+            return Mono.just(Map.of(
+                    "error", "无效的提供商代码: " + provider
+            ));
+        }
+    }
+
+    /**
+     * 检查提供商状态
+     */
+    @GetMapping("/providers/{provider}/status")
+    @Operation(summary = "检查提供商状态", description = "检查指定AI提供商的可用状态")
+    public Mono<Map<String, Object>> checkProviderStatus(
+            @Parameter(description = "AI提供商代码")
+            @PathVariable String provider) {
+
+        try {
+            AiProviderType providerType = AiProviderType.fromCode(provider);
+            boolean available = aiChatService.isProviderAvailable(providerType);
+
+            return Mono.just(Map.of(
+                    "provider", provider,
+                    "displayName", providerType.getDisplayName(),
+                    "available", available,
+                    "status", available ? "运行正常" : "服务不可用"
+            ));
+        } catch (IllegalArgumentException e) {
+            return Mono.just(Map.of(
+                    "provider", provider,
+                    "available", false,
+                    "error", "无效的提供商代码"
+            ));
+        }
+    }
+
+    /**
+     * 获取系统状态
+     */
+    @GetMapping("/system/status")
+    @Operation(summary = "获取系统状态", description = "获取AI聊天系统的整体状态信息")
+    public Mono<Map<String, Object>> getSystemStatus() {
+        Map<String, Object> status = aiChatService.getSystemStatus();
+        return Mono.just(status);
+    }
+
+    /**
+     * 健康检查
+     */
+    @GetMapping("/health")
+    @Operation(summary = "健康检查", description = "检查AI聊天服务的健康状态")
+    public Mono<Map<String, Object>> healthCheck() {
+        Set<AiProviderType> supportedProviders = aiChatService.getSupportedProviders();
+        long availableCount = supportedProviders.stream()
+                .mapToLong(provider -> aiChatService.isProviderAvailable(provider) ? 1 : 0)
+                .sum();
+
+        String status = availableCount > 0 ? "健康" : "异常";
+
+        return Mono.just(Map.of(
+                "status", status,
+                "totalProviders", supportedProviders.size(),
+                "availableProviders", availableCount,
+                "timestamp", System.currentTimeMillis()
+        ));
+    }
+}

+ 58 - 0
storlead-ai-api/src/main/java/com/storlead/ai/core/AiProviderType.java

@@ -0,0 +1,58 @@
+package com.storlead.ai.core;
+
+/**
+ * @program: AI提供商类型枚举
+ * @description:
+ * @author: chenkq
+ * @create: 2025-09-12 15:14
+ */
+public enum AiProviderType {
+
+    /**
+     * OpenAI GPT系列
+     */
+    OPENAI("openai", "OpenAI"),
+
+    /**
+     * 百度千帆大模型
+     */
+    QIANFAN("qianfan", "百度千帆"),
+
+    /**
+     * 智谱AI
+     */
+    ZHIPU("zhipu", "智谱AI"),
+
+    /**
+     * 阿里通义千问
+     */
+    TONGYI("tongyi", "通义千问");
+
+    private final String code;
+    private final String displayName;
+
+    AiProviderType(String code, String displayName) {
+        this.code = code;
+        this.displayName = displayName;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    /**
+     * 根据code获取对应的枚举
+     */
+    public static AiProviderType fromCode(String code) {
+        for (AiProviderType type : values()) {
+            if (type.code.equals(code)) {
+                return type;
+            }
+        }
+        throw new IllegalArgumentException("Unknown provider code: " + code);
+    }
+}

+ 58 - 0
storlead-ai-api/src/main/java/com/storlead/ai/core/AiService.java

@@ -0,0 +1,58 @@
+package com.storlead.ai.core;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+/**
+ * @program: AI服务接口 - 定义标准的AI交互方法
+ * @description:
+ * @author: chenkq
+ * @create: 2025-09-12 15:16
+ */
+
+public interface AiService {
+
+    /**
+     * 同步聊天 - 一次性返回完整响应
+     *
+     * @param request 聊天请求
+     * @return 聊天响应的Mono包装
+     */
+    Mono<ChatResponse> chat(ChatRequest request);
+
+    /**
+     * 流式聊天 - 逐步返回响应内容
+     *
+     * @param request 聊天请求
+     * @return 响应内容流
+     */
+    Flux<String> chatStream(ChatRequest request);
+
+    /**
+     * 获取AI提供商类型
+     *
+     * @return 提供商类型
+     */
+    AiProviderType getProviderType();
+
+    /**
+     * 检查服务是否可用
+     *
+     * @return 是否可用
+     */
+    boolean isAvailable();
+
+    /**
+     * 获取支持的模型列表
+     *
+     * @return 模型名称列表
+     */
+    java.util.List<String> getSupportedModels();
+
+    /**
+     * 获取默认模型名称
+     *
+     * @return 默认模型名称
+     */
+    String getDefaultModel();
+}

+ 129 - 0
storlead-ai-api/src/main/java/com/storlead/ai/core/ChatRequest.java

@@ -0,0 +1,129 @@
+package com.storlead.ai.core;
+
+import java.util.Map;
+
+
+/**
+ * @program: AI聊天请求对象
+ * @description:
+ * @author: chenkq
+ * @create: 2025-09-12 15:15
+ */
+public class ChatRequest {
+
+    /**
+     * 用户消息内容
+     */
+    private String message;
+
+    /**
+     * 系统提示词
+     */
+    private String systemPrompt;
+
+    /**
+     * 额外参数(如temperature、max_tokens等)
+     */
+    private Map<String, Object> parameters;
+
+    /**
+     * 对话ID(用于维护对话上下文)
+     */
+    private String conversationId;
+
+    /**
+     * 指定使用的模型名称
+     */
+    private String model;
+
+    // 构造函数
+    public ChatRequest() {}
+
+    public ChatRequest(String message) {
+        this.message = message;
+    }
+
+    public ChatRequest(String message, String systemPrompt) {
+        this.message = message;
+        this.systemPrompt = systemPrompt;
+    }
+
+    // Builder模式
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static class Builder {
+        private ChatRequest request = new ChatRequest();
+
+        public Builder message(String message) {
+            request.message = message;
+            return this;
+        }
+
+        public Builder systemPrompt(String systemPrompt) {
+            request.systemPrompt = systemPrompt;
+            return this;
+        }
+
+        public Builder parameters(Map<String, Object> parameters) {
+            request.parameters = parameters;
+            return this;
+        }
+
+        public Builder conversationId(String conversationId) {
+            request.conversationId = conversationId;
+            return this;
+        }
+
+        public Builder model(String model) {
+            request.model = model;
+            return this;
+        }
+
+        public ChatRequest build() {
+            return request;
+        }
+    }
+
+    // Getters and Setters
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public String getSystemPrompt() {
+        return systemPrompt;
+    }
+
+    public void setSystemPrompt(String systemPrompt) {
+        this.systemPrompt = systemPrompt;
+    }
+
+    public Map<String, Object> getParameters() {
+        return parameters;
+    }
+
+    public void setParameters(Map<String, Object> parameters) {
+        this.parameters = parameters;
+    }
+
+    public String getConversationId() {
+        return conversationId;
+    }
+
+    public void setConversationId(String conversationId) {
+        this.conversationId = conversationId;
+    }
+
+    public String getModel() {
+        return model;
+    }
+
+    public void setModel(String model) {
+        this.model = model;
+    }
+}

+ 220 - 0
storlead-ai-api/src/main/java/com/storlead/ai/core/ChatResponse.java

@@ -0,0 +1,220 @@
+package com.storlead.ai.core;
+
+
+import java.time.LocalDateTime;
+import java.util.Map;
+/**
+ * @program: AI聊天响应对象
+ * @description:
+ * @author: chenkq
+ * @create: 2025-09-12 15:15
+ */
+public class ChatResponse {
+
+    /**
+     * 响应内容
+     */
+    private String content;
+
+    /**
+     * 使用的模型名称
+     */
+    private String model;
+
+    /**
+     * 响应元数据(如token使用情况、响应时间等)
+     */
+    private Map<String, Object> metadata;
+
+    /**
+     * 是否成功
+     */
+    private boolean success;
+
+    /**
+     * 错误消息
+     */
+    private String errorMessage;
+
+    /**
+     * 错误码
+     */
+    private String errorCode;
+
+    /**
+     * AI提供商类型
+     */
+    private AiProviderType providerType;
+
+    /**
+     * 响应时间戳
+     */
+    private LocalDateTime timestamp;
+
+    /**
+     * 对话ID
+     */
+    private String conversationId;
+
+    // 构造函数
+    public ChatResponse() {
+        this.timestamp = LocalDateTime.now();
+    }
+
+    public ChatResponse(String content, String model) {
+        this();
+        this.content = content;
+        this.model = model;
+        this.success = true;
+    }
+
+    // 静态工厂方法
+    public static ChatResponse success(String content, String model) {
+        return new ChatResponse(content, model);
+    }
+
+    public static ChatResponse success(String content, String model, AiProviderType providerType) {
+        ChatResponse response = new ChatResponse(content, model);
+        response.setProviderType(providerType);
+        return response;
+    }
+
+    public static ChatResponse error(String errorMessage) {
+        ChatResponse response = new ChatResponse();
+        response.success = false;
+        response.errorMessage = errorMessage;
+        return response;
+    }
+
+    public static ChatResponse error(String errorCode, String errorMessage) {
+        ChatResponse response = error(errorMessage);
+        response.errorCode = errorCode;
+        return response;
+    }
+
+    // Builder模式
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static class Builder {
+        private ChatResponse response = new ChatResponse();
+
+        public Builder content(String content) {
+            response.content = content;
+            return this;
+        }
+
+        public Builder model(String model) {
+            response.model = model;
+            return this;
+        }
+
+        public Builder metadata(Map<String, Object> metadata) {
+            response.metadata = metadata;
+            return this;
+        }
+
+        public Builder success(boolean success) {
+            response.success = success;
+            return this;
+        }
+
+        public Builder errorMessage(String errorMessage) {
+            response.errorMessage = errorMessage;
+            return this;
+        }
+
+        public Builder errorCode(String errorCode) {
+            response.errorCode = errorCode;
+            return this;
+        }
+
+        public Builder providerType(AiProviderType providerType) {
+            response.providerType = providerType;
+            return this;
+        }
+
+        public Builder conversationId(String conversationId) {
+            response.conversationId = conversationId;
+            return this;
+        }
+
+        public ChatResponse build() {
+            return response;
+        }
+    }
+
+    // Getters and Setters
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public String getModel() {
+        return model;
+    }
+
+    public void setModel(String model) {
+        this.model = model;
+    }
+
+    public Map<String, Object> getMetadata() {
+        return metadata;
+    }
+
+    public void setMetadata(Map<String, Object> metadata) {
+        this.metadata = metadata;
+    }
+
+    public boolean isSuccess() {
+        return success;
+    }
+
+    public void setSuccess(boolean success) {
+        this.success = success;
+    }
+
+    public String getErrorMessage() {
+        return errorMessage;
+    }
+
+    public void setErrorMessage(String errorMessage) {
+        this.errorMessage = errorMessage;
+    }
+
+    public String getErrorCode() {
+        return errorCode;
+    }
+
+    public void setErrorCode(String errorCode) {
+        this.errorCode = errorCode;
+    }
+
+    public AiProviderType getProviderType() {
+        return providerType;
+    }
+
+    public void setProviderType(AiProviderType providerType) {
+        this.providerType = providerType;
+    }
+
+    public LocalDateTime getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(LocalDateTime timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    public String getConversationId() {
+        return conversationId;
+    }
+
+    public void setConversationId(String conversationId) {
+        this.conversationId = conversationId;
+    }
+}

+ 116 - 0
storlead-ai-api/src/main/java/com/storlead/ai/exception/AiServiceException.java

@@ -0,0 +1,116 @@
+package com.storlead.ai.exception;
+
+import com.storlead.ai.core.AiProviderType;
+
+/**
+ * AI服务异常
+ *
+ * @author: chenkq
+ * @create: 2025-09-12 15:53
+ */
+public class AiServiceException extends RuntimeException {
+
+    /**
+     * 错误码
+     */
+    private String errorCode;
+
+    /**
+     * AI提供商类型
+     */
+    private AiProviderType providerType;
+
+    /**
+     * HTTP状态码
+     */
+    private Integer httpStatusCode;
+
+    public AiServiceException(String message) {
+        super(message);
+    }
+
+    public AiServiceException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public AiServiceException(String errorCode, String message) {
+        super(message);
+        this.errorCode = errorCode;
+    }
+
+    public AiServiceException(String errorCode, String message, AiProviderType providerType) {
+        super(message);
+        this.errorCode = errorCode;
+        this.providerType = providerType;
+    }
+
+    public AiServiceException(String errorCode, String message, Throwable cause) {
+        super(message, cause);
+        this.errorCode = errorCode;
+    }
+
+    public AiServiceException(String errorCode, String message, AiProviderType providerType, Throwable cause) {
+        super(message, cause);
+        this.errorCode = errorCode;
+        this.providerType = providerType;
+    }
+
+    // 静态工厂方法
+    public static AiServiceException invalidProvider(String providerCode) {
+        return new AiServiceException("INVALID_PROVIDER", "无效的AI提供商: " + providerCode);
+    }
+
+    public static AiServiceException serviceUnavailable(AiProviderType providerType) {
+        return new AiServiceException("SERVICE_UNAVAILABLE",
+                providerType.getDisplayName() + " 服务不可用", providerType);
+    }
+
+    public static AiServiceException apiError(AiProviderType providerType, String message) {
+        return new AiServiceException("API_ERROR", message, providerType);
+    }
+
+    public static AiServiceException networkError(AiProviderType providerType, Throwable cause) {
+        return new AiServiceException("NETWORK_ERROR",
+                "网络连接异常: " + providerType.getDisplayName(), providerType, cause);
+    }
+
+    public static AiServiceException authenticationError(AiProviderType providerType) {
+        return new AiServiceException("AUTHENTICATION_ERROR",
+                "认证失败: " + providerType.getDisplayName(), providerType);
+    }
+
+    public static AiServiceException rateLimitError(AiProviderType providerType) {
+        return new AiServiceException("RATE_LIMIT_ERROR",
+                "请求频率限制: " + providerType.getDisplayName(), providerType);
+    }
+
+    public static AiServiceException quotaExceededError(AiProviderType providerType) {
+        return new AiServiceException("QUOTA_EXCEEDED",
+                "配额已用尽: " + providerType.getDisplayName(), providerType);
+    }
+
+    // Getters and Setters
+    public String getErrorCode() {
+        return errorCode;
+    }
+
+    public void setErrorCode(String errorCode) {
+        this.errorCode = errorCode;
+    }
+
+    public AiProviderType getProviderType() {
+        return providerType;
+    }
+
+    public void setProviderType(AiProviderType providerType) {
+        this.providerType = providerType;
+    }
+
+    public Integer getHttpStatusCode() {
+        return httpStatusCode;
+    }
+
+    public void setHttpStatusCode(Integer httpStatusCode) {
+        this.httpStatusCode = httpStatusCode;
+    }
+}

+ 50 - 0
storlead-ai-api/src/main/java/com/storlead/ai/factory/AiServiceFactory.java

@@ -0,0 +1,50 @@
+package com.storlead.ai.factory;
+
+import com.storlead.ai.core.AiProviderType;
+import com.storlead.ai.core.AiService;
+/**
+ * @program: AI服务抽象工厂 - 使用抽象工厂模式,方便扩展新的AI服务提供商
+ * @description:
+ * @author: chenkq
+ * @create: 2025-09-12 15:17
+ */
+
+public abstract class AiServiceFactory {
+
+    /**
+     * 创建AI服务实例
+     *
+     * @return AI服务实例
+     */
+    public abstract AiService createAiService();
+
+    /**
+     * 获取支持的AI提供商类型
+     *
+     * @return 提供商类型
+     */
+    public abstract AiProviderType getSupportedType();
+
+    /**
+     * 获取工厂描述信息
+     *
+     * @return 工厂描述
+     */
+    public String getDescription() {
+        return getSupportedType().getDisplayName() + " AI服务工厂";
+    }
+
+    /**
+     * 检查工厂是否已准备就绪
+     *
+     * @return 是否就绪
+     */
+    public boolean isReady() {
+        try {
+            AiService service = createAiService();
+            return service != null && service.isAvailable();
+        } catch (Exception e) {
+            return false;
+        }
+    }
+}

+ 181 - 0
storlead-ai-api/src/main/java/com/storlead/ai/factory/AiServiceFactoryManager.java

@@ -0,0 +1,181 @@
+package com.storlead.ai.factory;
+
+import com.storlead.ai.core.AiProviderType;
+import com.storlead.ai.core.AiService;
+import com.storlead.ai.factory.impl.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @program: AI服务工厂管理器 - 管理所有AI服务工厂
+ * @description:  提供统一的AI服务获取入口
+ * @author: chenkq
+ * @create: 2025-09-12 15:17
+ */
+
+@Component
+public class AiServiceFactoryManager {
+
+    private static final Logger logger = LoggerFactory.getLogger(AiServiceFactoryManager.class);
+
+    private final Map<AiProviderType, AiServiceFactory> factories = new ConcurrentHashMap<>();
+
+    // 可选注入,如果某个工厂不可用则忽略
+    @Resource
+    private OpenAiServiceFactory openAiFactory;
+
+    /**
+     * 初始化注册所有可用的工厂
+     */
+    @PostConstruct
+    public void init() {
+        logger.info("初始化AI服务工厂管理器...");
+
+        // 注册可用的工厂
+        if (openAiFactory != null) {
+            registerFactory(openAiFactory);
+        }
+        logger.info("AI服务工厂管理器初始化完成,已注册 {} 个工厂", factories.size());
+        factories.keySet().forEach(type ->
+                logger.info("- 已注册工厂: {}", type.getDisplayName()));
+    }
+
+    /**
+     * 注册工厂
+     *
+     * @param factory 工厂实例
+     */
+    private void registerFactory(AiServiceFactory factory) {
+        if (factory == null) {
+            return;
+        }
+
+        try {
+            AiProviderType type = factory.getSupportedType();
+            factories.put(type, factory);
+            logger.info("注册AI服务工厂: {} - {}", type.getCode(), factory.getDescription());
+        } catch (Exception e) {
+            logger.warn("注册AI服务工厂失败: {}", factory.getClass().getSimpleName(), e);
+        }
+    }
+
+    /**
+     * 根据类型获取AI服务
+     *
+     * @param providerType AI提供商类型
+     * @return AI服务实例
+     * @throws IllegalArgumentException 如果不支持该类型
+     */
+    public AiService getAiService(AiProviderType providerType) {
+        AiServiceFactory factory = factories.get(providerType);
+        if (factory == null) {
+            throw new IllegalArgumentException("不支持的AI提供商类型: " + providerType.getDisplayName());
+        }
+
+        try {
+            AiService service = factory.createAiService();
+            if (service == null || !service.isAvailable()) {
+                throw new IllegalStateException("AI服务不可用: " + providerType.getDisplayName());
+            }
+            return service;
+        } catch (Exception e) {
+            logger.error("创建AI服务失败: {}", providerType.getDisplayName(), e);
+            throw new RuntimeException("创建AI服务失败: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 根据代码获取AI服务
+     *
+     * @param providerCode 提供商代码
+     * @return AI服务实例
+     */
+    public AiService getAiService(String providerCode) {
+        AiProviderType type = AiProviderType.fromCode(providerCode);
+        return getAiService(type);
+    }
+
+    /**
+     * 获取所有可用的AI服务类型
+     *
+     * @return 支持的类型集合
+     */
+    public Set<AiProviderType> getSupportedTypes() {
+        return new HashSet<>(factories.keySet());
+    }
+
+    /**
+     * 获取所有可用的AI服务
+     *
+     * @return AI服务列表
+     */
+    public List<AiService> getAllAvailableServices() {
+        List<AiService> services = new ArrayList<>();
+        for (AiProviderType type : factories.keySet()) {
+            try {
+                AiService service = getAiService(type);
+                if (service.isAvailable()) {
+                    services.add(service);
+                }
+            } catch (Exception e) {
+                logger.warn("获取AI服务失败: {}", type.getDisplayName(), e);
+            }
+        }
+        return services;
+    }
+
+    /**
+     * 检查是否支持某个AI提供商
+     *
+     * @param providerType 提供商类型
+     * @return 是否支持
+     */
+    public boolean isSupported(AiProviderType providerType) {
+        return factories.containsKey(providerType);
+    }
+
+    /**
+     * 检查是否支持某个AI提供商代码
+     *
+     * @param providerCode 提供商代码
+     * @return 是否支持
+     */
+    public boolean isSupported(String providerCode) {
+        try {
+            AiProviderType type = AiProviderType.fromCode(providerCode);
+            return isSupported(type);
+        } catch (IllegalArgumentException e) {
+            return false;
+        }
+    }
+
+    /**
+     * 获取工厂状态信息
+     *
+     * @return 工厂状态Map
+     */
+    public Map<String, Object> getFactoryStatus() {
+        Map<String, Object> status = new HashMap<>();
+        status.put("totalFactories", factories.size());
+
+        Map<String, Object> factoryDetails = new HashMap<>();
+        factories.forEach((type, factory) -> {
+            Map<String, Object> details = new HashMap<>();
+            details.put("type", type.getCode());
+            details.put("displayName", type.getDisplayName());
+            details.put("ready", factory.isReady());
+            details.put("description", factory.getDescription());
+            factoryDetails.put(type.getCode(), details);
+        });
+
+        status.put("factories", factoryDetails);
+        return status;
+    }
+}

+ 33 - 0
storlead-ai-api/src/main/java/com/storlead/ai/factory/impl/OpenAiServiceFactory.java

@@ -0,0 +1,33 @@
+package com.storlead.ai.factory.impl;
+
+import com.storlead.ai.core.AiProviderType;
+import com.storlead.ai.core.AiService;
+import com.storlead.ai.factory.AiServiceFactory;
+import com.storlead.ai.service.impl.OpenAiService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Component;
+
+/**
+ * OpenAI服务工厂
+ *
+ * @author: chenkq
+ * @create: 2025-09-12 15:19
+ */
+@Component
+@ConditionalOnProperty(prefix = "ai.providers.openai", name = "enabled", havingValue = "true")
+public class OpenAiServiceFactory extends AiServiceFactory {
+
+    @Autowired
+    private OpenAiService openAiService;
+
+    @Override
+    public AiService createAiService() {
+        return openAiService;
+    }
+
+    @Override
+    public AiProviderType getSupportedType() {
+        return AiProviderType.OPENAI;
+    }
+}

+ 229 - 0
storlead-ai-api/src/main/java/com/storlead/ai/service/AiChatService.java

@@ -0,0 +1,229 @@
+package com.storlead.ai.service;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.storlead.ai.core.AiProviderType;
+import com.storlead.ai.core.AiService;
+import com.storlead.ai.core.ChatRequest;
+import com.storlead.ai.core.ChatResponse;
+import com.storlead.ai.factory.AiServiceFactoryManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 统一AI聊天服务
+ * 提供统一的聊天接口,支持多个AI提供商
+ *
+ * @author: chenkq
+ * @create: 2025-09-12 15:21
+ */
+@Service
+public class AiChatService {
+
+    private static final Logger logger = LoggerFactory.getLogger(AiChatService.class);
+
+    @Autowired
+    private AiServiceFactoryManager factoryManager;
+
+    @Autowired
+    private ObjectMapper objectMapper;
+
+    /**
+     * 同步聊天 - 指定AI提供商
+     *
+     * @param providerType AI提供商类型
+     * @param request 聊天请求
+     * @return 聊天响应
+     */
+    public Mono<ChatResponse> chat(AiProviderType providerType, ChatRequest request) {
+        try {
+            logger.info("开始聊天 - 提供商: {}, 消息长度: {}",
+                    providerType.getDisplayName(),
+                    request.getMessage() != null ? request.getMessage().length() : 0);
+
+            AiService aiService = factoryManager.getAiService(providerType);
+            return aiService.chat(request)
+                    .doOnSuccess(response -> {
+                        logger.info("聊天完成 - 提供商: {}, 响应长度: {}",
+                                providerType.getDisplayName(),
+                                response.getContent() != null ? response.getContent().length() : 0);
+                    })
+                    .doOnError(error -> {
+                        logger.error("聊天失败 - 提供商: {}, 错误: {}",
+                                providerType.getDisplayName(), error.getMessage());
+                    });
+        } catch (Exception e) {
+            logger.error("聊天服务异常 - 提供商: {}", providerType.getDisplayName(), e);
+            return Mono.just(ChatResponse.error("SERVICE_ERROR", e.getMessage()));
+        }
+    }
+
+    /**
+     * 同步聊天 - 使用提供商代码
+     *
+     * @param providerCode 提供商代码
+     * @param request 聊天请求
+     * @return 聊天响应
+     */
+    public Mono<ChatResponse> chat(String providerCode, ChatRequest request) {
+        try {
+            AiProviderType providerType = AiProviderType.fromCode(providerCode);
+            return chat(providerType, request);
+        } catch (IllegalArgumentException e) {
+            logger.warn("无效的提供商代码: {}", providerCode);
+            return Mono.just(ChatResponse.error("INVALID_PROVIDER", "无效的AI提供商: " + providerCode));
+        }
+    }
+
+    /**
+     * 流式聊天 - 指定AI提供商
+     *
+     * @param providerType AI提供商类型
+     * @param request 聊天请求
+     * @return 响应内容流
+     */
+    public Flux<String> chatStream(AiProviderType providerType, ChatRequest request) {
+        try {
+            logger.info("开始流式聊天 - 提供商: {}, 消息长度: {}",
+                    providerType.getDisplayName(),
+                    request.getMessage() != null ? request.getMessage().length() : 0);
+
+            AiService aiService = factoryManager.getAiService(providerType);
+            return aiService.chatStream(request)
+                    .doOnNext(chunk -> {
+                        logger.info("{}",chunk);
+                    });
+        } catch (Exception e) {
+            logger.error("流式聊天服务异常 - 提供商: {}", providerType.getDisplayName(), e);
+            return Flux.error(e);
+        }
+    }
+
+    /**
+     * 流式聊天 - 使用提供商代码
+     *
+     * @param providerCode 提供商代码
+     * @param request 聊天请求
+     * @return 响应内容流
+     */
+    public Flux<String> chatStream(String providerCode, ChatRequest request) {
+        try {
+            AiProviderType providerType = AiProviderType.fromCode(providerCode);
+            return chatStream(providerType, request);
+        } catch (IllegalArgumentException e) {
+            logger.warn("无效的提供商代码: {}", providerCode);
+            return Flux.error(new IllegalArgumentException("无效的AI提供商: " + providerCode));
+        }
+    }
+
+    /**
+     * 自动选择可用的AI提供商进行聊天
+     *
+     * @param request 聊天请求
+     * @return 聊天响应
+     */
+    public Mono<ChatResponse> chatWithAutoProvider(ChatRequest request) {
+        List<AiService> availableServices = factoryManager.getAllAvailableServices();
+
+        if (availableServices.isEmpty()) {
+            logger.warn("没有可用的AI服务");
+            return Mono.just(ChatResponse.error("NO_AVAILABLE_SERVICE", "没有可用的AI服务"));
+        }
+
+        // 使用第一个可用的服务
+        AiService service = availableServices.get(0);
+        logger.info("自动选择AI提供商: {}", service.getProviderType().getDisplayName());
+
+        return service.chat(request);
+    }
+
+    /**
+     * 获取所有支持的AI提供商
+     *
+     * @return 支持的提供商类型集合
+     */
+    public Set<AiProviderType> getSupportedProviders() {
+        return factoryManager.getSupportedTypes();
+    }
+
+    /**
+     * 获取指定提供商支持的模型列表
+     *
+     * @param providerType 提供商类型
+     * @return 模型列表
+     */
+    public List<String> getSupportedModels(AiProviderType providerType) {
+        try {
+            AiService aiService = factoryManager.getAiService(providerType);
+            return aiService.getSupportedModels();
+        } catch (Exception e) {
+            logger.error("获取支持的模型失败 - 提供商: {}", providerType.getDisplayName(), e);
+            return List.of();
+        }
+    }
+
+    /**
+     * 检查指定提供商是否可用
+     *
+     * @param providerType 提供商类型
+     * @return 是否可用
+     */
+    public boolean isProviderAvailable(AiProviderType providerType) {
+        try {
+            if (!factoryManager.isSupported(providerType)) {
+                return false;
+            }
+            AiService aiService = factoryManager.getAiService(providerType);
+            return aiService.isAvailable();
+        } catch (Exception e) {
+            logger.debug("检查提供商可用性失败: {}", providerType.getDisplayName(), e);
+            return false;
+        }
+    }
+
+    /**
+     * 获取系统状态信息
+     *
+     * @return 系统状态
+     */
+    public Map<String, Object> getSystemStatus() {
+        return factoryManager.getFactoryStatus();
+    }
+
+
+    /**
+     * 解析流式响应块
+     */
+    private String parseStreamChunk(String chunk) {
+        try {
+            JsonNode chunkJson = objectMapper.readTree(chunk);
+
+            if (chunkJson.has("choices")) {
+                JsonNode choices = chunkJson.get("choices");
+                if (!choices.isEmpty()) {
+                    JsonNode firstChoice = choices.get(0);
+                    if (firstChoice.has("delta")) {
+                        JsonNode delta = firstChoice.get("delta");
+                        if (delta.has("content")) {
+                            String content = delta.get("content").asText();
+                            // 只返回纯文本内容,不包含任何JSON结构
+                            return content;
+                        }
+                    }
+                }
+            }
+            return null; // 如果没有content,返回null会被mapNotNull过滤掉
+        } catch (Exception e) {
+            logger.warn("解析流式响应块失败: {}", chunk, e);
+            return null;
+        }
+    }
+}

+ 320 - 0
storlead-ai-api/src/main/java/com/storlead/ai/service/impl/OpenAiService.java

@@ -0,0 +1,320 @@
+package com.storlead.ai.service.impl;
+
+/**
+ * @program: storlead-ai-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2025-09-12 15:52
+ */
+import com.storlead.ai.config.properties.OpenAiProperties;
+import com.storlead.ai.core.*;
+import com.storlead.ai.exception.AiServiceException;
+import com.storlead.ai.util.HttpClientUtil;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.core.io.buffer.DataBufferUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.client.WebClient;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import javax.annotation.PostConstruct;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+
+/**
+ * OpenAI服务实现
+ *
+ * @author your-name
+ * @since 1.0.0
+ */
+@Service
+@ConditionalOnProperty(prefix = "ai.providers.openai", name = "enabled", havingValue = "true")
+public class OpenAiService implements AiService {
+
+    private static final Logger logger = LoggerFactory.getLogger(OpenAiService.class);
+
+    @Autowired
+    private OpenAiProperties properties;
+
+    @Autowired
+    private HttpClientUtil httpClientUtil;
+
+    @Autowired
+    private ObjectMapper objectMapper;
+
+    private WebClient webClient;
+
+    @PostConstruct
+    public void init() {
+        this.webClient = httpClientUtil.createWebClient(
+                properties.getBaseUrl(),
+                properties.getTimeoutSeconds()
+        );
+        logger.info("OpenAI服务初始化完成 - baseUrl: {}, model: {}",
+                properties.getBaseUrl(), properties.getDefaultModel());
+    }
+
+    @Override
+    public Mono<ChatResponse> chat(ChatRequest request) {
+        return Mono.fromCallable(() -> buildRequestBody(request))
+                .flatMap(requestBody -> {
+                    return httpClientUtil.postForObject(
+                            webClient,
+                            "/v1/chat/completions",
+                            requestBody,
+                            JsonNode.class,
+                            httpClientUtil.createBearerAuthHeaders(properties.getApiKey()),
+                            properties.getMaxRetries()
+                    );
+                })
+                .map(this::parseResponse)
+                .onErrorMap(this::handleException);
+    }
+
+    @Override
+    public Flux<String> chatStream(ChatRequest request) {
+        logger.debug("开始构建流式请求");
+
+        Map<String, Object> requestBody = buildStreamRequestBody(request);
+        logger.debug("流式请求体: {}", requestBody);
+
+        return webClient.post()
+                .uri("/v1/chat/completions")
+                .headers(httpHeaders -> {
+                    httpHeaders.setBearerAuth(properties.getApiKey());
+                    httpHeaders.set("Accept", "text/event-stream");
+                })
+                .bodyValue(requestBody)
+                .retrieve()
+                .bodyToFlux(DataBuffer.class)
+                .map(dataBuffer -> {
+                    byte[] bytes = new byte[dataBuffer.readableByteCount()];
+                    dataBuffer.read(bytes);
+                    DataBufferUtils.release(dataBuffer);
+                    return new String(bytes, StandardCharsets.UTF_8);
+                })
+                .flatMapIterable(data -> Arrays.asList(data.split("\n")))
+                .filter(line -> !line.trim().isEmpty())
+                .filter(line -> line.startsWith("data: ") && !line.equals("data: [DONE]"))
+                .map(line -> line.substring(6).trim())
+                .filter(this::isValidJson)  // 添加JSON有效性检查
+                .mapNotNull(this::parseStreamChunk)
+                .onErrorMap(this::handleException);
+    }
+
+    /**
+     * 检查JSON是否有效且完整
+     */
+    private boolean isValidJson(String json) {
+        if (json.trim().isEmpty()) return false;
+        try {
+            objectMapper.readTree(json);
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    @Override
+    public AiProviderType getProviderType() {
+        return AiProviderType.OPENAI;
+    }
+
+    @Override
+    public boolean isAvailable() {
+        try {
+            // 简单的健康检查 - 发送一个极简的请求
+            Map<String, Object> testRequest = Map.of(
+                    "model", properties.getDefaultModel(),
+                    "messages", List.of(Map.of("role", "user", "content", "test")),
+                    "max_tokens", 1
+            );
+
+            return httpClientUtil.postForObject(
+                    webClient,
+                    "/v1/chat/completions",
+                    testRequest,
+                    JsonNode.class,
+                    httpClientUtil.createBearerAuthHeaders(properties.getApiKey()),
+                    1
+            ).block() != null;
+        } catch (Exception e) {
+            logger.warn("OpenAI服务健康检查失败: {}", e.getMessage());
+            return false;
+        }
+    }
+
+    @Override
+    public List<String> getSupportedModels() {
+        return properties.getSupportedModels();
+    }
+
+    @Override
+    public String getDefaultModel() {
+        return properties.getDefaultModel();
+    }
+
+    /**
+     * 构建请求体
+     */
+    private Map<String, Object> buildRequestBody(ChatRequest request) {
+        Map<String, Object> requestBody = new HashMap<>();
+
+        // 基础参数
+        requestBody.put("model", getModelName(request));
+        requestBody.put("messages", buildMessages(request));
+
+        // 可选参数
+        Map<String, Object> params = request.getParameters();
+        if (params != null) {
+            requestBody.put("temperature", params.getOrDefault("temperature", properties.getDefaultTemperature()));
+            requestBody.put("max_tokens", params.getOrDefault("max_tokens", properties.getDefaultMaxTokens()));
+
+            if (params.containsKey("top_p")) {
+                requestBody.put("top_p", params.get("top_p"));
+            }
+            if (params.containsKey("frequency_penalty")) {
+                requestBody.put("frequency_penalty", params.get("frequency_penalty"));
+            }
+            if (params.containsKey("presence_penalty")) {
+                requestBody.put("presence_penalty", params.get("presence_penalty"));
+            }
+        } else {
+            requestBody.put("temperature", properties.getDefaultTemperature());
+            requestBody.put("max_tokens", properties.getDefaultMaxTokens());
+        }
+
+        return requestBody;
+    }
+
+    /**
+     * 构建流式请求体
+     */
+    private Map<String, Object> buildStreamRequestBody(ChatRequest request) {
+        Map<String, Object> requestBody = buildRequestBody(request);
+        requestBody.put("stream", true);
+        return requestBody;
+    }
+
+    /**
+     * 构建消息列表
+     */
+    private List<Map<String, String>> buildMessages(ChatRequest request) {
+        List<Map<String, String>> messages = new java.util.ArrayList<>();
+
+        // 系统提示词
+        if (request.getSystemPrompt() != null && !request.getSystemPrompt().trim().isEmpty()) {
+            messages.add(Map.of("role", "system", "content", request.getSystemPrompt()));
+        }
+
+        // 用户消息
+        messages.add(Map.of("role", "user", "content", request.getMessage()));
+
+        return messages;
+    }
+
+    /**
+     * 获取模型名称
+     */
+    private String getModelName(ChatRequest request) {
+        if (request.getModel() != null && !request.getModel().trim().isEmpty()) {
+            return request.getModel();
+        }
+        return properties.getDefaultModel();
+    }
+
+    /**
+     * 解析响应
+     */
+    private ChatResponse parseResponse(JsonNode responseJson) {
+        try {
+            if (responseJson.has("error")) {
+                JsonNode error = responseJson.get("error");
+                String errorMessage = error.get("message").asText();
+                String errorCode = error.has("code") ? error.get("code").asText() : "OPENAI_ERROR";
+                throw AiServiceException.apiError(AiProviderType.OPENAI, errorMessage);
+            }
+
+            JsonNode choices = responseJson.get("choices");
+            if (choices == null || choices.isEmpty()) {
+                throw AiServiceException.apiError(AiProviderType.OPENAI, "响应中没有选择项");
+            }
+
+            JsonNode firstChoice = choices.get(0);
+            JsonNode message = firstChoice.get("message");
+            String content = message.get("content").asText();
+
+            String model = responseJson.has("model") ? responseJson.get("model").asText() : properties.getDefaultModel();
+
+            // 构建元数据
+            Map<String, Object> metadata = new HashMap<>();
+            if (responseJson.has("usage")) {
+                metadata.put("usage", responseJson.get("usage"));
+            }
+            if (firstChoice.has("finish_reason")) {
+                metadata.put("finishReason", firstChoice.get("finish_reason").asText());
+            }
+
+            ChatResponse response = ChatResponse.success(content, model);
+            response.setProviderType(AiProviderType.OPENAI);
+            response.setMetadata(metadata);
+
+            return response;
+        } catch (Exception e) {
+            logger.error("解析OpenAI响应失败", e);
+            throw AiServiceException.apiError(AiProviderType.OPENAI, "响应解析失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 解析流式响应块
+     */
+    private String parseStreamChunk(String chunk) {
+        try {
+            JsonNode chunkJson = objectMapper.readTree(chunk);
+
+            if (chunkJson.has("choices")) {
+                JsonNode choices = chunkJson.get("choices");
+                if (!choices.isEmpty()) {
+                    JsonNode firstChoice = choices.get(0);
+                    if (firstChoice.has("delta")) {
+                        JsonNode delta = firstChoice.get("delta");
+                        if (delta.has("content")) {
+                            String content = delta.get("content").asText();
+                            // 只返回纯文本内容,不包含任何JSON结构
+                            return content;
+                        }
+                    }
+                }
+            }
+            return null; // 如果没有content,返回null会被mapNotNull过滤掉
+        } catch (Exception e) {
+            logger.warn("解析流式响应块失败: {}", chunk, e);
+            return null;
+        }
+    }
+
+    /**
+     * 异常处理
+     */
+    private Throwable handleException(Throwable throwable) {
+        String errorMessage = httpClientUtil.parseWebClientError(throwable);
+        logger.error("OpenAI API调用失败: {}", errorMessage, throwable);
+
+        if (errorMessage.contains("401")) {
+            return AiServiceException.authenticationError(AiProviderType.OPENAI);
+        } else if (errorMessage.contains("429")) {
+            return AiServiceException.rateLimitError(AiProviderType.OPENAI);
+        } else if (errorMessage.contains("quota")) {
+            return AiServiceException.quotaExceededError(AiProviderType.OPENAI);
+        } else {
+            return AiServiceException.networkError(AiProviderType.OPENAI, throwable);
+        }
+    }
+}

+ 204 - 0
storlead-ai-api/src/main/java/com/storlead/ai/util/HttpClientUtil.java

@@ -0,0 +1,204 @@
+package com.storlead.ai.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.core.io.buffer.DataBufferUtils;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Component;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import reactor.util.retry.Retry;
+
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * HTTP客户端工具类
+ * 封装WebClient,提供统一的HTTP请求方法
+ *
+ * @author: chenkq
+ * @create: 2025-09-12 15:50
+ */
+@Component
+public class HttpClientUtil {
+
+    private static final Logger logger = LoggerFactory.getLogger(HttpClientUtil.class);
+
+    /**
+     * 创建WebClient实例
+     */
+    public WebClient createWebClient(String baseUrl, int timeoutSeconds) {
+        return WebClient.builder()
+                .baseUrl(baseUrl)
+                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
+                .defaultHeader(HttpHeaders.USER_AGENT, "Multi-AI-Chat-Service/1.0")
+                .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(10 * 1024 * 1024)) // 10MB
+                .build();
+    }
+
+    /**
+     * 发送POST请求并获取JSON响应
+     */
+    public <T> Mono<T> postForObject(WebClient webClient, String uri, Object requestBody,
+                                     Class<T> responseType, Consumer<HttpHeaders> headersCustomizer,
+                                     int maxRetries) {
+        WebClient.RequestHeadersSpec<?> spec = webClient.post()
+                .uri(uri)
+                .bodyValue(requestBody);
+
+        if (headersCustomizer != null) {
+            spec = spec.headers(headersCustomizer);
+        }
+
+        return spec.retrieve()
+                .bodyToMono(responseType)
+                .timeout(Duration.ofSeconds(30))
+                .retryWhen(Retry.backoff(maxRetries, Duration.ofSeconds(1))
+                        .filter(this::isRetryableException))
+                .doOnError(error -> {
+                    logger.error("HTTP请求失败: {} {}", uri, error.getMessage());
+                });
+    }
+
+    /**
+     * 发送流式POST请求 - 专门处理Server-Sent Events
+     */
+    public Flux<String> postForStream(WebClient webClient, String uri, Object requestBody,
+                                      Consumer<HttpHeaders> headersCustomizer, int maxRetries) {
+        WebClient.RequestHeadersSpec<?> spec = webClient.post()
+                .uri(uri)
+                .bodyValue(requestBody);
+
+        if (headersCustomizer != null) {
+            spec = spec.headers(headersCustomizer);
+        }
+
+        // 设置Accept头为text/event-stream
+        spec = spec.header("Accept", "text/event-stream");
+
+        return spec.retrieve()
+                .bodyToFlux(DataBuffer.class)  // 使用DataBuffer而不是String
+                .map(dataBuffer -> {
+                    byte[] bytes = new byte[dataBuffer.readableByteCount()];
+                    dataBuffer.read(bytes);
+                    DataBufferUtils.release(dataBuffer);
+                    return new String(bytes, StandardCharsets.UTF_8);
+                })
+                .doOnNext(rawData -> {
+                    logger.debug("接收到原始数据: [{}]", rawData);
+                })
+                .flatMapIterable(rawData -> {
+                    // 按行分割数据
+                    return Arrays.asList(rawData.split("\n"));
+                })
+                .filter(line -> !line.trim().isEmpty())
+                .doOnNext(line -> {
+                    logger.debug("处理行: [{}]", line);
+                })
+                .doOnSubscribe(subscription -> {
+                    logger.debug("开始流式HTTP请求订阅: {}", uri);
+                })
+                .doOnComplete(() -> {
+                    logger.debug("HTTP流完成: {}", uri);
+                })
+                .timeout(Duration.ofSeconds(60))
+                .retryWhen(Retry.backoff(maxRetries, Duration.ofSeconds(1))
+                        .filter(this::isRetryableException))
+                .doOnError(error -> {
+                    logger.error("流式HTTP请求失败: {} {}", uri, error.getMessage());
+                });
+    }
+
+    /**
+     * 发送GET请求
+     */
+    public <T> Mono<T> getForObject(WebClient webClient, String uri, Class<T> responseType,
+                                    Consumer<HttpHeaders> headersCustomizer, Map<String, Object> uriVariables) {
+        WebClient.RequestHeadersSpec<?> spec = webClient.get().uri(uri, uriVariables);
+
+        if (headersCustomizer != null) {
+            spec = spec.headers(headersCustomizer);
+        }
+
+        return spec.retrieve()
+                .bodyToMono(responseType)
+                .timeout(Duration.ofSeconds(30))
+                .doOnError(error -> {
+                    logger.error("GET请求失败: {} {}", uri, error.getMessage());
+                });
+    }
+
+    /**
+     * 创建授权头设置器 - Bearer Token
+     */
+    public Consumer<HttpHeaders> createBearerAuthHeaders(String token) {
+        return headers -> headers.setBearerAuth(token);
+    }
+
+    /**
+     * 创建授权头设置器 - API Key
+     */
+    public Consumer<HttpHeaders> createApiKeyHeaders(String headerName, String apiKey) {
+        return headers -> headers.set(headerName, apiKey);
+    }
+
+    /**
+     * 创建自定义头设置器
+     */
+    public Consumer<HttpHeaders> createCustomHeaders(Map<String, String> customHeaders) {
+        return headers -> customHeaders.forEach(headers::set);
+    }
+
+    /**
+     * 判断异常是否可重试
+     */
+    private boolean isRetryableException(Throwable throwable) {
+        if (throwable instanceof WebClientResponseException webEx) {
+            int statusCode = webEx.getStatusCode().value();
+            // 5xx错误和429(请求过多)可以重试
+            return statusCode >= 500 || statusCode == 429;
+        }
+        // 网络相关异常也可以重试
+        return throwable instanceof java.net.ConnectException ||
+                throwable instanceof java.net.SocketTimeoutException ||
+                throwable instanceof java.io.IOException;
+    }
+
+    /**
+     * 解析WebClient异常并转换为友好的错误信息
+     */
+    public String parseWebClientError(Throwable throwable) {
+        if (throwable instanceof WebClientResponseException webEx) {
+            int statusCode = webEx.getStatusCode().value();
+            String responseBody = webEx.getResponseBodyAsString();
+
+            return switch (statusCode) {
+                case 400 -> "请求参数错误: " + responseBody;
+                case 401 -> "认证失败,请检查API密钥";
+                case 403 -> "权限不足,请检查API密钥权限";
+                case 429 -> "请求频率过高,请稍后重试";
+                case 500 -> "服务器内部错误";
+                case 502 -> "网关错误,服务不可用";
+                case 503 -> "服务暂时不可用";
+                default -> "HTTP错误 " + statusCode + ": " + responseBody;
+            };
+        }
+
+        if (throwable instanceof java.net.ConnectException) {
+            return "连接超时,请检查网络连接";
+        }
+
+        if (throwable instanceof java.net.SocketTimeoutException) {
+            return "请求超时,请稍后重试";
+        }
+
+        return "网络请求失败: " + throwable.getMessage();
+    }
+}

+ 276 - 0
storlead-ai-api/src/main/resources/application-dev.yml

@@ -0,0 +1,276 @@
+server:
+  port: 18090
+  tomcat:
+    max-swallow-size: -1
+    max-upload-size: 200MB
+    basedir: /app/temp
+  servlet:
+    context-path: /router/rest
+
+  compression:
+    enabled: true
+    min-response-size: 1024
+    mime-types: application/javascript,application/json,application/xml,text/html,text/xml,text/plain,text/css,image/*
+
+management:
+  endpoints:
+    web:
+      exposure:
+        include: metrics,httptrace
+
+spring:
+  servlet:
+    multipart:
+      max-file-size: 200MB
+      max-request-size: 200MB
+  ## quartz定时任务,采用数据库方式
+  #  quartz:
+  #    job-store-type: jdbc
+  #json 时间戳统一转换
+  jackson:
+    date-format:   yyyy-MM-dd HH:mm:ss
+    time-zone:   GMT+8
+  aop:
+    proxy-target-class: true
+  #配置freemarker
+  freemarker:
+    # 设置模板后缀名
+    suffix: .ftl
+    # 设置文档类型
+    content-type: text/html
+    # 设置页面编码格式
+    charset: UTF-8
+    # 设置页面缓存
+    cache: false
+    prefer-file-system-access: false
+    # 设置ftl文件路径
+    template-loader-path:
+      - classpath:/templates
+  # 设置静态文件路径,js,css等  #redis 配置
+#  cloud:
+#    nacos:
+#      discovery:
+#        server-addr: http://192.168.1.93:8848
+#        group: DEV
+#        namespace: 1b26d8af-529f-4118-9519-47b6a4aaaa4e
+#        username: nacos
+#        password: nacos
+
+  redis:
+    host: 192.168.1.69
+    port: 6379
+    database: 13
+    lettuce:
+      pool:
+        max-wait: 100000
+        max-idle: 10
+        max-active: 100
+    timeout: 5000
+    password: 123456
+
+  mvc:
+    static-path-pattern: /**
+  resource:
+    static-locations: classpath:/static/,classpath:/public/
+  autoconfigure:
+    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
+  datasource:
+    dynamic:
+      druid: # 全局druid参数,绝大部分值和默认保持一致。(现已支持的参数如下,不清楚含义不要乱设置)
+        # 连接池的配置信息
+        # 初始化大小,最小,最大
+        initial-size: 5
+        min-idle: 5
+        maxActive: 20
+        # 配置获取连接等待超时的时间
+        maxWait: 60000
+        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+        timeBetweenEvictionRunsMillis: 60000
+        # 配置一个连接在池中最小生存的时间,单位是毫秒
+        minEvictableIdleTimeMillis: 300000
+        validationQuery: SELECT 1 FROM DUAL
+        testWhileIdle: true
+        testOnBorrow: false
+        testOnReturn: false
+        # 打开PSCache,并且指定每个连接上PSCache的大小
+        poolPreparedStatements: true
+        maxPoolPreparedStatementPerConnectionSize: 20
+        # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
+        filters: stat,wall,slf4j
+        # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
+        connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
+      datasource:
+        master:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://192.168.1.69:3306/sp_sales_dev?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: storlead
+          password: DW8YRN*5!6u&Agt7N
+#          driver-class-name: com.mysql.jdbc.Driver
+#          url: jdbc:mysql://mysql.test.storlead.com:39091/sp_sales_test?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+#          username: root
+#          password: rCgRgLjH99Xvg5BN
+
+#          driver-class-name: com.mysql.jdbc.Driver
+#          url: jdbc:mysql://139.159.206.64:65369/sp_sales_system_prod?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&rewriteBatchedStatements=true
+#          username: sp_sales_prod
+#          password: MsKLJue01MLIYnd2*0ReMQ
+        management:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://mysql.test.storlead.com:39091/storlead_test?useSSL=false&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&noDatetimeStringSync=true&serverTimezone=Asia/Shanghai
+          username: root
+          password: rCgRgLjH99Xvg5BN
+
+#          driver-class-name: com.mysql.jdbc.Driver
+#          url: jdbc:mysql://139.159.206.64:65369/storlead_prod?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&rewriteBatchedStatements=true
+#          username: storlead_platform
+#          password: QkfgG7Cw6E&*PlvYYw==oBfjSf2zw
+        oa:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://139.159.206.64:65369/storlead_ecology_prod?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: storlead_ecology
+          password: 3raNoDvbo7jqbwtedQGQ
+ai:
+  providers:
+    # OpenAI配置
+    openai:
+      enabled: true
+      api-key:
+      base-url: http://47.112.196.2:11434
+      default-model: deepseek-r1:14b
+      timeout-seconds: 30
+      max-retries: 3
+      default-temperature: 0.7
+      default-max-tokens: 1000
+      stream-enabled: true
+      supported-models:
+        - deepseek-r1:14b
+        - deepseek-r1:8b
+        - gpt-oss:20b
+
+#mybatis plus 设置
+mybatis-plus:
+  mapper-locations: classpath:/mapper/*/*Mapper.xml
+  # 实体扫描,多个 package 用逗号或者分号分隔
+  type-aliases-package: com.storlead.sales.modules.*.pojo.entity
+  typeEnumsPackage: com.storlead.sales.modules.console.enums;com.storlead.sales.modules.perform.enums
+#  configuration:
+  #配置显示查询SQL
+  #    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+#    default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler
+  global-config:
+    # 关闭MP3.0自带的banner
+    banner: false
+    db-config:
+      #主键类型  0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)";
+      id-type: 4
+      # 默认数据库表下划线命名
+      table-underline: true
+    #configuration:
+    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
+    #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+
+#应用专用配置
+lingcun :
+  path :
+    #文件上传根目录 设置
+    upload: /app/upload
+    #webapp文件路径
+    webapp: /app/upload
+  shiro:
+    excludeUrls: /lingcun/login,/lingcun/logout,/lingcun/getCheckCode
+  # 表单设计器配置
+  desform:
+    # 主题颜色(仅支持 16进制颜色代码)
+    theme-color: "#1890ff"
+  # 在线预览文件服务器地址配置
+  file-view-domain: http://localhost:8080/api/dist/app/view/
+
+sp:
+  oss:
+    endpoint: oss-cn-shenzhen.aliyuncs.com
+    accessKey: LTAI5t5n6K9ramopnY4KNQnM
+    #    LTAIWmbTnmF9lzjy
+    secretKey: xmA6wObeUAKDux92DX3qfLNWAQAQZm
+    #    v346LNyCRIZCvTTgYTr5ikJsneBAaZ
+    bucketName: sp-sales-test
+
+
+message:
+  task:
+    enable : false
+  send:
+    enable : true
+storlead:
+  project:
+    baseUrl: http://127.0.0.1:8101/sp-project
+
+#Mybatis输出sql日志
+logging:
+  file:
+    # 日志存放目录
+    path: /app/sp/${spring.application.name}/log
+  level:
+    root: info
+    io:
+      swagger:
+        models:
+          parameters:
+            AbstractSerializableParameter: error
+    c:
+      a:
+        icatch:
+          provider:
+            imp:
+              AssemblerImp: debug
+    o:
+      s:
+        h:
+          c:
+            j:
+              Jackson2ObjectMapperBuilder: off # 禁止okhttp4 打印警告日志 For Jackson Kotlin classes support please add "com.fasterxml.jackson.module:jackson-module-kotlin" to the classpath
+    com:
+      storlead: debug
+
+
+# 企业微信
+#corp-wechat:
+#  corpId: ww5323bd8ab4394132
+#  # 这个是通讯录secret只能用来调用通讯录相关API使用
+#  corpAddressSecret: FE-ofiE08oeT8DsccbigqPFWl6Nk8LRKBFffpL76Z-M
+#  corpAgentId: 1000024
+#  corpAgentSecret: T2N9RAs0ATGrgUEedAovo80a1Z4wpepO9eKn-_5qsBo
+
+
+environment: dev
+domainname: https://sales.test.storlead.com
+
+system:
+  config:
+    orgSyncMode: 0  # 0:系统维护 1:同步OA, 2:同步企业微信
+  license:
+    httpUrl: http://localhost:10010${server.servlet.context-path}/tenant/license/getLicenseCode
+    activationCode: "activationCode"
+
+
+file:
+  mode: 1
+  filePath: /files
+  active: dev
+  mac:
+    path: ~/file/
+    avatar: ~/avatar/
+  linux:
+    path: /app/files/
+    avatar: /app/files/avatar/
+  windows:
+    path: D:\mail\attachment\
+    avatar: D:\mail\
+    jpeg: D:\mail\image\
+  # 文件大小 /M
+  maxSize: 100
+  avatarMaxSize: 5
+
+feign:
+  user-service:
+    app-name: sp-internal-gateway
+    context-path: /router/rest

+ 223 - 0
storlead-ai-api/src/main/resources/application-prod.yml

@@ -0,0 +1,223 @@
+#开发模式
+debug: false
+server:
+  port: 18094
+  tomcat:
+    max-swallow-size: -1
+    max-upload-size: 200MB
+    basedir: /mnt/vdb/storlead/sales/temp
+  servlet:
+    context-path: /sales
+  compression:
+    enabled: true
+    min-response-size: 1024
+    mime-types: application/javascript,application/json,application/xml,text/html,text/xml,text/plain,text/css,image/*
+
+management:
+  endpoints:
+    web:
+      exposure:
+        include: metrics,httptrace
+
+spring:
+  servlet:
+    multipart:
+      max-file-size: 200MB
+      max-request-size: 200MB
+  ## quartz定时任务,采用数据库方式
+  #  quartz:
+  #    job-store-type: jdbc
+  #json 时间戳统一转换
+  jackson:
+    date-format:   yyyy-MM-dd HH:mm:ss
+    time-zone:   GMT+8
+  aop:
+    proxy-target-class: true
+  #配置freemarker
+  freemarker:
+    # 设置模板后缀名
+    suffix: .ftl
+    # 设置文档类型
+    content-type: text/html
+    # 设置页面编码格式
+    charset: UTF-8
+    # 设置页面缓存
+    cache: false
+    prefer-file-system-access: false
+    # 设置ftl文件路径
+    template-loader-path:
+      - classpath:/templates
+  # 设置静态文件路径,js,css等  #redis 配置
+  redis:
+    database: 14
+    host: 192.168.0.210
+    lettuce:
+      pool:
+        max-active: 8   #最大连接数据库连接数,设 0 为没有限制
+        max-idle: 8     #最大等待连接中的数量,设 0 为没有限制
+        max-wait: -1ms  #最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。
+        min-idle: 0     #最小等待连接中的数量,设 0 为没有限制
+      shutdown-timeout: 100ms
+    password: 'B3f@NT4y%etekQaDkufd'
+    port: 56379
+  mvc:
+    static-path-pattern: /**
+  resource:
+    static-locations: classpath:/static/,classpath:/public/
+  autoconfigure:
+    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
+  datasource:
+    dynamic:
+      druid: # 全局druid参数,绝大部分值和默认保持一致。(现已支持的参数如下,不清楚含义不要乱设置)
+        # 连接池的配置信息
+        # 初始化大小,最小,最大
+        initial-size: 5
+        min-idle: 5
+        maxActive: 20
+        # 配置获取连接等待超时的时间
+        maxWait: 60000
+        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+        timeBetweenEvictionRunsMillis: 60000
+        # 配置一个连接在池中最小生存的时间,单位是毫秒
+        minEvictableIdleTimeMillis: 300000
+        validationQuery: SELECT 1 FROM DUAL
+        testWhileIdle: true
+        testOnBorrow: false
+        testOnReturn: false
+        # 打开PSCache,并且指定每个连接上PSCache的大小
+        poolPreparedStatements: true
+        maxPoolPreparedStatementPerConnectionSize: 20
+        # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
+        filters: stat,wall,slf4j
+        # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
+        connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
+      datasource:
+        master:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://storlead.internal.cn-south-1.mysql.rds.myhuaweicloud.com:65369/sp_sales_system_prod?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&rewriteBatchedStatements=true
+          username: sp_sales_prod
+          password: MsKLJue01MLIYnd2*0ReMQ
+        management:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://storlead.internal.cn-south-1.mysql.rds.myhuaweicloud.com:65369/storlead_prod?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&rewriteBatchedStatements=true
+          username: storlead_platform
+          password: QkfgG7Cw6E&*PlvYYw==oBfjSf2zw
+        oa:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://storlead.internal.cn-south-1.mysql.rds.myhuaweicloud.com:65369/storlead_ecology_prod?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: storlead_ecology
+          password: 3raNoDvbo7jqbwtedQGQ
+#mybatis plus 设置
+mybatis-plus:
+  mapper-locations: classpath:/mapper/*/*Mapper.xml
+  # 实体扫描,多个 package 用逗号或者分号分隔
+  type-aliases-package: com.storlead.sales.modules.*.entity
+  type-enums-package: com.storlead.sales.modules.console.enums;com.storlead.sales.modules.perform.enums;com.storlead.sales.modules.project.enums
+    #  configuration:
+  #配置显示查询SQL
+  #    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+  #    default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler
+  global-config:
+    # 关闭MP3.0自带的banner
+    banner: false
+    db-config:
+      #主键类型  0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)";
+      id-type: 4
+      # 默认数据库表下划线命名
+      table-underline: true
+    #configuration:
+    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
+    #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+
+#应用专用配置
+lingcun :
+  path :
+    #文件上传根目录 设置
+    upload: /mnt/vdb/storlead/sales/upload
+    #webapp文件路径
+    webapp: /mnt/vdb/storlead/sales/upload
+  shiro:
+    excludeUrls: /lingcun/login,/lingcun/logout,/lingcun/getCheckCode
+  # 表单设计器配置
+  desform:
+    # 主题颜色(仅支持 16进制颜色代码)
+    theme-color: "#1890ff"
+  # 在线预览文件服务器地址配置
+  file-view-domain: http://localhost:8080/api/dist/app/view/
+  #
+sp:
+  oss:
+    endpoint: oss-cn-shenzhen.aliyuncs.com
+    accessKey: LTAI5t5n6K9ramopnY4KNQnM
+    secretKey: xmA6wObeUAKDux92DX3qfLNWAQAQZm
+    bucketName: sp-sales-prod
+
+
+storlead:
+  project:
+    baseUrl: project.storlead.com
+
+#Mybatis输出sql日志
+logging:
+  file:
+    # 日志存放目录
+    path: /mnt/vdb/storlead/sales/log/
+  level:
+    root: info
+    io:
+      swagger:
+        models:
+          parameters:
+            AbstractSerializableParameter: error
+    c:
+      a:
+        icatch:
+          provider:
+            imp:
+              AssemblerImp: debug
+    o:
+      s:
+        h:
+          c:
+            j:
+              Jackson2ObjectMapperBuilder: off # 禁止okhttp4 打印警告日志 For Jackson Kotlin classes support please add "com.fasterxml.jackson.module:jackson-module-kotlin" to the classpath
+    com:
+      storlead: error
+
+
+# 企业微信
+corp-wechat:
+  corpId: ww5323bd8ab4394132
+  # 这个是通讯录secret只能用来调用通讯录相关API使用
+  corpAddressSecret: FE-ofiE08oeT8DsccbigqPFWl6Nk8LRKBFffpL76Z-M
+  corpAgentId: 1000025
+  corpAgentSecret: MqG2_t0HB4L2KN6MsOVdpL48XFB26VkUyTsKHT0T6y0
+
+environment: prod
+domainname: https://sales.storlead.com
+
+system:
+  config:
+    orgSyncMode: 0  # 0:系统维护 1:同步OA, 2:同步企业微信
+
+  license:
+    httpUrl: http://localhost:10010${server.servlet.context-path}
+    activationCode: "activationCode"
+
+file:
+  mode: 1
+  filePath: /files
+  active: prod
+  mac:
+    path: ~/file/
+    avatar: ~/avatar/
+  linux:
+    path: /mnt/vdb/storlead/sales/file/email/attachments/
+    avatar: /mnt/vdb/storlead/sales/file/avatar/
+    jpeg: /mnt/vdb/storlead/sales/file/email/image/
+  windows:
+    path: D:\images\file\
+    avatar: D:\images\
+  # 文件大小 /M
+  maxSize: 100
+  avatarMaxSize: 5

+ 235 - 0
storlead-ai-api/src/main/resources/application-test.yml

@@ -0,0 +1,235 @@
+server:
+  port: 18090
+  tomcat:
+    max-swallow-size: -1
+    max-upload-size: 200MB
+    basedir: /mnt/vdb/storlead/sales/temp/${spring.profiles.active}
+  servlet:
+    context-path: /sales
+  compression:
+    enabled: true
+    min-response-size: 1024
+    mime-types: application/javascript,application/json,application/xml,text/html,text/xml,text/plain,text/css,image/*
+
+management:
+  endpoints:
+    web:
+      exposure:
+        include: metrics,httptrace
+
+spring:
+  servlet:
+    multipart:
+      max-file-size: 200MB
+      max-request-size: 200MB
+  ## quartz定时任务,采用数据库方式
+  #  quartz:
+  #    job-store-type: jdbc
+  #json 时间戳统一转换
+  jackson:
+    date-format:   yyyy-MM-dd HH:mm:ss
+    time-zone:   GMT+8
+  aop:
+    proxy-target-class: true
+  #配置freemarker
+  freemarker:
+    # 设置模板后缀名
+    suffix: .ftl
+    # 设置文档类型
+    content-type: text/html
+    # 设置页面编码格式
+    charset: UTF-8
+    # 设置页面缓存
+    cache: false
+    prefer-file-system-access: false
+    # 设置ftl文件路径
+    template-loader-path:
+      - classpath:/templates
+  # 设置静态文件路径,js,css等  #redis 配置
+#  cloud:
+#    nacos:
+#      config:
+#        name: sp-project-management
+#        username: nacos
+#        password: nacos
+#      discovery:
+#        server-addr: http://172.18.194.168:8848
+#        group: TEST
+#        namespace: 76d9e9c9-e012-4845-a0a6-c6ba7818b727
+#        username: nacos
+#        password: nacos
+#        ip: 172.18.194.168
+
+  redis:
+    host: test1.storlead.com
+    port: 59394
+    database: 15
+    lettuce:
+      pool:
+        max-wait: 100000
+        max-idle: 10
+        max-active: 100
+    timeout: 5000
+    password: bnoCWkyDqYA*ecT7FoL7
+
+  mvc:
+    static-path-pattern: /**
+  resource:
+    static-locations: classpath:/static/,classpath:/public/
+  autoconfigure:
+    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
+  datasource:
+    dynamic:
+      druid: # 全局druid参数,绝大部分值和默认保持一致。(现已支持的参数如下,不清楚含义不要乱设置)
+        # 连接池的配置信息
+        # 初始化大小,最小,最大
+        initial-size: 5
+        min-idle: 5
+        maxActive: 20
+        # 配置获取连接等待超时的时间
+        maxWait: 60000
+        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+        timeBetweenEvictionRunsMillis: 60000
+        # 配置一个连接在池中最小生存的时间,单位是毫秒
+        minEvictableIdleTimeMillis: 300000
+        validationQuery: SELECT 1 FROM DUAL
+        testWhileIdle: true
+        testOnBorrow: false
+        testOnReturn: false
+        # 打开PSCache,并且指定每个连接上PSCache的大小
+        poolPreparedStatements: true
+        maxPoolPreparedStatementPerConnectionSize: 20
+        # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
+        filters: stat,wall,slf4j
+        # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
+        connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
+      datasource:
+        master:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://mysql.test.storlead.com:39091/sp_sales_test?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: root
+          password: rCgRgLjH99Xvg5BN
+        management:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://mysql.test.storlead.com:39091/storlead_test?useSSL=false&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&noDatetimeStringSync=true&serverTimezone=Asia/Shanghai
+          username: root
+          password: rCgRgLjH99Xvg5BN
+        oa:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://139.159.206.64:65369/storlead_ecology_prod?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: storlead_ecology
+          password: 3raNoDvbo7jqbwtedQGQ
+#mybatis plus 设置
+mybatis-plus:
+  mapper-locations: classpath:/mapper/*/*Mapper.xml
+  # 实体扫描,多个 package 用逗号或者分号分隔
+  type-aliases-package: com.storlead.sales.modules.*.entity
+  type-enums-package: com.storlead.sales.enums
+    #  configuration:
+  #配置显示查询SQL
+  #    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+  #    default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler
+  global-config:
+    # 关闭MP3.0自带的banner
+    banner: false
+    db-config:
+      #主键类型  0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)";
+      id-type: 4
+      # 默认数据库表下划线命名
+      table-underline: true
+    #configuration:
+    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
+    #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+
+#应用专用配置
+lingcun :
+  path :
+    #文件上传根目录 设置
+    upload: /app/sp/sales/upload/${spring.profiles.active}
+    #webapp文件路径
+    webapp: /app/sp/sales/upload/${spring.profiles.active}
+  shiro:
+    excludeUrls: /lingcun/login,/lingcun/logout,/lingcun/getCheckCode
+  # 表单设计器配置
+  desform:
+    # 主题颜色(仅支持 16进制颜色代码)
+    theme-color: "#1890ff"
+  # 在线预览文件服务器地址配置
+  file-view-domain: http://localhost:8080/api/dist/app/view/
+  #
+sp:
+  oss:
+    endpoint: oss-cn-shenzhen.aliyuncs.com
+    accessKey: LTAI5t5n6K9ramopnY4KNQnM
+    secretKey: xmA6wObeUAKDux92DX3qfLNWAQAQZm
+    bucketName: sp-sales-test
+storlead:
+  project:
+    baseUrl: project.test.storlead.com
+
+system:
+  config:
+    orgSyncMode: 0  # 0:系统维护 1:同步OA, 2:同步企业微信
+  license:
+    httpUrl: http://localhost:10010${server.servlet.context-path}/tenant/license/getLicenseCode
+    activationCode: "activationCode"
+#Mybatis输出sql日志
+logging:
+  file:
+    # 日志存放目录
+    path: /mnt/vdb/storlead/sales/log/${spring.profiles.active}/
+  level:
+    root: info
+    io:
+      swagger:
+        models:
+          parameters:
+            AbstractSerializableParameter: error
+    c:
+      a:
+        icatch:
+          provider:
+            imp:
+              AssemblerImp: INFO
+    o:
+      s:
+        h:
+          c:
+            j:
+              Jackson2ObjectMapperBuilder: off # 禁止okhttp4 打印警告日志 For Jackson Kotlin classes support please add "com.fasterxml.jackson.module:jackson-module-kotlin" to the classpath
+    com:
+      storlead: INFO
+
+# 企业微信
+corp-wechat:
+  corpId: ww5323bd8ab4394132
+  # 这个是通讯录secret只能用来调用通讯录相关API使用
+  corpAddressSecret: FE-ofiE08oeT8DsccbigqPFWl6Nk8LRKBFffpL76Z-M
+  corpAgentId: 1000024
+  corpAgentSecret: T2N9RAs0ATGrgUEedAovo80a1Z4wpepO9eKn-_5qsBo
+
+environment: test
+domainname: https://sales.test.storlead.com
+
+file:
+  mode: 1
+  filePath: /files
+  active: test
+  mac:
+    path: ~/file/
+    avatar: ~/avatar/
+  linux:
+    path: /mnt/vdb/storlead/sales/file/email/attachments/
+    avatar: /mnt/vdb/storlead/sales/file/avatar/
+    jpeg: /mnt/vdb/storlead/sales/file/email/image/
+  windows:
+    path: D:\images\file\
+    avatar: D:\images\
+  # 文件大小 /M
+  maxSize: 100
+  avatarMaxSize: 5
+
+feign:
+  user-service:
+    app-name: sp-internal-gateway
+    context-path: /router/rest

+ 207 - 0
storlead-ai-api/src/main/resources/application-uat.yml

@@ -0,0 +1,207 @@
+server:
+  port: 10022
+  tomcat:
+    max-swallow-size: -1
+    basedir: /app/sp/okr/temp/${spring.profiles.active}
+  servlet:
+    context-path: /api
+  compression:
+    enabled: true
+    min-response-size: 1024
+    mime-types: application/javascript,application/json,application/xml,text/html,text/xml,text/plain,text/css,image/*
+
+management:
+  endpoints:
+    web:
+      exposure:
+        include: metrics,httptrace
+
+spring:
+  servlet:
+    multipart:
+      max-file-size: 20MB
+      max-request-size: 20MB
+  ## quartz定时任务,采用数据库方式
+  #  quartz:
+  #    job-store-type: jdbc
+  #json 时间戳统一转换
+  jackson:
+    date-format:   yyyy-MM-dd HH:mm:ss
+    time-zone:   GMT+8
+  aop:
+    proxy-target-class: true
+  #配置freemarker
+  freemarker:
+    # 设置模板后缀名
+    suffix: .ftl
+    # 设置文档类型
+    content-type: text/html
+    # 设置页面编码格式
+    charset: UTF-8
+    # 设置页面缓存
+    cache: false
+    prefer-file-system-access: false
+    # 设置ftl文件路径
+    template-loader-path:
+      - classpath:/templates
+  # 设置静态文件路径,js,css等  #redis 配置
+  redis:
+    host: test1.storlead.com
+    port: 59394
+    database: 15
+    lettuce:
+      pool:
+        max-wait: 100000
+        max-idle: 10
+        max-active: 100
+    timeout: 5000
+    password: bnoCWkyDqYA*ecT7FoL7
+  mvc:
+    static-path-pattern: /**
+  resource:
+    static-locations: classpath:/static/,classpath:/public/
+  autoconfigure:
+    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
+  datasource:
+    dynamic:
+      druid: # 全局druid参数,绝大部分值和默认保持一致。(现已支持的参数如下,不清楚含义不要乱设置)
+        # 连接池的配置信息
+        # 初始化大小,最小,最大
+        initial-size: 5
+        min-idle: 5
+        maxActive: 20
+        # 配置获取连接等待超时的时间
+        maxWait: 60000
+        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+        timeBetweenEvictionRunsMillis: 60000
+        # 配置一个连接在池中最小生存的时间,单位是毫秒
+        minEvictableIdleTimeMillis: 300000
+        validationQuery: SELECT 1 FROM DUAL
+        testWhileIdle: true
+        testOnBorrow: false
+        testOnReturn: false
+        # 打开PSCache,并且指定每个连接上PSCache的大小
+        poolPreparedStatements: true
+        maxPoolPreparedStatementPerConnectionSize: 20
+        # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
+        filters: stat,wall,slf4j
+        # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
+        connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
+      datasource:
+        master:
+          url: jdbc:mysql://mysql.test.storlead.com:39091/sp_tems_uat?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: sp_tems_test
+          password: NxTRSCcikjsynR9Ki
+          driver-class-name: com.mysql.jdbc.Driver
+        project:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://mysql.test.storlead.com:39091/sp_project_uat?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: root
+          password: rCgRgLjH99Xvg5BN
+        oa:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://storlead-ecology.mysql.rds.aliyuncs.com:65369/storlead_ecology_prod?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: storlead_admin
+          password: UqdiWfZca9bqP58QZdPZ
+#mybatis plus 设置
+mybatis-plus:
+  mapper-locations: classpath:/mapper/*/*Mapper.xml
+  # 实体扫描,多个 package 用逗号或者分号分隔
+  type-aliases-package: com.storlead.sales.modules.*.entity
+  type-enums-package: com.storlead.sales.modules.console.enums;com.storlead.sales.modules.perform.enums;com.storlead.sales.modules.project.enums
+  #  configuration:
+  #配置显示查询SQL
+  #    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+  #    default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler
+  global-config:
+    # 关闭MP3.0自带的banner
+    banner: false
+    db-config:
+      #主键类型  0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)";
+      id-type: 4
+      # 默认数据库表下划线命名
+      table-underline: true
+    #configuration:
+    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
+    #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+
+#应用专用配置
+lingcun :
+  path :
+    #文件上传根目录 设置
+    upload: /app/sp/okr/upload/${spring.profiles.active}
+    #webapp文件路径
+    webapp: /app/sp/okr/upload/${spring.profiles.active}
+  shiro:
+    excludeUrls: /lingcun/login,/lingcun/logout,/lingcun/getCheckCode
+  # 表单设计器配置
+  desform:
+    # 主题颜色(仅支持 16进制颜色代码)
+    theme-color: "#1890ff"
+  # 在线预览文件服务器地址配置
+  file-view-domain: http://localhost:8080/api/dist/app/view/
+  #
+sp:
+  oss:
+    endpoint: oss-cn-shenzhen.aliyuncs.com
+    accessKey: LTAI5t5n6K9ramopnY4KNQnM
+    secretKey: xmA6wObeUAKDux92DX3qfLNWAQAQZm
+    bucketName: sp-sales
+storlead:
+  project:
+    baseUrl: project.test.storlead.com
+
+#Mybatis输出sql日志
+logging:
+  file:
+    # 日志存放目录
+    path: /app/sp/okr/${spring.profiles.active}/log
+  level:
+    root: info
+    io:
+      swagger:
+        models:
+          parameters:
+            AbstractSerializableParameter: error
+    c:
+      a:
+        icatch:
+          provider:
+            imp:
+              AssemblerImp: debug
+    o:
+      s:
+        h:
+          c:
+            j:
+              Jackson2ObjectMapperBuilder: off # 禁止okhttp4 打印警告日志 For Jackson Kotlin classes support please add "com.fasterxml.jackson.module:jackson-module-kotlin" to the classpath
+    com:
+      storlead: debug
+
+# 企业微信
+corp-wechat:
+  corpId: ww5323bd8ab4394132
+  # 这个是通讯录secret只能用来调用通讯录相关API使用
+  corpAddressSecret: FE-ofiE08oeT8DsccbigqPFWl6Nk8LRKBFffpL76Z-M
+  corpAgentId: 1000024
+  corpAgentSecret: T2N9RAs0ATGrgUEedAovo80a1Z4wpepO9eKn-_5qsBo
+
+environment: test
+
+
+file:
+  mode: 1
+  filePath: /files
+  active: uat
+  mac:
+    path: ~/file/
+    avatar: ~/avatar/
+  linux:
+    path: /app/files/
+    avatar: /app/files/avatar/
+  windows:
+    path: D:\images\file\
+    avatar: D:\images\
+  # 文件大小 /M
+  maxSize: 100
+  avatarMaxSize: 5

+ 86 - 0
storlead-ai-api/src/main/resources/application.yml

@@ -0,0 +1,86 @@
+spring:
+  profiles:
+    active: dev
+  servlet:
+    multipart:
+      max-file-size: 50MB
+      max-request-size: 200MB
+  tomcat:
+    max-upload-size: 200MB
+
+  mvc:
+    pathmatch:
+      matching-strategy: ant_path_matcher
+
+  application:
+    name: sp-sales
+  main:
+    allow-circular-references: true
+    allow-bean-definition-overriding: true
+  # 定时任务
+#  quartz:
+#    jdbc:
+#      initialize-schema: never
+#    job-store-type: jdbc
+#    properties:
+#      org:
+#        quartz:
+#          dataSource:
+#            myDS: # 设置数据源名称
+#              driver: com.mysql.jdbc.Driver
+#              URL: jdbc:mysql://192.168.1.93:39091/sp_sales_dev?useSSL=false&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&noDatetimeStringSync=true&serverTimezone=Asia/Shanghai
+#              username: root
+#              password: DW8YRN*5!6u&Agt7N
+#          scheduler:
+#            instanceId: AUTO
+#            instanceName: quartzScheduler
+#          jobStore:
+#            useProperties: true
+#            isClustered: true
+#            maxMisfiresToHandleAtATime: 1
+#            tablePrefix: qrtz_
+#            dataSource: myDS
+##            class: org.quartz.impl.jdbcjobstore.LocalDataSourceJobStore
+#            class: org.quartz.impl.jdbcjobstore.JobStoreTX
+#            clusterCheckinInterval: 3600000
+#            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
+#          threadPool:
+#            threadCount: 10
+#            threadPriority: 5
+#            threadsInheritContextClassLoaderOfInitializingThread: true
+#            class: org.quartz.simpl.SimpleThreadPool
+  # 在上下文中没有Executor bean的情况下,Spring Boot会自动配置ThreadPoolTaskExecutor,并使用合理的默认值,这些默认值可以自动关联到异步任务执行(@EnableAsync)和Spring MVC异步请求处理。
+  #如果您已经在上下文中定义了一个自定义执行器,那么常规任务执行(即@EnableAsync)将透明地使用它,但是Spring MVC支持将不会被配置,因为它需要一个AsyncTaskExecutor实现(名为applicationTaskExecutor)。根据您的目标安排,您可以将执行程序更改为ThreadPoolTaskExecutor,或者定义一个ThreadPoolTaskExecutor和一个包装自定义执行程序的AsyncConfigurer。
+  task:
+    execution:
+      pool:
+        core-size: 10
+        max-size: 100
+        queue-capacity: 50
+
+swagger:
+  production: false
+  basic:
+    enable: false
+    username: lingcun
+    password: l123@.com
+
+fdfs:
+  so-timeout: 1501
+  connect-timeout:  601
+  thumb-image: # 缩略图
+      width: 60
+      height: 60
+  tracker-list: 113.106.164.42:22122
+
+weixin:
+  appid: wxe441c4feba0ca8dd
+  secret: 06f454271578d3ed1db2f970e5ef47fd
+
+gpt:
+  gptLength: 16000
+
+xh:
+  xhLength: 8000
+
+

+ 72 - 0
storlead-ai-api/target/classes/META-INF/spring-configuration-metadata.json

@@ -0,0 +1,72 @@
+{
+  "groups": [
+    {
+      "name": "ai.providers.openai",
+      "type": "com.storlead.ai.config.properties.OpenAiProperties",
+      "sourceType": "com.storlead.ai.config.properties.OpenAiProperties"
+    }
+  ],
+  "properties": [
+    {
+      "name": "ai.providers.openai.api-key",
+      "type": "java.lang.String",
+      "description": "API密钥",
+      "sourceType": "com.storlead.ai.config.properties.OpenAiProperties"
+    },
+    {
+      "name": "ai.providers.openai.base-url",
+      "type": "java.lang.String",
+      "description": "API基础URL",
+      "sourceType": "com.storlead.ai.config.properties.OpenAiProperties"
+    },
+    {
+      "name": "ai.providers.openai.default-max-tokens",
+      "type": "java.lang.Integer",
+      "description": "默认最大令牌数",
+      "sourceType": "com.storlead.ai.config.properties.OpenAiProperties"
+    },
+    {
+      "name": "ai.providers.openai.default-model",
+      "type": "java.lang.String",
+      "description": "默认模型",
+      "sourceType": "com.storlead.ai.config.properties.OpenAiProperties"
+    },
+    {
+      "name": "ai.providers.openai.default-temperature",
+      "type": "java.lang.Double",
+      "description": "默认温度参数",
+      "sourceType": "com.storlead.ai.config.properties.OpenAiProperties"
+    },
+    {
+      "name": "ai.providers.openai.enabled",
+      "type": "java.lang.Boolean",
+      "description": "是否启用OpenAI",
+      "sourceType": "com.storlead.ai.config.properties.OpenAiProperties"
+    },
+    {
+      "name": "ai.providers.openai.max-retries",
+      "type": "java.lang.Integer",
+      "description": "最大重试次数",
+      "sourceType": "com.storlead.ai.config.properties.OpenAiProperties"
+    },
+    {
+      "name": "ai.providers.openai.stream-enabled",
+      "type": "java.lang.Boolean",
+      "description": "是否启用流式响应",
+      "sourceType": "com.storlead.ai.config.properties.OpenAiProperties"
+    },
+    {
+      "name": "ai.providers.openai.supported-models",
+      "type": "java.util.List<java.lang.String>",
+      "description": "支持的模型列表",
+      "sourceType": "com.storlead.ai.config.properties.OpenAiProperties"
+    },
+    {
+      "name": "ai.providers.openai.timeout-seconds",
+      "type": "java.lang.Integer",
+      "description": "请求超时时间(秒)",
+      "sourceType": "com.storlead.ai.config.properties.OpenAiProperties"
+    }
+  ],
+  "hints": []
+}

+ 276 - 0
storlead-ai-api/target/classes/application-dev.yml

@@ -0,0 +1,276 @@
+server:
+  port: 18090
+  tomcat:
+    max-swallow-size: -1
+    max-upload-size: 200MB
+    basedir: /app/temp
+  servlet:
+    context-path: /router/rest
+
+  compression:
+    enabled: true
+    min-response-size: 1024
+    mime-types: application/javascript,application/json,application/xml,text/html,text/xml,text/plain,text/css,image/*
+
+management:
+  endpoints:
+    web:
+      exposure:
+        include: metrics,httptrace
+
+spring:
+  servlet:
+    multipart:
+      max-file-size: 200MB
+      max-request-size: 200MB
+  ## quartz定时任务,采用数据库方式
+  #  quartz:
+  #    job-store-type: jdbc
+  #json 时间戳统一转换
+  jackson:
+    date-format:   yyyy-MM-dd HH:mm:ss
+    time-zone:   GMT+8
+  aop:
+    proxy-target-class: true
+  #配置freemarker
+  freemarker:
+    # 设置模板后缀名
+    suffix: .ftl
+    # 设置文档类型
+    content-type: text/html
+    # 设置页面编码格式
+    charset: UTF-8
+    # 设置页面缓存
+    cache: false
+    prefer-file-system-access: false
+    # 设置ftl文件路径
+    template-loader-path:
+      - classpath:/templates
+  # 设置静态文件路径,js,css等  #redis 配置
+#  cloud:
+#    nacos:
+#      discovery:
+#        server-addr: http://192.168.1.93:8848
+#        group: DEV
+#        namespace: 1b26d8af-529f-4118-9519-47b6a4aaaa4e
+#        username: nacos
+#        password: nacos
+
+  redis:
+    host: 192.168.1.69
+    port: 6379
+    database: 13
+    lettuce:
+      pool:
+        max-wait: 100000
+        max-idle: 10
+        max-active: 100
+    timeout: 5000
+    password: 123456
+
+  mvc:
+    static-path-pattern: /**
+  resource:
+    static-locations: classpath:/static/,classpath:/public/
+  autoconfigure:
+    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
+  datasource:
+    dynamic:
+      druid: # 全局druid参数,绝大部分值和默认保持一致。(现已支持的参数如下,不清楚含义不要乱设置)
+        # 连接池的配置信息
+        # 初始化大小,最小,最大
+        initial-size: 5
+        min-idle: 5
+        maxActive: 20
+        # 配置获取连接等待超时的时间
+        maxWait: 60000
+        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+        timeBetweenEvictionRunsMillis: 60000
+        # 配置一个连接在池中最小生存的时间,单位是毫秒
+        minEvictableIdleTimeMillis: 300000
+        validationQuery: SELECT 1 FROM DUAL
+        testWhileIdle: true
+        testOnBorrow: false
+        testOnReturn: false
+        # 打开PSCache,并且指定每个连接上PSCache的大小
+        poolPreparedStatements: true
+        maxPoolPreparedStatementPerConnectionSize: 20
+        # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
+        filters: stat,wall,slf4j
+        # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
+        connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
+      datasource:
+        master:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://192.168.1.69:3306/sp_sales_dev?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: storlead
+          password: DW8YRN*5!6u&Agt7N
+#          driver-class-name: com.mysql.jdbc.Driver
+#          url: jdbc:mysql://mysql.test.storlead.com:39091/sp_sales_test?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+#          username: root
+#          password: rCgRgLjH99Xvg5BN
+
+#          driver-class-name: com.mysql.jdbc.Driver
+#          url: jdbc:mysql://139.159.206.64:65369/sp_sales_system_prod?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&rewriteBatchedStatements=true
+#          username: sp_sales_prod
+#          password: MsKLJue01MLIYnd2*0ReMQ
+        management:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://mysql.test.storlead.com:39091/storlead_test?useSSL=false&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&noDatetimeStringSync=true&serverTimezone=Asia/Shanghai
+          username: root
+          password: rCgRgLjH99Xvg5BN
+
+#          driver-class-name: com.mysql.jdbc.Driver
+#          url: jdbc:mysql://139.159.206.64:65369/storlead_prod?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&rewriteBatchedStatements=true
+#          username: storlead_platform
+#          password: QkfgG7Cw6E&*PlvYYw==oBfjSf2zw
+        oa:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://139.159.206.64:65369/storlead_ecology_prod?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: storlead_ecology
+          password: 3raNoDvbo7jqbwtedQGQ
+ai:
+  providers:
+    # OpenAI配置
+    openai:
+      enabled: true
+      api-key:
+      base-url: http://47.112.196.2:11434
+      default-model: deepseek-r1:14b
+      timeout-seconds: 30
+      max-retries: 3
+      default-temperature: 0.7
+      default-max-tokens: 1000
+      stream-enabled: true
+      supported-models:
+        - deepseek-r1:14b
+        - deepseek-r1:8b
+        - gpt-oss:20b
+
+#mybatis plus 设置
+mybatis-plus:
+  mapper-locations: classpath:/mapper/*/*Mapper.xml
+  # 实体扫描,多个 package 用逗号或者分号分隔
+  type-aliases-package: com.storlead.sales.modules.*.pojo.entity
+  typeEnumsPackage: com.storlead.sales.modules.console.enums;com.storlead.sales.modules.perform.enums
+#  configuration:
+  #配置显示查询SQL
+  #    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+#    default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler
+  global-config:
+    # 关闭MP3.0自带的banner
+    banner: false
+    db-config:
+      #主键类型  0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)";
+      id-type: 4
+      # 默认数据库表下划线命名
+      table-underline: true
+    #configuration:
+    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
+    #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+
+#应用专用配置
+lingcun :
+  path :
+    #文件上传根目录 设置
+    upload: /app/upload
+    #webapp文件路径
+    webapp: /app/upload
+  shiro:
+    excludeUrls: /lingcun/login,/lingcun/logout,/lingcun/getCheckCode
+  # 表单设计器配置
+  desform:
+    # 主题颜色(仅支持 16进制颜色代码)
+    theme-color: "#1890ff"
+  # 在线预览文件服务器地址配置
+  file-view-domain: http://localhost:8080/api/dist/app/view/
+
+sp:
+  oss:
+    endpoint: oss-cn-shenzhen.aliyuncs.com
+    accessKey: LTAI5t5n6K9ramopnY4KNQnM
+    #    LTAIWmbTnmF9lzjy
+    secretKey: xmA6wObeUAKDux92DX3qfLNWAQAQZm
+    #    v346LNyCRIZCvTTgYTr5ikJsneBAaZ
+    bucketName: sp-sales-test
+
+
+message:
+  task:
+    enable : false
+  send:
+    enable : true
+storlead:
+  project:
+    baseUrl: http://127.0.0.1:8101/sp-project
+
+#Mybatis输出sql日志
+logging:
+  file:
+    # 日志存放目录
+    path: /app/sp/${spring.application.name}/log
+  level:
+    root: info
+    io:
+      swagger:
+        models:
+          parameters:
+            AbstractSerializableParameter: error
+    c:
+      a:
+        icatch:
+          provider:
+            imp:
+              AssemblerImp: debug
+    o:
+      s:
+        h:
+          c:
+            j:
+              Jackson2ObjectMapperBuilder: off # 禁止okhttp4 打印警告日志 For Jackson Kotlin classes support please add "com.fasterxml.jackson.module:jackson-module-kotlin" to the classpath
+    com:
+      storlead: debug
+
+
+# 企业微信
+#corp-wechat:
+#  corpId: ww5323bd8ab4394132
+#  # 这个是通讯录secret只能用来调用通讯录相关API使用
+#  corpAddressSecret: FE-ofiE08oeT8DsccbigqPFWl6Nk8LRKBFffpL76Z-M
+#  corpAgentId: 1000024
+#  corpAgentSecret: T2N9RAs0ATGrgUEedAovo80a1Z4wpepO9eKn-_5qsBo
+
+
+environment: dev
+domainname: https://sales.test.storlead.com
+
+system:
+  config:
+    orgSyncMode: 0  # 0:系统维护 1:同步OA, 2:同步企业微信
+  license:
+    httpUrl: http://localhost:10010${server.servlet.context-path}/tenant/license/getLicenseCode
+    activationCode: "activationCode"
+
+
+file:
+  mode: 1
+  filePath: /files
+  active: dev
+  mac:
+    path: ~/file/
+    avatar: ~/avatar/
+  linux:
+    path: /app/files/
+    avatar: /app/files/avatar/
+  windows:
+    path: D:\mail\attachment\
+    avatar: D:\mail\
+    jpeg: D:\mail\image\
+  # 文件大小 /M
+  maxSize: 100
+  avatarMaxSize: 5
+
+feign:
+  user-service:
+    app-name: sp-internal-gateway
+    context-path: /router/rest

+ 223 - 0
storlead-ai-api/target/classes/application-prod.yml

@@ -0,0 +1,223 @@
+#开发模式
+debug: false
+server:
+  port: 18094
+  tomcat:
+    max-swallow-size: -1
+    max-upload-size: 200MB
+    basedir: /mnt/vdb/storlead/sales/temp
+  servlet:
+    context-path: /sales
+  compression:
+    enabled: true
+    min-response-size: 1024
+    mime-types: application/javascript,application/json,application/xml,text/html,text/xml,text/plain,text/css,image/*
+
+management:
+  endpoints:
+    web:
+      exposure:
+        include: metrics,httptrace
+
+spring:
+  servlet:
+    multipart:
+      max-file-size: 200MB
+      max-request-size: 200MB
+  ## quartz定时任务,采用数据库方式
+  #  quartz:
+  #    job-store-type: jdbc
+  #json 时间戳统一转换
+  jackson:
+    date-format:   yyyy-MM-dd HH:mm:ss
+    time-zone:   GMT+8
+  aop:
+    proxy-target-class: true
+  #配置freemarker
+  freemarker:
+    # 设置模板后缀名
+    suffix: .ftl
+    # 设置文档类型
+    content-type: text/html
+    # 设置页面编码格式
+    charset: UTF-8
+    # 设置页面缓存
+    cache: false
+    prefer-file-system-access: false
+    # 设置ftl文件路径
+    template-loader-path:
+      - classpath:/templates
+  # 设置静态文件路径,js,css等  #redis 配置
+  redis:
+    database: 14
+    host: 192.168.0.210
+    lettuce:
+      pool:
+        max-active: 8   #最大连接数据库连接数,设 0 为没有限制
+        max-idle: 8     #最大等待连接中的数量,设 0 为没有限制
+        max-wait: -1ms  #最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。
+        min-idle: 0     #最小等待连接中的数量,设 0 为没有限制
+      shutdown-timeout: 100ms
+    password: 'B3f@NT4y%etekQaDkufd'
+    port: 56379
+  mvc:
+    static-path-pattern: /**
+  resource:
+    static-locations: classpath:/static/,classpath:/public/
+  autoconfigure:
+    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
+  datasource:
+    dynamic:
+      druid: # 全局druid参数,绝大部分值和默认保持一致。(现已支持的参数如下,不清楚含义不要乱设置)
+        # 连接池的配置信息
+        # 初始化大小,最小,最大
+        initial-size: 5
+        min-idle: 5
+        maxActive: 20
+        # 配置获取连接等待超时的时间
+        maxWait: 60000
+        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+        timeBetweenEvictionRunsMillis: 60000
+        # 配置一个连接在池中最小生存的时间,单位是毫秒
+        minEvictableIdleTimeMillis: 300000
+        validationQuery: SELECT 1 FROM DUAL
+        testWhileIdle: true
+        testOnBorrow: false
+        testOnReturn: false
+        # 打开PSCache,并且指定每个连接上PSCache的大小
+        poolPreparedStatements: true
+        maxPoolPreparedStatementPerConnectionSize: 20
+        # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
+        filters: stat,wall,slf4j
+        # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
+        connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
+      datasource:
+        master:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://storlead.internal.cn-south-1.mysql.rds.myhuaweicloud.com:65369/sp_sales_system_prod?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&rewriteBatchedStatements=true
+          username: sp_sales_prod
+          password: MsKLJue01MLIYnd2*0ReMQ
+        management:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://storlead.internal.cn-south-1.mysql.rds.myhuaweicloud.com:65369/storlead_prod?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&rewriteBatchedStatements=true
+          username: storlead_platform
+          password: QkfgG7Cw6E&*PlvYYw==oBfjSf2zw
+        oa:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://storlead.internal.cn-south-1.mysql.rds.myhuaweicloud.com:65369/storlead_ecology_prod?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: storlead_ecology
+          password: 3raNoDvbo7jqbwtedQGQ
+#mybatis plus 设置
+mybatis-plus:
+  mapper-locations: classpath:/mapper/*/*Mapper.xml
+  # 实体扫描,多个 package 用逗号或者分号分隔
+  type-aliases-package: com.storlead.sales.modules.*.entity
+  type-enums-package: com.storlead.sales.modules.console.enums;com.storlead.sales.modules.perform.enums;com.storlead.sales.modules.project.enums
+    #  configuration:
+  #配置显示查询SQL
+  #    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+  #    default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler
+  global-config:
+    # 关闭MP3.0自带的banner
+    banner: false
+    db-config:
+      #主键类型  0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)";
+      id-type: 4
+      # 默认数据库表下划线命名
+      table-underline: true
+    #configuration:
+    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
+    #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+
+#应用专用配置
+lingcun :
+  path :
+    #文件上传根目录 设置
+    upload: /mnt/vdb/storlead/sales/upload
+    #webapp文件路径
+    webapp: /mnt/vdb/storlead/sales/upload
+  shiro:
+    excludeUrls: /lingcun/login,/lingcun/logout,/lingcun/getCheckCode
+  # 表单设计器配置
+  desform:
+    # 主题颜色(仅支持 16进制颜色代码)
+    theme-color: "#1890ff"
+  # 在线预览文件服务器地址配置
+  file-view-domain: http://localhost:8080/api/dist/app/view/
+  #
+sp:
+  oss:
+    endpoint: oss-cn-shenzhen.aliyuncs.com
+    accessKey: LTAI5t5n6K9ramopnY4KNQnM
+    secretKey: xmA6wObeUAKDux92DX3qfLNWAQAQZm
+    bucketName: sp-sales-prod
+
+
+storlead:
+  project:
+    baseUrl: project.storlead.com
+
+#Mybatis输出sql日志
+logging:
+  file:
+    # 日志存放目录
+    path: /mnt/vdb/storlead/sales/log/
+  level:
+    root: info
+    io:
+      swagger:
+        models:
+          parameters:
+            AbstractSerializableParameter: error
+    c:
+      a:
+        icatch:
+          provider:
+            imp:
+              AssemblerImp: debug
+    o:
+      s:
+        h:
+          c:
+            j:
+              Jackson2ObjectMapperBuilder: off # 禁止okhttp4 打印警告日志 For Jackson Kotlin classes support please add "com.fasterxml.jackson.module:jackson-module-kotlin" to the classpath
+    com:
+      storlead: error
+
+
+# 企业微信
+corp-wechat:
+  corpId: ww5323bd8ab4394132
+  # 这个是通讯录secret只能用来调用通讯录相关API使用
+  corpAddressSecret: FE-ofiE08oeT8DsccbigqPFWl6Nk8LRKBFffpL76Z-M
+  corpAgentId: 1000025
+  corpAgentSecret: MqG2_t0HB4L2KN6MsOVdpL48XFB26VkUyTsKHT0T6y0
+
+environment: prod
+domainname: https://sales.storlead.com
+
+system:
+  config:
+    orgSyncMode: 0  # 0:系统维护 1:同步OA, 2:同步企业微信
+
+  license:
+    httpUrl: http://localhost:10010${server.servlet.context-path}
+    activationCode: "activationCode"
+
+file:
+  mode: 1
+  filePath: /files
+  active: prod
+  mac:
+    path: ~/file/
+    avatar: ~/avatar/
+  linux:
+    path: /mnt/vdb/storlead/sales/file/email/attachments/
+    avatar: /mnt/vdb/storlead/sales/file/avatar/
+    jpeg: /mnt/vdb/storlead/sales/file/email/image/
+  windows:
+    path: D:\images\file\
+    avatar: D:\images\
+  # 文件大小 /M
+  maxSize: 100
+  avatarMaxSize: 5

+ 235 - 0
storlead-ai-api/target/classes/application-test.yml

@@ -0,0 +1,235 @@
+server:
+  port: 18090
+  tomcat:
+    max-swallow-size: -1
+    max-upload-size: 200MB
+    basedir: /mnt/vdb/storlead/sales/temp/${spring.profiles.active}
+  servlet:
+    context-path: /sales
+  compression:
+    enabled: true
+    min-response-size: 1024
+    mime-types: application/javascript,application/json,application/xml,text/html,text/xml,text/plain,text/css,image/*
+
+management:
+  endpoints:
+    web:
+      exposure:
+        include: metrics,httptrace
+
+spring:
+  servlet:
+    multipart:
+      max-file-size: 200MB
+      max-request-size: 200MB
+  ## quartz定时任务,采用数据库方式
+  #  quartz:
+  #    job-store-type: jdbc
+  #json 时间戳统一转换
+  jackson:
+    date-format:   yyyy-MM-dd HH:mm:ss
+    time-zone:   GMT+8
+  aop:
+    proxy-target-class: true
+  #配置freemarker
+  freemarker:
+    # 设置模板后缀名
+    suffix: .ftl
+    # 设置文档类型
+    content-type: text/html
+    # 设置页面编码格式
+    charset: UTF-8
+    # 设置页面缓存
+    cache: false
+    prefer-file-system-access: false
+    # 设置ftl文件路径
+    template-loader-path:
+      - classpath:/templates
+  # 设置静态文件路径,js,css等  #redis 配置
+#  cloud:
+#    nacos:
+#      config:
+#        name: sp-project-management
+#        username: nacos
+#        password: nacos
+#      discovery:
+#        server-addr: http://172.18.194.168:8848
+#        group: TEST
+#        namespace: 76d9e9c9-e012-4845-a0a6-c6ba7818b727
+#        username: nacos
+#        password: nacos
+#        ip: 172.18.194.168
+
+  redis:
+    host: test1.storlead.com
+    port: 59394
+    database: 15
+    lettuce:
+      pool:
+        max-wait: 100000
+        max-idle: 10
+        max-active: 100
+    timeout: 5000
+    password: bnoCWkyDqYA*ecT7FoL7
+
+  mvc:
+    static-path-pattern: /**
+  resource:
+    static-locations: classpath:/static/,classpath:/public/
+  autoconfigure:
+    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
+  datasource:
+    dynamic:
+      druid: # 全局druid参数,绝大部分值和默认保持一致。(现已支持的参数如下,不清楚含义不要乱设置)
+        # 连接池的配置信息
+        # 初始化大小,最小,最大
+        initial-size: 5
+        min-idle: 5
+        maxActive: 20
+        # 配置获取连接等待超时的时间
+        maxWait: 60000
+        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+        timeBetweenEvictionRunsMillis: 60000
+        # 配置一个连接在池中最小生存的时间,单位是毫秒
+        minEvictableIdleTimeMillis: 300000
+        validationQuery: SELECT 1 FROM DUAL
+        testWhileIdle: true
+        testOnBorrow: false
+        testOnReturn: false
+        # 打开PSCache,并且指定每个连接上PSCache的大小
+        poolPreparedStatements: true
+        maxPoolPreparedStatementPerConnectionSize: 20
+        # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
+        filters: stat,wall,slf4j
+        # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
+        connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
+      datasource:
+        master:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://mysql.test.storlead.com:39091/sp_sales_test?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: root
+          password: rCgRgLjH99Xvg5BN
+        management:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://mysql.test.storlead.com:39091/storlead_test?useSSL=false&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&noDatetimeStringSync=true&serverTimezone=Asia/Shanghai
+          username: root
+          password: rCgRgLjH99Xvg5BN
+        oa:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://139.159.206.64:65369/storlead_ecology_prod?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: storlead_ecology
+          password: 3raNoDvbo7jqbwtedQGQ
+#mybatis plus 设置
+mybatis-plus:
+  mapper-locations: classpath:/mapper/*/*Mapper.xml
+  # 实体扫描,多个 package 用逗号或者分号分隔
+  type-aliases-package: com.storlead.sales.modules.*.entity
+  type-enums-package: com.storlead.sales.enums
+    #  configuration:
+  #配置显示查询SQL
+  #    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+  #    default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler
+  global-config:
+    # 关闭MP3.0自带的banner
+    banner: false
+    db-config:
+      #主键类型  0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)";
+      id-type: 4
+      # 默认数据库表下划线命名
+      table-underline: true
+    #configuration:
+    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
+    #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+
+#应用专用配置
+lingcun :
+  path :
+    #文件上传根目录 设置
+    upload: /app/sp/sales/upload/${spring.profiles.active}
+    #webapp文件路径
+    webapp: /app/sp/sales/upload/${spring.profiles.active}
+  shiro:
+    excludeUrls: /lingcun/login,/lingcun/logout,/lingcun/getCheckCode
+  # 表单设计器配置
+  desform:
+    # 主题颜色(仅支持 16进制颜色代码)
+    theme-color: "#1890ff"
+  # 在线预览文件服务器地址配置
+  file-view-domain: http://localhost:8080/api/dist/app/view/
+  #
+sp:
+  oss:
+    endpoint: oss-cn-shenzhen.aliyuncs.com
+    accessKey: LTAI5t5n6K9ramopnY4KNQnM
+    secretKey: xmA6wObeUAKDux92DX3qfLNWAQAQZm
+    bucketName: sp-sales-test
+storlead:
+  project:
+    baseUrl: project.test.storlead.com
+
+system:
+  config:
+    orgSyncMode: 0  # 0:系统维护 1:同步OA, 2:同步企业微信
+  license:
+    httpUrl: http://localhost:10010${server.servlet.context-path}/tenant/license/getLicenseCode
+    activationCode: "activationCode"
+#Mybatis输出sql日志
+logging:
+  file:
+    # 日志存放目录
+    path: /mnt/vdb/storlead/sales/log/${spring.profiles.active}/
+  level:
+    root: info
+    io:
+      swagger:
+        models:
+          parameters:
+            AbstractSerializableParameter: error
+    c:
+      a:
+        icatch:
+          provider:
+            imp:
+              AssemblerImp: INFO
+    o:
+      s:
+        h:
+          c:
+            j:
+              Jackson2ObjectMapperBuilder: off # 禁止okhttp4 打印警告日志 For Jackson Kotlin classes support please add "com.fasterxml.jackson.module:jackson-module-kotlin" to the classpath
+    com:
+      storlead: INFO
+
+# 企业微信
+corp-wechat:
+  corpId: ww5323bd8ab4394132
+  # 这个是通讯录secret只能用来调用通讯录相关API使用
+  corpAddressSecret: FE-ofiE08oeT8DsccbigqPFWl6Nk8LRKBFffpL76Z-M
+  corpAgentId: 1000024
+  corpAgentSecret: T2N9RAs0ATGrgUEedAovo80a1Z4wpepO9eKn-_5qsBo
+
+environment: test
+domainname: https://sales.test.storlead.com
+
+file:
+  mode: 1
+  filePath: /files
+  active: test
+  mac:
+    path: ~/file/
+    avatar: ~/avatar/
+  linux:
+    path: /mnt/vdb/storlead/sales/file/email/attachments/
+    avatar: /mnt/vdb/storlead/sales/file/avatar/
+    jpeg: /mnt/vdb/storlead/sales/file/email/image/
+  windows:
+    path: D:\images\file\
+    avatar: D:\images\
+  # 文件大小 /M
+  maxSize: 100
+  avatarMaxSize: 5
+
+feign:
+  user-service:
+    app-name: sp-internal-gateway
+    context-path: /router/rest

+ 207 - 0
storlead-ai-api/target/classes/application-uat.yml

@@ -0,0 +1,207 @@
+server:
+  port: 10022
+  tomcat:
+    max-swallow-size: -1
+    basedir: /app/sp/okr/temp/${spring.profiles.active}
+  servlet:
+    context-path: /api
+  compression:
+    enabled: true
+    min-response-size: 1024
+    mime-types: application/javascript,application/json,application/xml,text/html,text/xml,text/plain,text/css,image/*
+
+management:
+  endpoints:
+    web:
+      exposure:
+        include: metrics,httptrace
+
+spring:
+  servlet:
+    multipart:
+      max-file-size: 20MB
+      max-request-size: 20MB
+  ## quartz定时任务,采用数据库方式
+  #  quartz:
+  #    job-store-type: jdbc
+  #json 时间戳统一转换
+  jackson:
+    date-format:   yyyy-MM-dd HH:mm:ss
+    time-zone:   GMT+8
+  aop:
+    proxy-target-class: true
+  #配置freemarker
+  freemarker:
+    # 设置模板后缀名
+    suffix: .ftl
+    # 设置文档类型
+    content-type: text/html
+    # 设置页面编码格式
+    charset: UTF-8
+    # 设置页面缓存
+    cache: false
+    prefer-file-system-access: false
+    # 设置ftl文件路径
+    template-loader-path:
+      - classpath:/templates
+  # 设置静态文件路径,js,css等  #redis 配置
+  redis:
+    host: test1.storlead.com
+    port: 59394
+    database: 15
+    lettuce:
+      pool:
+        max-wait: 100000
+        max-idle: 10
+        max-active: 100
+    timeout: 5000
+    password: bnoCWkyDqYA*ecT7FoL7
+  mvc:
+    static-path-pattern: /**
+  resource:
+    static-locations: classpath:/static/,classpath:/public/
+  autoconfigure:
+    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
+  datasource:
+    dynamic:
+      druid: # 全局druid参数,绝大部分值和默认保持一致。(现已支持的参数如下,不清楚含义不要乱设置)
+        # 连接池的配置信息
+        # 初始化大小,最小,最大
+        initial-size: 5
+        min-idle: 5
+        maxActive: 20
+        # 配置获取连接等待超时的时间
+        maxWait: 60000
+        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+        timeBetweenEvictionRunsMillis: 60000
+        # 配置一个连接在池中最小生存的时间,单位是毫秒
+        minEvictableIdleTimeMillis: 300000
+        validationQuery: SELECT 1 FROM DUAL
+        testWhileIdle: true
+        testOnBorrow: false
+        testOnReturn: false
+        # 打开PSCache,并且指定每个连接上PSCache的大小
+        poolPreparedStatements: true
+        maxPoolPreparedStatementPerConnectionSize: 20
+        # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
+        filters: stat,wall,slf4j
+        # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
+        connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
+      datasource:
+        master:
+          url: jdbc:mysql://mysql.test.storlead.com:39091/sp_tems_uat?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: sp_tems_test
+          password: NxTRSCcikjsynR9Ki
+          driver-class-name: com.mysql.jdbc.Driver
+        project:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://mysql.test.storlead.com:39091/sp_project_uat?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: root
+          password: rCgRgLjH99Xvg5BN
+        oa:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://storlead-ecology.mysql.rds.aliyuncs.com:65369/storlead_ecology_prod?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: storlead_admin
+          password: UqdiWfZca9bqP58QZdPZ
+#mybatis plus 设置
+mybatis-plus:
+  mapper-locations: classpath:/mapper/*/*Mapper.xml
+  # 实体扫描,多个 package 用逗号或者分号分隔
+  type-aliases-package: com.storlead.sales.modules.*.entity
+  type-enums-package: com.storlead.sales.modules.console.enums;com.storlead.sales.modules.perform.enums;com.storlead.sales.modules.project.enums
+  #  configuration:
+  #配置显示查询SQL
+  #    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+  #    default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler
+  global-config:
+    # 关闭MP3.0自带的banner
+    banner: false
+    db-config:
+      #主键类型  0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)";
+      id-type: 4
+      # 默认数据库表下划线命名
+      table-underline: true
+    #configuration:
+    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
+    #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+
+#应用专用配置
+lingcun :
+  path :
+    #文件上传根目录 设置
+    upload: /app/sp/okr/upload/${spring.profiles.active}
+    #webapp文件路径
+    webapp: /app/sp/okr/upload/${spring.profiles.active}
+  shiro:
+    excludeUrls: /lingcun/login,/lingcun/logout,/lingcun/getCheckCode
+  # 表单设计器配置
+  desform:
+    # 主题颜色(仅支持 16进制颜色代码)
+    theme-color: "#1890ff"
+  # 在线预览文件服务器地址配置
+  file-view-domain: http://localhost:8080/api/dist/app/view/
+  #
+sp:
+  oss:
+    endpoint: oss-cn-shenzhen.aliyuncs.com
+    accessKey: LTAI5t5n6K9ramopnY4KNQnM
+    secretKey: xmA6wObeUAKDux92DX3qfLNWAQAQZm
+    bucketName: sp-sales
+storlead:
+  project:
+    baseUrl: project.test.storlead.com
+
+#Mybatis输出sql日志
+logging:
+  file:
+    # 日志存放目录
+    path: /app/sp/okr/${spring.profiles.active}/log
+  level:
+    root: info
+    io:
+      swagger:
+        models:
+          parameters:
+            AbstractSerializableParameter: error
+    c:
+      a:
+        icatch:
+          provider:
+            imp:
+              AssemblerImp: debug
+    o:
+      s:
+        h:
+          c:
+            j:
+              Jackson2ObjectMapperBuilder: off # 禁止okhttp4 打印警告日志 For Jackson Kotlin classes support please add "com.fasterxml.jackson.module:jackson-module-kotlin" to the classpath
+    com:
+      storlead: debug
+
+# 企业微信
+corp-wechat:
+  corpId: ww5323bd8ab4394132
+  # 这个是通讯录secret只能用来调用通讯录相关API使用
+  corpAddressSecret: FE-ofiE08oeT8DsccbigqPFWl6Nk8LRKBFffpL76Z-M
+  corpAgentId: 1000024
+  corpAgentSecret: T2N9RAs0ATGrgUEedAovo80a1Z4wpepO9eKn-_5qsBo
+
+environment: test
+
+
+file:
+  mode: 1
+  filePath: /files
+  active: uat
+  mac:
+    path: ~/file/
+    avatar: ~/avatar/
+  linux:
+    path: /app/files/
+    avatar: /app/files/avatar/
+  windows:
+    path: D:\images\file\
+    avatar: D:\images\
+  # 文件大小 /M
+  maxSize: 100
+  avatarMaxSize: 5

+ 86 - 0
storlead-ai-api/target/classes/application.yml

@@ -0,0 +1,86 @@
+spring:
+  profiles:
+    active: dev
+  servlet:
+    multipart:
+      max-file-size: 50MB
+      max-request-size: 200MB
+  tomcat:
+    max-upload-size: 200MB
+
+  mvc:
+    pathmatch:
+      matching-strategy: ant_path_matcher
+
+  application:
+    name: sp-sales
+  main:
+    allow-circular-references: true
+    allow-bean-definition-overriding: true
+  # 定时任务
+#  quartz:
+#    jdbc:
+#      initialize-schema: never
+#    job-store-type: jdbc
+#    properties:
+#      org:
+#        quartz:
+#          dataSource:
+#            myDS: # 设置数据源名称
+#              driver: com.mysql.jdbc.Driver
+#              URL: jdbc:mysql://192.168.1.93:39091/sp_sales_dev?useSSL=false&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&noDatetimeStringSync=true&serverTimezone=Asia/Shanghai
+#              username: root
+#              password: DW8YRN*5!6u&Agt7N
+#          scheduler:
+#            instanceId: AUTO
+#            instanceName: quartzScheduler
+#          jobStore:
+#            useProperties: true
+#            isClustered: true
+#            maxMisfiresToHandleAtATime: 1
+#            tablePrefix: qrtz_
+#            dataSource: myDS
+##            class: org.quartz.impl.jdbcjobstore.LocalDataSourceJobStore
+#            class: org.quartz.impl.jdbcjobstore.JobStoreTX
+#            clusterCheckinInterval: 3600000
+#            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
+#          threadPool:
+#            threadCount: 10
+#            threadPriority: 5
+#            threadsInheritContextClassLoaderOfInitializingThread: true
+#            class: org.quartz.simpl.SimpleThreadPool
+  # 在上下文中没有Executor bean的情况下,Spring Boot会自动配置ThreadPoolTaskExecutor,并使用合理的默认值,这些默认值可以自动关联到异步任务执行(@EnableAsync)和Spring MVC异步请求处理。
+  #如果您已经在上下文中定义了一个自定义执行器,那么常规任务执行(即@EnableAsync)将透明地使用它,但是Spring MVC支持将不会被配置,因为它需要一个AsyncTaskExecutor实现(名为applicationTaskExecutor)。根据您的目标安排,您可以将执行程序更改为ThreadPoolTaskExecutor,或者定义一个ThreadPoolTaskExecutor和一个包装自定义执行程序的AsyncConfigurer。
+  task:
+    execution:
+      pool:
+        core-size: 10
+        max-size: 100
+        queue-capacity: 50
+
+swagger:
+  production: false
+  basic:
+    enable: false
+    username: lingcun
+    password: l123@.com
+
+fdfs:
+  so-timeout: 1501
+  connect-timeout:  601
+  thumb-image: # 缩略图
+      width: 60
+      height: 60
+  tracker-list: 113.106.164.42:22122
+
+weixin:
+  appid: wxe441c4feba0ca8dd
+  secret: 06f454271578d3ed1db2f970e5ef47fd
+
+gpt:
+  gptLength: 16000
+
+xh:
+  xhLength: 8000
+
+

BIN
storlead-ai-api/target/classes/com/storlead/ai/AiPlatformApplication.class


BIN
storlead-ai-api/target/classes/com/storlead/ai/config/properties/OpenAiProperties.class


BIN
storlead-ai-api/target/classes/com/storlead/ai/controller/AiChatController.class


BIN
storlead-ai-api/target/classes/com/storlead/ai/core/AiProviderType.class


BIN
storlead-ai-api/target/classes/com/storlead/ai/core/AiService.class


BIN
storlead-ai-api/target/classes/com/storlead/ai/core/ChatRequest$Builder.class


BIN
storlead-ai-api/target/classes/com/storlead/ai/core/ChatRequest.class


BIN
storlead-ai-api/target/classes/com/storlead/ai/core/ChatResponse$Builder.class


BIN
storlead-ai-api/target/classes/com/storlead/ai/core/ChatResponse.class


BIN
storlead-ai-api/target/classes/com/storlead/ai/exception/AiServiceException.class


BIN
storlead-ai-api/target/classes/com/storlead/ai/factory/AiServiceFactory.class


BIN
storlead-ai-api/target/classes/com/storlead/ai/factory/AiServiceFactoryManager.class


BIN
storlead-ai-api/target/classes/com/storlead/ai/factory/impl/OpenAiServiceFactory.class


BIN
storlead-ai-api/target/classes/com/storlead/ai/service/AiChatService.class


BIN
storlead-ai-api/target/classes/com/storlead/ai/service/impl/OpenAiService.class


BIN
storlead-ai-api/target/classes/com/storlead/ai/util/HttpClientUtil.class


+ 386 - 0
storlead-dependencies/pom.xml

@@ -0,0 +1,386 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.storlead.boot</groupId>
+    <artifactId>storlead-dependencies</artifactId>
+    <version>${revision}</version>
+
+    <packaging>pom</packaging>
+    <name>${project.artifactId}</name>
+    <description>整个项目的依赖版本</description>
+
+    <properties>
+        <revision>2.7.0-jdk11-SNAPSHOT</revision>
+        <flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
+        <java.version>1.8</java.version>
+        <maven.compiler.source>${java.version}</maven.compiler.source>
+        <maven.compiler.target>${java.version}</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <spring.boot.starter.parent>2.7.0</spring.boot.starter.parent>
+        <spring.boot.version>2.7.0</spring.boot.version>
+        <knife4j.version>4.5.0</knife4j.version>
+        <spring-web.version>5.3.32</spring-web.version>
+        <jsoup.version>1.18.3</jsoup.version>
+        <mockito-inline.version>4.11.0</mockito-inline.version>
+        <lombok.version>1.18.36</lombok.version>
+        <spring.framework.version>5.3.20</spring.framework.version>
+        <spring.security.version>5.8.14</spring.security.version>
+        <fastjson.version>2.0.23</fastjson.version>
+        <!--    5.8.35-->
+        <hutool.version>5.4.3</hutool.version>
+        <springdoc.version>1.7.0</springdoc.version>
+        <swagger.ui.version>3.0.0</swagger.ui.version>
+        <swagger.version>3.0.0</swagger.version>
+        <swagger.annotations.version>1.5.22</swagger.annotations.version>
+        <spring.web.socket.version>1.5.3</spring.web.socket.version>
+
+        <!-- DB 相关 -->
+        <druid.version>1.1.24</druid.version>
+        <mybatis.version>3.5.17</mybatis.version>
+        <mybatis-plus.version>3.1.2</mybatis-plus.version>
+        <datasource.spring.boot.starter>2.5.4</datasource.spring.boot.starter>
+        <dynamic-datasource.version>4.3.1</dynamic-datasource.version>
+        <mybatis-plus-join.version>1.4.13</mybatis-plus-join.version>
+        <mysql.connector.version>5.1.47</mysql.connector.version>
+        <guava.version>33.4.0-jre</guava.version>
+        <aviator.version>5.2.7</aviator.version>
+        <transmittable.thread.version>2.14.0</transmittable.thread.version>
+        <google.code.gson.version>2.10.1</google.code.gson.version>
+        <squareup.okhttp.version>4.12.0</squareup.okhttp.version>
+        <okhttp.sse.version>4.10.0</okhttp.sse.version>
+        <spring.validation.version>2.6.3</spring.validation.version>
+        <apache.httpcore5.client5.version>5.2.1</apache.httpcore5.client5.version>
+        <apache.httpcore5.version>5.2.1</apache.httpcore5.version>
+        <apache.httpcore5-h2.version>5.2.1</apache.httpcore5-h2.version>
+        <apache.httpasyncclient.version>4.1.5</apache.httpasyncclient.version>
+        <io.jsonwebtoken.version>0.9.1</io.jsonwebtoken.version>
+        <io.jsonwebtoken.java.jwt.version>3.7.0</io.jsonwebtoken.java.jwt.version>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework</groupId>
+                <artifactId>spring-web</artifactId>
+                <version>${spring-web.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.storlead.boot</groupId>
+                <artifactId>storlead-core</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.storlead.boot</groupId>
+                <artifactId>storlead-auth</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.storlead.boot</groupId>
+                <artifactId>storlead-redis</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+<!--            <dependency>-->
+<!--                <groupId>com.storlead.boot</groupId>-->
+<!--                <artifactId>storlead-framework</artifactId>-->
+<!--                <version>${revision}</version>-->
+<!--            </dependency>-->
+
+            <dependency>
+                <groupId>com.storlead.boot</groupId>
+                <artifactId>storlead-share-aicms</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.storlead.boot</groupId>
+                <artifactId>storlead-web</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>mybatis-plus-generator</artifactId>
+                <version>${mybatis-plus.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.storlead.boot</groupId>
+                <artifactId>storlead-mybatis</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.storlead.boot</groupId>
+                <artifactId>storlead-common</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.storlead.boot</groupId>
+                <artifactId>storlead-system</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+<!--            &lt;!&ndash;swagger&ndash;&gt;-->
+            <dependency>
+                <groupId>io.springfox</groupId>
+                <artifactId>springfox-swagger2</artifactId>
+                <version>${swagger.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>io.springfox</groupId>
+                <artifactId>springfox-swagger-ui</artifactId>
+                <version>${swagger.ui.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>io.swagger</groupId>
+                <artifactId>swagger-annotations</artifactId>
+                <version>${swagger.annotations.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.google.code.gson</groupId>
+                <artifactId>gson</artifactId>
+                <version>${google.code.gson.version}</version> <!-- 建议使用最新版本 -->
+            </dependency>
+
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>fastjson</artifactId>
+                <version>${fastjson.version}</version>
+            </dependency>
+
+
+            <dependency>
+                <groupId>org.apache.httpcomponents.client5</groupId>
+                <artifactId>httpclient5</artifactId>
+                <version>${apache.httpcore5.client5.version}</version>
+            </dependency>
+
+            <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents.core5/httpcore5 -->
+            <dependency>
+                <groupId>org.apache.httpcomponents.core5</groupId>
+                <artifactId>httpcore5</artifactId>
+                <version>${apache.httpcore5.version}</version>
+            </dependency>
+
+            <!--JWT-->
+            <dependency>
+                <groupId>com.auth0</groupId>
+                <artifactId>java-jwt</artifactId>
+                <version>${io.jsonwebtoken.java.jwt.version}</version>
+            </dependency>
+
+            <!--  jwt 需要,移除会报错  -->
+            <dependency>
+                <groupId>io.jsonwebtoken</groupId>
+                <artifactId>jjwt</artifactId>
+                <version>${io.jsonwebtoken.version}</version>
+            </dependency>
+            <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents.core5/httpcore5-h2 -->
+            <dependency>
+                <groupId>org.apache.httpcomponents.core5</groupId>
+                <artifactId>httpcore5-h2</artifactId>
+                <version>${apache.httpcore5-h2.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.apache.httpcomponents</groupId>
+                <artifactId>httpasyncclient</artifactId>
+                <version>${apache.httpasyncclient.version}</version>
+            </dependency>
+<!--            <dependency>-->
+<!--                <groupId>com.github.xiaoymin</groupId>-->
+<!--                <artifactId>swagger-bootstrap-ui</artifactId>-->
+<!--                <version>1.8.7</version>-->
+<!--            </dependency>-->
+
+
+<!--            <dependency>-->
+<!--                <groupId>com.squareup.okhttp3</groupId>-->
+<!--                <artifactId>okhttp</artifactId>-->
+<!--                <version>${squareup.okhttp.version}</version> &lt;!&ndash; 2023年最新稳定版 &ndash;&gt;-->
+<!--            </dependency>-->
+
+            <dependency>
+                <groupId>com.squareup.okhttp3</groupId>
+                <artifactId>okhttp-sse</artifactId>
+                <version>${okhttp.sse.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.projectlombok</groupId>
+                <artifactId>lombok</artifactId>
+                <version>${lombok.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>cn.hutool</groupId>
+                <artifactId>hutool-all</artifactId>
+                <version>${hutool.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring.boot.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.github.xiaoymin</groupId>
+                <artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
+                <version>${knife4j.version}</version>
+            </dependency>
+<!--            <dependency>-->
+<!--                <groupId>cn.hutool</groupId>-->
+<!--                <artifactId>hutool-all</artifactId>-->
+<!--                <version>4.5.11</version>-->
+<!--            </dependency>-->
+
+            <dependency>
+                <groupId>com.googlecode.aviator</groupId>
+                <artifactId>aviator</artifactId>
+                <version>${aviator.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>transmittable-thread-local</artifactId>
+                <version>${transmittable.thread.version}</version> <!-- 建议使用最新版本 -->
+            </dependency>
+
+            <!-- mybatis-plus -->
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>mybatis-plus-boot-starter</artifactId>
+                <version>${mybatis-plus.version}</version>
+            </dependency>
+
+            <!--mysql-->
+            <dependency>
+                <groupId>mysql</groupId>
+                <artifactId>mysql-connector-java</artifactId>
+                <version>${mysql.connector.version}</version>
+            </dependency>
+
+            <!-- 动态数据源 -->
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
+                <version>${datasource.spring.boot.starter}</version>
+            </dependency>
+
+            <!-- druid -->
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>druid-spring-boot-starter</artifactId>
+                <version>${druid.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.github.yulichang</groupId>
+                <artifactId>mybatis-plus-join-boot-starter</artifactId> <!-- MyBatis 联表查询 -->
+                <version>${mybatis-plus-join.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.java-websocket</groupId>
+                <artifactId>Java-WebSocket</artifactId>
+                <version>${spring.web.socket.version}</version>
+            </dependency>
+
+            <!-- Web 相关 -->
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-starter-web</artifactId>
+                <version>2.7.0</version>
+                <exclusions>
+                    <exclusion>
+                        <groupId>org.springframework</groupId>
+                        <artifactId>spring-web</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+
+            <dependency>
+                <groupId>org.jsoup</groupId>
+                <artifactId>jsoup</artifactId>
+                <version>${jsoup.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.google.guava</groupId>
+                <artifactId>guava</artifactId>
+                <version>${guava.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.mockito</groupId>
+                <artifactId>mockito-inline</artifactId>
+                <version>${mockito-inline.version}</version> <!-- 支持 Mockito 的 final 类与 static 方法的 mock -->
+            </dependency>
+
+            <dependency>
+                <groupId>org.springdoc</groupId>
+                <artifactId>springdoc-openapi-ui</artifactId>
+                <version>${springdoc.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-starter-test</artifactId>
+                <version>${spring.boot.version}</version>
+                <exclusions>
+                    <exclusion>
+                        <artifactId>asm</artifactId>
+                        <groupId>org.ow2.asm</groupId>
+                    </exclusion>
+                    <exclusion>
+                        <groupId>org.mockito</groupId>
+                        <artifactId>mockito-core</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+
+        </dependencies>
+
+    </dependencyManagement>
+
+<!--    <build>-->
+<!--        <plugins>-->
+<!--            &lt;!&ndash; 统一 revision 版本 &ndash;&gt;-->
+<!--            <plugin>-->
+<!--                <groupId>org.codehaus.mojo</groupId>-->
+<!--                <artifactId>flatten-maven-plugin</artifactId>-->
+<!--                <version>${flatten-maven-plugin.version}</version>-->
+<!--                <configuration>-->
+<!--                    <flattenMode>bom</flattenMode>-->
+<!--                    <updatePomFile>true</updatePomFile>-->
+<!--                </configuration>-->
+<!--                <executions>-->
+<!--                    <execution>-->
+<!--                        <goals>-->
+<!--                            <goal>flatten</goal>-->
+<!--                        </goals>-->
+<!--                        <id>flatten</id>-->
+<!--                        <phase>process-resources</phase>-->
+<!--                    </execution>-->
+<!--                    <execution>-->
+<!--                        <goals>-->
+<!--                            <goal>clean</goal>-->
+<!--                        </goals>-->
+<!--                        <id>flatten.clean</id>-->
+<!--                        <phase>clean</phase>-->
+<!--                    </execution>-->
+<!--                </executions>-->
+<!--            </plugin>-->
+<!--        </plugins>-->
+<!--    </build>-->
+</project>

+ 24 - 0
storlead-framework/pom.xml

@@ -0,0 +1,24 @@
+<?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>storlead-platform</artifactId>
+        <groupId>com.storlead.boot</groupId>
+        <version>${revision}</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>storlead-framework</artifactId>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>storlead-common</module>
+        <module>storlead-redis</module>
+        <module>storlead-mybatis</module>
+        <module>storlead-web</module>
+        <module>storlead-core</module>
+        <module>storlead-auth</module>
+    </modules>
+
+</project>

+ 31 - 0
storlead-framework/storlead-auth/pom.xml

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>com.storlead.boot</groupId>
+        <artifactId>storlead-framework</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>storlead-auth</artifactId>
+    <packaging>jar</packaging>
+    <name>${project.artifactId}</name>
+
+
+    <dependencies>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-common</artifactId>
+            <version>${revision}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-core</artifactId>
+            <version>${revision}</version>
+        </dependency>
+
+    </dependencies>
+
+</project>

+ 133 - 0
storlead-framework/storlead-auth/src/main/java/com/storlead/framework/auth/jwt/JwtUtil.java

@@ -0,0 +1,133 @@
+package com.storlead.framework.auth.jwt;
+
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.JSONObject;
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.algorithms.Algorithm;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.JwtBuilder;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+
+import java.util.*;
+
+/**
+ * @program: storlead-storlead-Platform
+ * @description:
+ * @author: chenkq
+ * @create: 2025-07-09 14:53
+ */
+public class JwtUtil {
+
+    /**
+     * 失效时间: 1 天 =24*3600*1000 ms
+     */
+    public static final long  EFFECTIVE_TIME = 1 * 24 * 3600 * 1000;
+    /**
+     * 私钥
+     */
+    public static final String APP_SECRET = "sp_user_2012";
+    /**
+     * token 前缀
+     */
+    public static final String APP_KEY = "sp_APP_KEY";
+    public static final String CLAIMS_KEY = "sp_CLAIMS_KEY";
+    /**
+     * 用户登录成功后生成Jwt
+     * 使用Hs256算法  私匙使用用户密码
+     *
+     * @param jsonInfo
+     * @param subjectKey
+     * @return
+     */
+    public static String createJWT(String jsonInfo,String subjectKey) {
+
+        //指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。
+        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
+
+        long nowMillis = System.currentTimeMillis();
+        Date now = new Date(nowMillis);
+        //创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
+        //EntityUtils.entityToMap(user)
+
+        Map<String, Object> claims = new HashMap<>();
+        claims.put(CLAIMS_KEY, JSONObject.toJSON(jsonInfo));
+        //生成签发人
+        String subject = subjectKey;
+        //下面就是在为payload添加各种标准声明和私有声明了 JwtBuilder
+        //这里其实就是new一个JwtBuilder,设置jwt的body
+        JwtBuilder builder = Jwts.builder()
+                //如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
+                .setClaims(claims)
+                //设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
+                .setId(UUID.randomUUID().toString())
+                //iat: jwt的签发时间
+                .setIssuedAt(now)
+                //代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。
+                .setSubject(subject)
+                //设置签名使用的签名算法和签名使用的秘钥Algorithm
+                .signWith(signatureAlgorithm, APP_KEY);
+
+        return builder.compact();
+    }
+
+
+    /**
+     * Token的解密
+     * @param jwtToken 加密后的token
+     * @return
+     */
+    public static String parseJWT(String jwtToken) {
+        //签名秘钥,和生成的签名的秘钥一模一样
+        Claims claims = Jwts.parser()
+                //设置签名的秘钥
+                .setSigningKey(APP_KEY)
+                //设置需要解析的jwt
+                .parseClaimsJws(jwtToken).getBody();
+        LinkedHashMap linkedHashMap = (LinkedHashMap)claims.get(CLAIMS_KEY);
+        String json =JSONObject.toJSONString(linkedHashMap);
+        return json;
+    }
+
+
+
+    //设置token的密钥
+//    private final static String SECRET = "token123";
+
+    public static String getGptToken(String apiKey) {
+        String[] apiKeySet = apiKey.split("\\.");
+        String apiKeyId = apiKeySet[0];
+
+        Date signDate = new Date();
+        Date date = new Date(System.currentTimeMillis() + EFFECTIVE_TIME);//过期时间
+        Algorithm algorithm = Algorithm.HMAC256(apiKeySet[1]);//进行加密算法
+
+        //token的Header信息
+        Map<String, Object> map = new HashMap<>();
+        map.put("alg", "HS256");
+        map.put("sign_type", "SIGN");
+
+        String token= JWT.create()
+                //(token的Header信息)
+                .withHeader(map)
+                //设置当前签发时间(token的Payload信息)
+                .withClaim("timestamp",signDate)
+
+                //设置token过期时间(token的Payload信息)
+                .withClaim("exp",date)
+                //自定义存放用户id在tokne中(token的自定义Payload信息)
+                .withClaim("api_key", apiKeyId)
+                //自定义存放用户名在token中(token的自定义Payload信息)
+//                .withClaim("username", user.getName())
+                //(token的Signature信息)
+                .sign(algorithm);
+        return token;
+    }
+
+
+    public static void main(String[] args) {
+        String apiKey = "89934b295eb37f5d5750643ba2b18dae.nnR0vfmj3rgMOgD6";
+
+        System.out.println(getGptToken(apiKey));
+    }
+}

+ 18 - 0
storlead-framework/storlead-auth/src/main/java/com/storlead/framework/auth/login/CurrentEmployeeInfo.java

@@ -0,0 +1,18 @@
+package com.storlead.framework.auth.login;
+
+import com.storlead.framework.common.constant.UserCacheKeyConstants;
+import com.storlead.framework.core.context.Context;
+import lombok.Data;
+import lombok.extern.log4j.Log4j2;
+
+/**
+ * @program: storlead-storlead-Platform
+ * @description:
+ * @author: chenkq
+ * @create: 2025-07-10 10:58
+ */
+@Log4j2
+@Data
+public class CurrentEmployeeInfo {
+    private String acount;
+}

+ 41 - 0
storlead-framework/storlead-auth/src/main/java/com/storlead/framework/auth/login/LoginEmployee.java

@@ -0,0 +1,41 @@
+package com.storlead.framework.auth.login;
+
+import com.storlead.framework.common.constant.UserCacheKeyConstants;
+import com.storlead.framework.core.context.Context;
+import lombok.extern.log4j.Log4j2;
+
+/**
+ * @program: storlead-storlead-Platform
+ * @description:
+ * @author: chenkq
+ * @create: 2025-07-10 11:00
+ */
+@Log4j2
+public class LoginEmployee {
+
+    /**
+     * 获取当前用户信息
+     * @return
+     */
+    public static CurrentEmployeeInfo getCurrentEmpInfo(){
+        try {
+            CurrentEmployeeInfo employeeInfo = Context.getContext().getAttribute(UserCacheKeyConstants.LOGIN_USER_INFO_KEY, CurrentEmployeeInfo.class);
+            return employeeInfo;
+        } catch (Exception e) {
+            return null;
+        }
+    }
+    /**
+     * 获取当前用户信息
+     * @return
+     */
+    public static Long getCurrentUserId(){
+        try {
+            Long currentUserId = Context.getContext().getAttribute(UserCacheKeyConstants.LOGIN_USER_INFO_ID_KEY, Long.class);
+            return currentUserId;
+        } catch (Exception e) {
+            log.error("error - getCurrentUserId",e);
+            return null;
+        }
+    }
+}

+ 29 - 0
storlead-framework/storlead-auth/src/main/java/com/storlead/framework/auth/vo/LoginUser.java

@@ -0,0 +1,29 @@
+package com.storlead.framework.auth.vo;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+
+/**
+ * @program: storlead-storlead-Platform
+ * @description:
+ * @author: chenkq
+ * @create: 2025-06-16 17:26
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+public class LoginUser implements Serializable {
+
+    /**
+     * 登录人id
+     */
+    private Long id;
+
+    /**
+     * 登录人账号
+     */
+    private String userName;
+}

+ 151 - 0
storlead-framework/storlead-common/pom.xml

@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>com.storlead.boot</groupId>
+        <artifactId>storlead-framework</artifactId>
+        <version>${revision}</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>storlead-common</artifactId>
+    <packaging>jar</packaging>
+    <name>${project.artifactId}</name>
+
+    <dependencies>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.swagger</groupId>
+            <artifactId>swagger-annotations</artifactId>
+        </dependency>
+
+        <!--JWT-->
+        <dependency>
+            <groupId>com.auth0</groupId>
+            <artifactId>java-jwt</artifactId>
+        </dependency>
+
+        <!--  jwt 需要,移除会报错  -->
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.googlecode.aviator</groupId>
+            <artifactId>aviator</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+        <!-- excel导出-->
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi</artifactId>
+            <version>4.1.2</version>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+            <version>4.1.2</version>
+            <scope>compile</scope>
+        </dependency>
+
+
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml-schemas</artifactId>
+            <version>4.1.2</version>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-ui</artifactId>
+            <scope>provided</scope> <!-- 设置为 provided,主要是 PageParam 使用到 -->
+        </dependency>
+
+        <dependency>
+            <groupId>ma.glasnost.orika</groupId>
+            <artifactId>orika-core</artifactId>
+            <version>1.5.4</version>
+            <scope>compile</scope>
+        </dependency>
+
+        <!--   导出到word(循环图片)     -->
+        <!-- word导出  方式:easypoi-->
+        <dependency>
+            <groupId>cn.afterturn</groupId>
+            <artifactId>easypoi-base</artifactId>
+            <version>4.4.0</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.afterturn</groupId>
+            <artifactId>easypoi-web</artifactId>
+            <version>4.4.0</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.afterturn</groupId>
+            <artifactId>easypoi-annotation</artifactId>
+            <version>4.4.0</version>
+        </dependency>
+
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+<!--        <dependency>-->
+<!--            <groupId>com.squareup.okhttp3</groupId>-->
+<!--            <artifactId>okhttp</artifactId>-->
+<!--        </dependency>-->
+
+<!--        <dependency>-->
+<!--            <groupId>com.squareup.okhttp3</groupId>-->
+<!--            <artifactId>okhttp-sse</artifactId>-->
+<!--        </dependency>-->
+        <!-- https://mvnrepository.com/artifact/org.freemarker/freemarker -->
+        <dependency>
+            <groupId>org.freemarker</groupId>
+            <artifactId>freemarker</artifactId>
+            <version>2.3.32</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+        </dependency>
+
+    </dependencies>
+</project>

+ 32 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/CacheConstant.java

@@ -0,0 +1,32 @@
+package com.storlead.framework.common.constant;
+
+/**
+ * @author: huangxutao
+ * @date: 2019-06-14
+ * @description: 缓存常量
+ */
+public interface CacheConstant {
+
+	/**
+	 * 缓存用户信息
+	 */
+	public static final String SYS_USERS_CACHE = "sys:cache:user";
+
+	/**
+	 * 全部部门信息缓存
+	 */
+	public static final String SYS_DEPARTS_CACHE = "sys:cache:depart:alldata";
+
+
+	/**
+	 * 全部部门ids缓存
+	 */
+	public static final String SYS_DEPART_IDS_CACHE = "sys:cache:depart:allids";
+
+
+	/**
+	 * 测试缓存key
+	 */
+	public static final String TEST_DEMO_CACHE = "test:demo";
+
+}

+ 41 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/CodeGenerateInterface.java

@@ -0,0 +1,41 @@
+package com.storlead.framework.common.constant;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-08-21 16:27
+ */
+public interface CodeGenerateInterface {
+    // 统一编码
+    String CODE_KEY = "";
+    //订单费用
+    String ORDER_COST_CODE= "OC";
+    //订单票据
+    String ORDER_INVOICE_CODE= "OI";
+    //回款记录
+    String ORDER_RECEIVE_CODE= "OP";
+    //退款记录
+    String ORDER_REFUND_CODE= "ORF";
+    //报销单
+    String ORDER_REIMBURSE_BILL_CODE= "ORB";
+    //客户/线索编码
+    String LIAISON_CODE= "LC";
+    //客户/线索编码
+    String CUSTOMER_CODE= "CC";
+    //商机编码
+    String BUSINESS_CODE= "BC";
+    //订单编码
+    String ORDER_FORM_CODE= "OFC";
+    //工单编码
+    String WORK_ORDER_CODE= "WOC";
+    // 报价编码
+    String QUOTATION_CODE= "QC";
+    //任务
+    String TASK_CODE= "TC";
+    //邮件
+    String EMAIL_CODE= "EM";
+
+    //邮件
+    String BULLETIN_CODE= "BUC";
+}

+ 81 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/CommonConstant.java

@@ -0,0 +1,81 @@
+package com.storlead.framework.common.constant;
+
+public interface CommonConstant {
+
+	/**
+	 * 正常状态
+	 */
+	Integer STATUS_NORMAL = 1;
+
+	/**
+	 * 审批未通过状态
+	 */
+	Integer STATUS_NOT_ADOPT = -1;
+
+	/**
+	 * 禁用状态
+	 */
+	Integer STATUS_DISABLE = 0;
+
+	/**
+	 * 未删除
+	 */
+	Integer DEL_FLAG_0 = 0;
+
+    /**
+     * 删除
+     */
+    Integer DEL_FLAG_1 = 1;
+
+	/** {@code 500 Server Error} (HTTP/1.0 - RFC 1945) */
+    Integer SC_INTERNAL_SERVER_ERROR_500 = 500;
+    /** {@code 200 OK} (HTTP/1.0 - RFC 1945) */
+    Integer SC_OK_200 = 200;
+
+    /**
+     * 业务通用异常
+     */
+    Integer BUSSINESS_COMM_ERROR = 19000;
+
+    /**访问权限认证未通过 510*/
+    Integer SC_JEECG_NO_AUTHZ=510;
+
+    /** 登录用户Shiro权限缓存KEY前缀 */
+    public static String PREFIX_USER_SHIRO_CACHE  = "shiro:cache:com.storlead.sales.modules.shiro.authc.ShiroRealm.authorizationCache:";
+    /** 登录用户Token令牌缓存KEY前缀 */
+    String PREFIX_USER_TOKEN  = "prefix_user_token_";
+
+    /**
+     * 是否用户已被冻结 1(解冻)正常 2冻结
+     */
+    Integer USER_FREEZE = 2;
+
+    /**字典翻译文本后缀*/
+    String DICT_TEXT_SUFFIX = "_dictText";
+
+    String USER_CACHE = "userCache";
+
+    String CAPCHA_CACHE = "capchaCache";
+
+    //长度超限
+    String LENGTH_EXCEEDS_THE_LIMIT = "length exceeds the limit";
+
+    /**
+     * 撤回状态
+     */
+    Integer STATUS_WITH_DRAW = 2;
+
+    /**
+     * 待发放状态
+     */
+    Integer STATUS_NOT_ISSUED = 3;
+
+    /**
+     * 已发放状态
+     */
+    Integer STATUS_ISSUED = 4;
+    /**
+     * 已完成状态
+     */
+    Integer STATUS_COMPLETE = 5;
+}

+ 30 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/DSConstants.java

@@ -0,0 +1,30 @@
+package com.storlead.framework.common.constant;
+
+import lombok.Data;
+
+/**
+ * DBConstants
+ *
+ * @author blank
+ * @date 2021-6-16 上午 10:17
+ */
+@Data
+public class DSConstants {
+
+    /**
+     * 数据源分组 master库
+     */
+    public static final String DATASOURCE_MASTER = "master";
+
+    /**
+     * 数据源分组 oa库
+     */
+    public static final String DATASOURCE_OA = "oa";
+
+    /**
+     * 数据源分组 oa库
+     */
+    public static final String DATASOURCE_MANAGEMENT = "management";
+
+
+}

+ 60 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/DataBaseConstant.java

@@ -0,0 +1,60 @@
+package com.storlead.framework.common.constant;
+/**
+ * 数据库上下文常量
+ */
+public interface DataBaseConstant {
+	//*********数据库类型****************************************
+	public static final String DB_TYPE_MYSQL = "MYSQL";
+	public static final String DB_TYPE_ORACLE = "ORACLE";
+	public static final String DB_TYPE_POSTGRESQL = "POSTGRESQL";
+	public static final String DB_TYPE_SQLSERVER = "SQLSERVER";
+
+	//*********系统上下文变量****************************************
+	/**
+	 * 数据-系统用户编码(对应登录用户账号)
+	 */
+	public static final String SYS_USER_CODE = "sysUserCode";
+	/**
+	 * 数据-系统用户编码(对应登录用户账号)
+	 */
+	public static final String SYS_USER_CODE_TABLE = "sys_user_code";
+
+	/**
+	 * 登录用户真实姓名
+	 */
+	public static final String SYS_USER_NAME = "sysUserName";
+	/**
+	 * 登录用户真实姓名
+	 */
+	public static final String SYS_USER_NAME_TABLE = "sys_user_name";
+	/**
+	 * 系统日期"yyyy-MM-dd"
+	 */
+	public static final String SYS_DATE = "sysDate";
+	/**
+	 * 系统日期"yyyy-MM-dd"
+	 */
+	public static final String SYS_DATE_TABLE = "sys_date";
+	/**
+	 * 系统时间"yyyy-MM-dd HH:mm"
+	 */
+	public static final String SYS_TIME = "sysTime";
+	/**
+	 * 系统时间"yyyy-MM-dd HH:mm"
+	 */
+	public static final String SYS_TIME_TABLE = "sys_time";
+	//*********系统上下文变量****************************************
+
+
+	//*********系统建表标准字段****************************************
+
+	/**
+	 * 业务流程状态
+	 */
+	public static final String BPM_STATUS = "bpmStatus";
+	/**
+	 * 业务流程状态
+	 */
+	public static final String BPM_STATUS_TABLE = "bpm_status";
+	//*********系统建表标准字段****************************************
+}

+ 12 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/DefContants.java

@@ -0,0 +1,12 @@
+package com.storlead.framework.common.constant;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-03-29 17:37
+ */
+public class DefContants {
+    public static String ACCESS_TOKEN = "token";
+    public static String AUTHORIZATION = "Authorization";
+}

+ 14 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/LongToStringSerializer.java

@@ -0,0 +1,14 @@
+package com.storlead.framework.common.constant;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import java.io.IOException;
+
+public class LongToStringSerializer extends JsonSerializer<Long> {
+
+    @Override
+    public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+        gen.writeNumber(value);
+    }
+}

+ 11 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/SystemConstant.java

@@ -0,0 +1,11 @@
+package com.storlead.framework.common.constant;
+
+/**
+ * @program: sp-sales
+ * @description: 系统产量
+ * @author: chenkq
+ * @create: 2022-07-25 20:22
+ */
+public class SystemConstant {
+
+}

+ 24 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/UserCacheKeyConstants.java

@@ -0,0 +1,24 @@
+package com.storlead.framework.common.constant;
+
+/**
+ * @program: storlead-storlead-Platform
+ * @description:
+ * @author: chenkq
+ * @create: 2025-06-17 15:39
+ */
+public class UserCacheKeyConstants {
+        /**
+         * 数据源分组 master库
+         */
+        public static final String LOGIN_USER_INFO_KEY = "login_user_info";
+
+        /**
+         * 数据源分组 project库
+         */
+        public static final String LOGIN_USER_INFO_TOKEN_KEY = "login_user_token_key";
+
+        /**
+         * 数据源分组 oa库
+         */
+        public static final String LOGIN_USER_INFO_ID_KEY = "login_user_user_id";
+}

+ 26 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/enums/DataTypeEnum.java

@@ -0,0 +1,26 @@
+package com.storlead.framework.common.enums;
+
+/**
+ * @program: sp-sales-gotr
+ * @description:
+ * @author: chenkq
+ * @create: 2023-07-25 16:31
+ */
+public enum DataTypeEnum {
+
+    STRING(0, "字符串"),
+    INTEGER(1, "整数"),
+    DOUBLE(2, "小数"),
+    BIGDECIMAL(3, "金额"),
+    TIME(4, "时间"),
+    DATE(5, "日期"),
+    ;
+    private Integer code;
+
+    private String desc;
+
+    DataTypeEnum(Integer code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+}

+ 6 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/package-info.java

@@ -0,0 +1,6 @@
+/**
+ * 基础的通用类,和框架无关
+ *
+ * 例如说,CommonResult 为通用返回
+ */
+package com.storlead.framework.common;

+ 40 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/property/YamlPropertyResourceFactory.java

@@ -0,0 +1,40 @@
+package com.storlead.framework.common.property;
+
+import org.springframework.boot.env.YamlPropertySourceLoader;
+import org.springframework.core.env.PropertiesPropertySource;
+import org.springframework.core.env.PropertySource;
+import org.springframework.core.io.support.EncodedResource;
+import org.springframework.core.io.support.PropertySourceFactory;
+import org.springframework.lang.Nullable;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Optional;
+import java.util.Properties;
+
+/**
+ * 让 PropertySourceFactory 支持读取 yml/yaml格式配置
+ *
+ * @author blank
+ * @since 2021-8-13 下午 4:08
+ */
+public class YamlPropertyResourceFactory implements PropertySourceFactory {
+    /**
+     * Create a {@link PropertySource} that wraps the given resource.
+     *
+     * @param name     the name of the property source
+     * @param encodedResource the resource (potentially encoded) to wrap
+     * @return the new {@link PropertySource} (never {@code null})
+     * @throws IOException if resource resolution failed
+     */
+    @Override
+    public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource encodedResource) throws IOException {
+        String resourceName = Optional.ofNullable(name).orElse(encodedResource.getResource().getFilename());
+        if (resourceName.endsWith(".yml") || resourceName.endsWith(".yaml")) {//yaml资源文件
+            List<PropertySource<?>> yamlSources = new YamlPropertySourceLoader().load(resourceName, encodedResource.getResource());
+            return yamlSources.get(0);
+        } else {//返回空的Properties
+            return new PropertiesPropertySource(resourceName, new Properties());
+        }
+    }
+}

+ 44 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/system/query/MatchTypeEnum.java

@@ -0,0 +1,44 @@
+package com.storlead.framework.common.system.query;
+
+import com.storlead.framework.common.util.ConvertUtils;
+
+/**
+ * 查询链接规则
+ *
+ * @Author Sunjianlei
+ */
+public enum MatchTypeEnum {
+
+    WHERE(" WHERE"),
+    AND(" AND"),
+    OR(" OR");
+
+    private String value;
+
+    MatchTypeEnum(String value) {
+        this.value = value;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public static MatchTypeEnum getByValue(Object value) {
+        if (ConvertUtils.isEmpty(value)) {
+            return null;
+        }
+        return getByValue(value.toString());
+    }
+
+    public static MatchTypeEnum getByValue(String value) {
+        if (ConvertUtils.isEmpty(value)) {
+            return null;
+        }
+        for (MatchTypeEnum val : values()) {
+            if (val.getValue().toLowerCase().equals(value.toLowerCase())) {
+                return val;
+            }
+        }
+        return null;
+    }
+}

+ 55 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/system/query/QueryCondition.java

@@ -0,0 +1,55 @@
+package com.storlead.framework.common.system.query;
+
+import java.io.Serializable;
+
+public class QueryCondition implements Serializable {
+
+	private static final long serialVersionUID = 4740166316629191651L;
+
+	private String field;
+	private String type;
+	private String rule;
+	private String val;
+
+	public String getField() {
+		return field;
+	}
+
+	public void setField(String field) {
+		this.field = field;
+	}
+
+	public String getType() {
+		return type;
+	}
+
+	public void setType(String type) {
+		this.type = type;
+	}
+
+	public String getRule() {
+		return rule;
+	}
+
+	public void setRule(String rule) {
+		this.rule = rule;
+	}
+
+	public String getVal() {
+		return val;
+	}
+
+	public void setVal(String val) {
+		this.val = val;
+	}
+
+	@Override
+	public String toString(){
+		StringBuffer sb =new StringBuffer();
+		if(field == null || "".equals(field)){
+			return "";
+		}
+		sb.append(this.field).append(" ").append(this.rule).append(" ").append(this.type).append(" ").append(this.val);
+		return sb.toString();
+	}
+}

+ 71 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/system/query/QueryRuleEnum.java

@@ -0,0 +1,71 @@
+package com.storlead.framework.common.system.query;
+
+import com.storlead.framework.common.util.ConvertUtils;
+
+/**
+ * Query 规则 常量
+ * @Author Scott
+ * @Date 2019年02月14日
+ */
+public enum QueryRuleEnum {
+
+    GT(">","gt","大于"),
+    GE(">=","ge","大于等于"),
+    LT("<","lt","小于"),
+    LE("<=","le","小于等于"),
+    EQ("=","eq","等于"),
+    NE("!=","ne","不等于"),
+    IN("IN","in","包含"),
+    LIKE("LIKE","like","全模糊"),
+    LEFT_LIKE("LEFT_LIKE","left_like","左模糊"),
+    RIGHT_LIKE("RIGHT_LIKE","right_like","右模糊"),
+    SQL_RULES("USE_SQL_RULES","ext","自定义SQL片段");
+
+    private String value;
+
+    private String condition;
+
+    private String msg;
+
+    QueryRuleEnum(String value, String condition, String msg){
+        this.value = value;
+        this.condition = condition;
+        this.msg = msg;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+    public void setMsg(String msg) {
+        this.msg = msg;
+    }
+
+    public String getCondition() {
+		return condition;
+	}
+
+	public void setCondition(String condition) {
+		this.condition = condition;
+	}
+
+	public static QueryRuleEnum getByValue(String value){
+    	if(ConvertUtils.isEmpty(value)) {
+    		return null;
+    	}
+        for(QueryRuleEnum val :values()){
+            if (val.getValue().equals(value) || val.getCondition().equals(value)){
+                return val;
+            }
+        }
+        return  null;
+    }
+}

+ 619 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/ConvertUtils.java

@@ -0,0 +1,619 @@
+package com.storlead.framework.common.util;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Field;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.sql.Date;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Slf4j
+public class ConvertUtils {
+	public static boolean isEmpty(Object object) {
+		if (object == null) {
+			return (true);
+		}
+		if ("".equals(object)) {
+			return (true);
+		}
+		if ("null".equals(object)) {
+			return (true);
+		}
+		return (false);
+	}
+
+	public static boolean isNotEmpty(Object object) {
+		if (object != null && !object.equals("") && !object.equals("null")) {
+			return (true);
+		}
+		return (false);
+	}
+
+	public static String decode(String strIn, String sourceCode, String targetCode) {
+		String temp = code2code(strIn, sourceCode, targetCode);
+		return temp;
+	}
+
+	public static String StrToUTF(String strIn, String sourceCode, String targetCode) {
+		strIn = "";
+		try {
+			strIn = new String(strIn.getBytes("ISO-8859-1"), "GBK");
+		} catch (UnsupportedEncodingException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+		return strIn;
+
+	}
+
+	private static String code2code(String strIn, String sourceCode, String targetCode) {
+		String strOut = null;
+		if (strIn == null || (strIn.trim()).equals("")) {
+			return strIn;
+		}
+		try {
+			byte[] b = strIn.getBytes(sourceCode);
+			for (int i = 0; i < b.length; i++) {
+				System.out.print(b[i] + "  ");
+			}
+			strOut = new String(b, targetCode);
+		} catch (Exception e) {
+			e.printStackTrace();
+			return null;
+		}
+		return strOut;
+	}
+
+	public static int getInt(String s, int defval) {
+		if (s == null || s == "") {
+			return (defval);
+		}
+		try {
+			return (Integer.parseInt(s));
+		} catch (NumberFormatException e) {
+			return (defval);
+		}
+	}
+
+	public static int getInt(String s) {
+		if (s == null || s == "") {
+			return 0;
+		}
+		try {
+			return (Integer.parseInt(s));
+		} catch (NumberFormatException e) {
+			return 0;
+		}
+	}
+
+	public static int getInt(String s, Integer df) {
+		if (s == null || s == "") {
+			return df;
+		}
+		try {
+			return (Integer.parseInt(s));
+		} catch (NumberFormatException e) {
+			return 0;
+		}
+	}
+
+	public static Integer[] getInts(String[] s) {
+		Integer[] integer = new Integer[s.length];
+		if (s == null) {
+			return null;
+		}
+		for (int i = 0; i < s.length; i++) {
+			integer[i] = Integer.parseInt(s[i]);
+		}
+		return integer;
+
+	}
+
+	public static double getDouble(String s, double defval) {
+		if (s == null || s == "") {
+			return (defval);
+		}
+		try {
+			return (Double.parseDouble(s));
+		} catch (NumberFormatException e) {
+			return (defval);
+		}
+	}
+
+	public static double getDou(Double s, double defval) {
+		if (s == null) {
+			return (defval);
+		}
+		return s;
+	}
+
+	/*public static Short getShort(String s) {
+		if (StringUtil.isNotEmpty(s)) {
+			return (Short.parseShort(s));
+		} else {
+			return null;
+		}
+	}*/
+
+	public static int getInt(Object object, int defval) {
+		if (isEmpty(object)) {
+			return (defval);
+		}
+		try {
+			return (Integer.parseInt(object.toString()));
+		} catch (NumberFormatException e) {
+			return (defval);
+		}
+	}
+
+	public static Integer getInt(Object object) {
+		if (isEmpty(object)) {
+			return null;
+		}
+		try {
+			return (Integer.parseInt(object.toString()));
+		} catch (NumberFormatException e) {
+			return null;
+		}
+	}
+
+	public static int getInt(BigDecimal s, int defval) {
+		if (s == null) {
+			return (defval);
+		}
+		return s.intValue();
+	}
+
+	public static Integer[] getIntegerArry(String[] object) {
+		int len = object.length;
+		Integer[] result = new Integer[len];
+		try {
+			for (int i = 0; i < len; i++) {
+				result[i] = new Integer(object[i].trim());
+			}
+			return result;
+		} catch (NumberFormatException e) {
+			return null;
+		}
+	}
+
+	public static String getString(String s) {
+		return (getString(s, ""));
+	}
+
+	/**
+	 * 转义成Unicode编码
+	 * @param s
+	 * @return
+	 */
+	/*public static String escapeJava(Object s) {
+		return StringEscapeUtils.escapeJava(getString(s));
+	}*/
+
+	public static String getString(Object object) {
+		if (isEmpty(object)) {
+			return "";
+		}
+		return (object.toString().trim());
+	}
+
+	public static String getString(int i) {
+		return (String.valueOf(i));
+	}
+
+	public static String getString(float i) {
+		return (String.valueOf(i));
+	}
+
+	public static String getString(String s, String defval) {
+		if (isEmpty(s)) {
+			return (defval);
+		}
+		return (s.trim());
+	}
+
+	public static String getString(Object s, String defval) {
+		if (isEmpty(s)) {
+			return (defval);
+		}
+		return (s.toString().trim());
+	}
+
+	public static long stringToLong(String str) {
+		Long test = new Long(0);
+		try {
+			test = Long.valueOf(str);
+		} catch (Exception e) {
+		}
+		return test.longValue();
+	}
+
+	/**
+	 * 获取本机IP
+	 */
+	public static String getIp() {
+		String ip = null;
+		try {
+			InetAddress address = InetAddress.getLocalHost();
+			ip = address.getHostAddress();
+
+		} catch (UnknownHostException e) {
+			e.printStackTrace();
+		}
+		return ip;
+	}
+
+	/**
+	 * 判断一个类是否为基本数据类型。
+	 *
+	 * @param clazz
+	 *            要判断的类。
+	 * @return true 表示为基本数据类型。
+	 */
+	private static boolean isBaseDataType(Class clazz) throws Exception {
+		return (clazz.equals(String.class) || clazz.equals(Integer.class) || clazz.equals(Byte.class) || clazz.equals(Long.class) || clazz.equals(Double.class) || clazz.equals(Float.class) || clazz.equals(Character.class) || clazz.equals(Short.class) || clazz.equals(BigDecimal.class) || clazz.equals(BigInteger.class) || clazz.equals(Boolean.class) || clazz.equals(Date.class) || clazz.isPrimitive());
+	}
+
+	/**
+	 * @param request
+	 *            IP
+	 * @return IP Address
+	 */
+//	public static String getIpAddrByRequest(HttpServletRequest request) {
+//		String ip = request.getHeader("x-forwarded-for");
+//		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+//			ip = request.getHeader("Proxy-Client-IP");
+//		}
+//		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+//			ip = request.getHeader("WL-Proxy-Client-IP");
+//		}
+//		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
+//			ip = request.getRemoteAddr();
+//		}
+//		return ip;
+//	}
+
+	/**
+	 * @return 本机IP
+	 * @throws SocketException
+	 */
+	public static String getRealIp() throws SocketException {
+		String localip = null;// 本地IP,如果没有配置外网IP则返回它
+		String netip = null;// 外网IP
+
+		Enumeration<NetworkInterface> netInterfaces = NetworkInterface.getNetworkInterfaces();
+		InetAddress ip = null;
+		boolean finded = false;// 是否找到外网IP
+		while (netInterfaces.hasMoreElements() && !finded) {
+			NetworkInterface ni = netInterfaces.nextElement();
+			Enumeration<InetAddress> address = ni.getInetAddresses();
+			while (address.hasMoreElements()) {
+				ip = address.nextElement();
+				if (!ip.isSiteLocalAddress() && !ip.isLoopbackAddress() && ip.getHostAddress().indexOf(":") == -1) {// 外网IP
+					netip = ip.getHostAddress();
+					finded = true;
+					break;
+				} else if (ip.isSiteLocalAddress() && !ip.isLoopbackAddress() && ip.getHostAddress().indexOf(":") == -1) {// 内网IP
+					localip = ip.getHostAddress();
+				}
+			}
+		}
+
+		if (netip != null && !"".equals(netip)) {
+			return netip;
+		} else {
+			return localip;
+		}
+	}
+
+	/**
+	 * java去除字符串中的空格、回车、换行符、制表符
+	 *
+	 * @param str
+	 * @return
+	 */
+	public static String replaceBlank(String str) {
+		String dest = "";
+		if (str != null) {
+			Pattern p = Pattern.compile("\\s*|\t|\r|\n");
+			Matcher m = p.matcher(str);
+			dest = m.replaceAll("");
+		}
+		return dest;
+
+	}
+
+	/**
+	 * 判断元素是否在数组内
+	 *
+	 * @param substring
+	 * @param source
+	 * @return
+	 */
+	public static boolean isIn(String substring, String[] source) {
+		if (source == null || source.length == 0) {
+			return false;
+		}
+		for (int i = 0; i < source.length; i++) {
+			String aSource = source[i];
+			if (aSource.equals(substring)) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * 获取Map对象
+	 */
+	public static Map<Object, Object> getHashMap() {
+		return new HashMap<Object, Object>();
+	}
+
+	/**
+	 * SET转换MAP
+	 *
+	 * @param str
+	 * @return
+	 */
+	public static Map<Object, Object> SetToMap(Set<Object> setobj) {
+		Map<Object, Object> map = getHashMap();
+		for (Iterator iterator = setobj.iterator(); iterator.hasNext();) {
+			Map.Entry<Object, Object> entry = (Map.Entry<Object, Object>) iterator.next();
+			map.put(entry.getKey().toString(), entry.getValue() == null ? "" : entry.getValue().toString().trim());
+		}
+		return map;
+
+	}
+
+	public static boolean isInnerIP(String ipAddress) {
+		boolean isInnerIp = false;
+		long ipNum = getIpNum(ipAddress);
+		/**
+		 * 私有IP:A类 10.0.0.0-10.255.255.255 B类 172.16.0.0-172.31.255.255 C类 192.168.0.0-192.168.255.255 当然,还有127这个网段是环回地址
+		 **/
+		long aBegin = getIpNum("10.0.0.0");
+		long aEnd = getIpNum("10.255.255.255");
+		long bBegin = getIpNum("172.16.0.0");
+		long bEnd = getIpNum("172.31.255.255");
+		long cBegin = getIpNum("192.168.0.0");
+		long cEnd = getIpNum("192.168.255.255");
+		isInnerIp = isInner(ipNum, aBegin, aEnd) || isInner(ipNum, bBegin, bEnd) || isInner(ipNum, cBegin, cEnd) || ipAddress.equals("127.0.0.1");
+		return isInnerIp;
+	}
+
+	private static long getIpNum(String ipAddress) {
+		String[] ip = ipAddress.split("\\.");
+		long a = Integer.parseInt(ip[0]);
+		long b = Integer.parseInt(ip[1]);
+		long c = Integer.parseInt(ip[2]);
+		long d = Integer.parseInt(ip[3]);
+
+		long ipNum = a * 256 * 256 * 256 + b * 256 * 256 + c * 256 + d;
+		return ipNum;
+	}
+
+	private static boolean isInner(long userIp, long begin, long end) {
+		return (userIp >= begin) && (userIp <= end);
+	}
+
+	/**
+	 * 将下划线大写方式命名的字符串转换为驼峰式。
+	 * 如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。</br>
+	 * 例如:hello_world->helloWorld
+	 *
+	 * @param name
+	 *            转换前的下划线大写方式命名的字符串
+	 * @return 转换后的驼峰式命名的字符串
+	 */
+	public static String camelName(String name) {
+		StringBuilder result = new StringBuilder();
+		// 快速检查
+		if (name == null || name.isEmpty()) {
+			// 没必要转换
+			return "";
+		} else if (!name.contains("_")) {
+			// 不含下划线,仅将首字母小写
+			//update-begin--Author:zhoujf  Date:20180503 for:TASK #2500 【代码生成器】代码生成器开发一通用模板生成功能
+			//update-begin--Author:zhoujf  Date:20180503 for:TASK #2500 【代码生成器】代码生成器开发一通用模板生成功能
+			return name.substring(0, 1).toLowerCase() + name.substring(1).toLowerCase();
+			//update-end--Author:zhoujf  Date:20180503 for:TASK #2500 【代码生成器】代码生成器开发一通用模板生成功能
+		}
+		// 用下划线将原始字符串分割
+		String camels[] = name.split("_");
+		for (String camel : camels) {
+			// 跳过原始字符串中开头、结尾的下换线或双重下划线
+			if (camel.isEmpty()) {
+				continue;
+			}
+			// 处理真正的驼峰片段
+			if (result.length() == 0) {
+				// 第一个驼峰片段,全部字母都小写
+				result.append(camel.toLowerCase());
+			} else {
+				// 其他的驼峰片段,首字母大写
+				result.append(camel.substring(0, 1).toUpperCase());
+				result.append(camel.substring(1).toLowerCase());
+			}
+		}
+		return result.toString();
+	}
+
+	/**
+	 * 将下划线大写方式命名的字符串转换为驼峰式。
+	 * 如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。</br>
+	 * 例如:hello_world,test_id->helloWorld,testId
+	 *
+	 * @param name
+	 *            转换前的下划线大写方式命名的字符串
+	 * @return 转换后的驼峰式命名的字符串
+	 */
+	public static String camelNames(String names) {
+		if(names==null||names.equals("")){
+			return null;
+		}
+		StringBuffer sf = new StringBuffer();
+		String[] fs = names.split(",");
+		for (String field : fs) {
+			field = camelName(field);
+			sf.append(field + ",");
+		}
+		String result = sf.toString();
+		return result.substring(0, result.length() - 1);
+	}
+
+	//update-begin--Author:zhoujf  Date:20180503 for:TASK #2500 【代码生成器】代码生成器开发一通用模板生成功能
+	/**
+	 * 将下划线大写方式命名的字符串转换为驼峰式。(首字母写)
+	 * 如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。</br>
+	 * 例如:hello_world->HelloWorld
+	 *
+	 * @param name
+	 *            转换前的下划线大写方式命名的字符串
+	 * @return 转换后的驼峰式命名的字符串
+	 */
+	public static String camelNameCapFirst(String name) {
+		StringBuilder result = new StringBuilder();
+		// 快速检查
+		if (name == null || name.isEmpty()) {
+			// 没必要转换
+			return "";
+		} else if (!name.contains("_")) {
+			// 不含下划线,仅将首字母小写
+			return name.substring(0, 1).toUpperCase() + name.substring(1).toLowerCase();
+		}
+		// 用下划线将原始字符串分割
+		String camels[] = name.split("_");
+		for (String camel : camels) {
+			// 跳过原始字符串中开头、结尾的下换线或双重下划线
+			if (camel.isEmpty()) {
+				continue;
+			}
+			// 其他的驼峰片段,首字母大写
+			result.append(camel.substring(0, 1).toUpperCase());
+			result.append(camel.substring(1).toLowerCase());
+		}
+		return result.toString();
+	}
+	//update-end--Author:zhoujf  Date:20180503 for:TASK #2500 【代码生成器】代码生成器开发一通用模板生成功能
+
+	/**
+	 * 将驼峰命名转化成下划线
+	 * @param para
+	 * @return
+	 */
+	public static String camelToUnderline(String para){
+        if(para.length()<3){
+        	return para.toLowerCase();
+        }
+        StringBuilder sb=new StringBuilder(para);
+        int temp=0;//定位
+        //从第三个字符开始 避免命名不规范
+        for(int i=2;i<para.length();i++){
+            if(Character.isUpperCase(para.charAt(i))){
+                sb.insert(i+temp, "_");
+                temp+=1;
+            }
+        }
+        return sb.toString().toLowerCase();
+	}
+
+	/**
+	 * 随机数
+	 * @param place 定义随机数的位数
+	 */
+	public static String randomGen(int place) {
+		String base = "qwertyuioplkjhgfdsazxcvbnmQAZWSXEDCRFVTGBYHNUJMIKLOP0123456789";
+		StringBuffer sb = new StringBuffer();
+		Random rd = new Random();
+		for(int i=0;i<place;i++) {
+			sb.append(base.charAt(rd.nextInt(base.length())));
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * 获取类的所有属性,包括父类
+	 *
+	 * @param object
+	 * @return
+	 */
+	public static Field[] getAllFields(Object object) {
+		Class<?> clazz = object.getClass();
+		List<Field> fieldList = new ArrayList<>();
+		while (clazz != null) {
+			fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
+			clazz = clazz.getSuperclass();
+		}
+		Field[] fields = new Field[fieldList.size()];
+		fieldList.toArray(fields);
+		return fields;
+	}
+
+	/**
+	  * 将map的key全部转成小写
+	 * @param list
+	 * @return
+	 */
+	public static List<Map<String, Object>> toLowerCasePageList(List<Map<String, Object>> list){
+		List<Map<String, Object>> select = new ArrayList<>();
+		for (Map<String, Object> row : list) {
+			 Map<String, Object> resultMap = new HashMap<>();
+			 Set<String> keySet = row.keySet();
+			 for (String key : keySet) {
+				 String newKey = key.toLowerCase();
+				 resultMap.put(newKey, row.get(key));
+			 }
+			 select.add(resultMap);
+		}
+		return select;
+	}
+
+	/**
+	 * 将entityList转换成modelList
+	 * @param fromList
+	 * @param tClass
+	 * @param <F>
+	 * @param <T>
+	 * @return
+	 */
+	public static<F,T> List<T> entityListToModelList(List<F> fromList, Class<T> tClass){
+		if(fromList.isEmpty() || fromList == null){
+			return null;
+		}
+		List<T> tList = new ArrayList<>();
+		for(F f : fromList){
+			T t = entityToModel(f, tClass);
+			tList.add(t);
+		}
+		return tList;
+	}
+
+	public static<F,T> T entityToModel(F entity, Class<T> modelClass) {
+		log.debug("entityToModel : Entity属性的值赋值到Model");
+		Object model = null;
+		if (entity == null || modelClass ==null) {
+			return null;
+		}
+
+		try {
+			model = modelClass.newInstance();
+		} catch (InstantiationException e) {
+			log.error("entityToModel : 实例化异常", e);
+		} catch (IllegalAccessException e) {
+			log.error("entityToModel : 安全权限异常", e);
+		}
+		BeanUtils.copyProperties(entity, model);
+		return (T)model;
+	}
+
+}

+ 905 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/DateUtil.java

@@ -0,0 +1,905 @@
+package com.storlead.framework.common.util;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.util.Assert;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+/**
+ * 时间/日期处理类
+ *
+ * @author blank
+ * @since 2018/5/17 上午10:16
+ */
+public class DateUtil {
+
+
+    /** 时间日期格式化到年月日时分秒. */
+    public static final String dateFormatYMDHMS = "yyyy-MM-dd HH:mm:ss";
+
+    /** 时间日期格式化到年月日时分秒无间隔 */
+    public static final String dateFormatYMDHMSSimple = "yyyyMMddHHmmss";
+
+    /** 时间日期格式化到年月日无间隔 */
+    public static final String dateFormatYMDSimple = "yyyyMMdd";
+
+    /** 时间日期格式化到年 */
+    public static final String dateFormatYear = "yyyy";
+
+    /** 时间日期格式化到月 */
+    public static final String dateFormatMonth = "MM";
+
+    /** 时间日期格式化到日 */
+    public static final String dateFormatDay = "dd";
+
+    /** 时间日期格式化到年月日. */
+    public static final String dateFormatYMD = "yyyy-MM-dd";
+
+    /** 时间日期格式化到年月日带中文. */
+    public static final String dateFormatYMDCN = "yyyy年MM月dd日";
+
+    /** 时间日期格式化到年月. */
+    public static final String dateFormatYM = "yyyy-MM";
+
+    /** 时间日期格式化到年月日时分. */
+    public static final String dateFormatYMDHM = "yyyy-MM-dd HH:mm";
+
+    /** 时间日期格式化到年月日时分. */
+    public static final String dateFormatYMDHMCN = "yyyy年MM月dd日 HH:mm";
+
+    /** 时间日期格式化到月日. */
+    public static final String dateFormatMD = "MM/dd";
+
+    /** 时分秒. */
+    public static final String dateFormatHMS = "HH:mm:ss";
+
+    /** 时分. */
+    public static final String dateFormatHM = "HH:mm";
+
+    /** 上午. */
+    public static final String AM = "AM";
+
+    /** 下午. */
+    public static final String PM = "PM";
+
+    /**
+     * 描述:String类型的日期时间转化为Date类型.
+     *
+     * @param strDate
+     *            String形式的日期时间
+     * @param format
+     *            格式化字符串,如:"yyyy-MM-dd HH:mm:ss"
+     * @return Date Date类型日期时间
+     */
+    public static Date getDateByFormat(String strDate, String format) {
+        SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat(format);
+        Date date = null;
+        try {
+            date = mSimpleDateFormat.parse(strDate);
+        } catch (ParseException e) {
+            e.printStackTrace();
+        }
+        return date;
+    }
+
+    /**
+     * 描述:获取偏移之后的Date.
+     *
+     * @param date
+     *            日期时间
+     * @param calendarField
+     *            Calendar属性,对应offset的值,
+     *            如(Calendar.DATE,表示+offset天,Calendar.HOUR_OF_DAY,表示+offset小时)
+     * @param offset
+     *            偏移(值大于0,表示+,值小于0,表示-)
+     * @return Date 偏移之后的日期时间
+     */
+    public static Date getDateByOffset(Date date, int calendarField, int offset) {
+        Calendar c = new GregorianCalendar();
+        try {
+            c.setTime(date);
+            c.add(calendarField, offset);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return c.getTime();
+    }
+
+    /**
+     * 描述:获取指定日期时间的字符串(可偏移).
+     *
+     * @param strDate
+     *            String形式的日期时间
+     * @param format
+     *            格式化字符串,如:"yyyy-MM-dd HH:mm:ss"
+     * @param calendarField
+     *            Calendar属性,对应offset的值,
+     *            如(Calendar.DATE,表示+offset天,Calendar.HOUR_OF_DAY,表示+offset小时)
+     * @param offset
+     *            偏移(值大于0,表示+,值小于0,表示-)
+     * @return String String类型的日期时间
+     */
+    public static String getStringByOffset(String strDate, String format, int calendarField, int offset) {
+        String mDateTime = null;
+        try {
+            Calendar c = new GregorianCalendar();
+            SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat(format);
+            c.setTime(mSimpleDateFormat.parse(strDate));
+            c.add(calendarField, offset);
+            mDateTime = mSimpleDateFormat.format(c.getTime());
+        } catch (ParseException e) {
+            e.printStackTrace();
+        }
+        return mDateTime;
+    }
+
+    /**
+     * 描述:Date类型转化为String类型(可偏移).
+     *
+     * @param date
+     *            the date
+     * @param format
+     *            the format
+     * @param calendarField
+     *            the calendar field
+     * @param offset
+     *            the offset
+     * @return String String类型日期时间
+     */
+    public static String getStringByOffset(Date date, String format, int calendarField, int offset) {
+        String strDate = null;
+        try {
+            Calendar c = new GregorianCalendar();
+            SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat(format);
+            c.setTime(date);
+            c.add(calendarField, offset);
+            strDate = mSimpleDateFormat.format(c.getTime());
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return strDate;
+    }
+
+    /**
+     * 描述:Date类型转化为String类型.
+     *
+     * @param date
+     *            the date
+     * @param format
+     *            the format
+     * @return String String类型日期时间
+     */
+    public static String getStringByFormat(Date date, String format) {
+        SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat(format);
+        String strDate = null;
+        try {
+            strDate = mSimpleDateFormat.format(date);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return strDate;
+    }
+
+    /**
+     * 描述:获取指定日期时间的字符串,用于导出想要的格式.
+     *
+     * @param strDate
+     *            String形式的日期时间,必须为yyyy-MM-dd HH:mm:ss格式
+     * @param format
+     *            输出格式化字符串,如:"yyyy-MM-dd HH:mm:ss"
+     * @return String 转换后的String类型的日期时间
+     */
+    public static String getStringByFormat(String strDate, String format) {
+        String mDateTime = null;
+        try {
+            Calendar c = new GregorianCalendar();
+            SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat(dateFormatYMDHMS);
+            c.setTime(mSimpleDateFormat.parse(strDate));
+            SimpleDateFormat mSimpleDateFormat2 = new SimpleDateFormat(format);
+            mDateTime = mSimpleDateFormat2.format(c.getTime());
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return mDateTime;
+    }
+
+    /**
+     * 描述:获取milliseconds表示的日期时间的字符串.
+     *
+     * @param milliseconds
+     *            the milliseconds
+     * @param format
+     *            格式化字符串,如:"yyyy-MM-dd HH:mm:ss"
+     * @return String 日期时间字符串
+     */
+    public static String getStringByFormat(long milliseconds, String format) {
+        String thisDateTime = null;
+        try {
+            SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat(format);
+            thisDateTime = mSimpleDateFormat.format(milliseconds);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return thisDateTime;
+    }
+
+    /**
+     * 描述:获取表示当前日期时间的字符串.
+     *
+     * @param format
+     *            格式化字符串,如:"yyyy-MM-dd HH:mm:ss"
+     * @return String String类型的当前日期时间
+     */
+    public static String getCurrentDate(String format) {
+        String curDateTime = null;
+        try {
+            SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat(format);
+            Calendar c = new GregorianCalendar();
+            curDateTime = mSimpleDateFormat.format(c.getTime());
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return curDateTime;
+
+    }
+
+    /**
+     * 获取当前日期时间的字符串 格式为 yyyyMMdd
+     *
+     * @return java.lang.String
+     * @author blank
+     * @since 2018-12-19 下午 12:01
+     */
+    public static String getCurrentDateString() {
+        return DateUtil.getCurrentDate(dateFormatYMDSimple);
+
+    }
+
+
+    /**
+     * 获取当前日期时间的字符串 格式为 yyyyMMddHHmmss
+     *
+     * @return java.lang.String
+     * @author blank
+     * @since 2018-12-19 下午 12:01
+     */
+    public static String getCurrentDateYearMonthDayMinSecString() {
+        return DateUtil.getCurrentDate(dateFormatYMDHMSSimple);
+
+    }
+
+    /**
+     * 获取当前日期时间的字符串 格式为 yyyy年-MM月-dd日
+     *
+     * @return 格式为 yyyy年-MM月-dd日
+     */
+    public static String getCurrentDateCNString() {
+        return DateUtil.getCurrentDate(dateFormatYMDCN);
+
+    }
+
+    /**
+     * 获取当前日期 年 的字符串 格式为 yyyy
+     *
+     * @return java.lang.String
+     * @author blank
+     * @since 2019-1-10 下午 4:30
+     */
+    public static String getCurrentDateYear() {
+        return DateUtil.getCurrentDate(dateFormatYear);
+    }
+
+    /**
+     * 获取当前日期 月 的字符串 格式为 MM
+     *
+     * @return java.lang.String
+     * @author blank
+     * @since 2019-1-10 下午 4:31
+     */
+    public static String getCurrentDateMonth() {
+        return DateUtil.getCurrentDate(dateFormatMonth);
+    }
+
+    /**
+     * 获取当前日期 天 的字符串 格式为 dd
+     *
+     * @return java.lang.String
+     * @author blank
+     * @since 2019-1-10 下午 4:32
+     */
+    public static String getCurrentDateDay() {
+        return DateUtil.getCurrentDate(dateFormatDay);
+    }
+
+    /**
+     * 返回当前系统时间毫秒值
+     *
+     * @return java.lang.String
+     * @author blank
+     * @since 2018-12-19 下午 12:03
+     */
+    public static String getCurrentTimeMills(){
+        return System.currentTimeMillis() + "";
+    }
+
+
+    /**
+     * 描述:获取表示当前日期时间的字符串(可偏移).
+     *
+     * @param format
+     *            格式化字符串,如:"yyyy-MM-dd HH:mm:ss"
+     * @param calendarField
+     *            Calendar属性,对应offset的值,
+     *            如(Calendar.DATE,表示+offset天,Calendar.HOUR_OF_DAY,表示+offset小时)
+     * @param offset
+     *            偏移(值大于0,表示+,值小于0,表示-)
+     * @return String String类型的日期时间
+     */
+    public static String getCurrentDateByOffset(String format, int calendarField, int offset) {
+        String mDateTime = null;
+        try {
+            SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat(format);
+            Calendar c = new GregorianCalendar();
+            c.add(calendarField, offset);
+            mDateTime = mSimpleDateFormat.format(c.getTime());
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return mDateTime;
+
+    }
+
+    /**
+     * 描述:计算两个日期所差的天数.
+     *
+     * @param milliseconds1
+     *            the milliseconds1
+     * @param milliseconds2
+     *            the milliseconds2
+     * @return int 所差的天数
+     */
+    public static int getOffectDay(long milliseconds1, long milliseconds2) {
+        Calendar calendar1 = Calendar.getInstance();
+        calendar1.setTimeInMillis(milliseconds1);
+        Calendar calendar2 = Calendar.getInstance();
+        calendar2.setTimeInMillis(milliseconds2);
+        // 先判断是否同年
+        int y1 = calendar1.get(Calendar.YEAR);
+        int y2 = calendar2.get(Calendar.YEAR);
+        int d1 = calendar1.get(Calendar.DAY_OF_YEAR);
+        int d2 = calendar2.get(Calendar.DAY_OF_YEAR);
+        int maxDays = 0;
+        int day = 0;
+        if (y1 - y2 > 0) {
+            maxDays = calendar2.getActualMaximum(Calendar.DAY_OF_YEAR);
+            day = d1 - d2 + maxDays;
+        } else if (y1 - y2 < 0) {
+            maxDays = calendar1.getActualMaximum(Calendar.DAY_OF_YEAR);
+            day = d1 - d2 - maxDays;
+        } else {
+            day = d1 - d2;
+        }
+        return day;
+    }
+
+    /**
+     * 描述:计算两个日期所差的小时数.
+     *
+     * @param date1
+     *            第一个时间的毫秒表示
+     * @param date2
+     *            第二个时间的毫秒表示
+     * @return int 所差的小时数
+     */
+    public static int getOffsetHour(long date1, long date2) {
+        Calendar calendar1 = Calendar.getInstance();
+        calendar1.setTimeInMillis(date1);
+        Calendar calendar2 = Calendar.getInstance();
+        calendar2.setTimeInMillis(date2);
+        int h1 = calendar1.get(Calendar.HOUR_OF_DAY);
+        int h2 = calendar2.get(Calendar.HOUR_OF_DAY);
+        int h = 0;
+        int day = getOffectDay(date1, date2);
+        h = h1 - h2 + day * 24;
+        return h;
+    }
+
+    /**
+     * 描述:计算两个日期所差的分钟数.
+     *
+     * @param date1
+     *            第一个时间的毫秒表示
+     * @param date2
+     *            第二个时间的毫秒表示
+     * @return int 所差的分钟数
+     */
+    public static int getOffsetMinutes(long date1, long date2) {
+        Calendar calendar1 = Calendar.getInstance();
+        calendar1.setTimeInMillis(date1);
+        Calendar calendar2 = Calendar.getInstance();
+        calendar2.setTimeInMillis(date2);
+        int m1 = calendar1.get(Calendar.MINUTE);
+        int m2 = calendar2.get(Calendar.MINUTE);
+        int h = getOffsetHour(date1, date2);
+        int m = 0;
+        m = m1 - m2 + h * 60;
+        return m;
+    }
+
+    /**
+     * 描述:获取本周一.
+     *
+     * @param format
+     *            the format
+     * @return String String类型日期时间
+     */
+    public static String getFirstDayOfWeek(String format) {
+        return getDayOfWeek(format, Calendar.MONDAY);
+    }
+
+    /**
+     * 描述:获取本周日.
+     *
+     * @param format
+     *            the format
+     * @return String String类型日期时间
+     */
+    public static String getLastDayOfWeek(String format) {
+        return getDayOfWeek(format, Calendar.SUNDAY);
+    }
+
+    /**
+     * 描述:获取本周的某一天.
+     *
+     * @param format
+     *            the format
+     * @param calendarField
+     *            the calendar field
+     * @return String String类型日期时间
+     */
+    private static String getDayOfWeek(String format, int calendarField) {
+        String strDate = null;
+        try {
+            Calendar c = new GregorianCalendar();
+            SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat(format);
+            int week = c.get(Calendar.DAY_OF_WEEK);
+            if (week == calendarField) {
+                strDate = mSimpleDateFormat.format(c.getTime());
+            } else {
+                int offectDay = calendarField - week;
+                if (calendarField == Calendar.SUNDAY) {
+                    offectDay = 7 - Math.abs(offectDay);
+                }
+                c.add(Calendar.DATE, offectDay);
+                strDate = mSimpleDateFormat.format(c.getTime());
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return strDate;
+    }
+
+    /**
+     * 描述:获取本月第一天.
+     *
+     * @param format
+     *            the format
+     * @return String String类型日期时间
+     */
+    public static String getFirstDayOfMonth(String format) {
+        String strDate = null;
+        try {
+            Calendar c = new GregorianCalendar();
+            SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat(format);
+            // 当前月的第一天
+            c.set(GregorianCalendar.DAY_OF_MONTH, 1);
+            strDate = mSimpleDateFormat.format(c.getTime());
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return strDate;
+
+    }
+
+    /**
+     * 描述:获取本月最后一天.
+     *
+     * @param format
+     *            the format
+     * @return String String类型日期时间
+     */
+    public static String getLastDayOfMonth(String format) {
+        String strDate = null;
+        try {
+            Calendar c = new GregorianCalendar();
+            SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat(format);
+            // 当前月的最后一天
+            c.set(Calendar.DATE, 1);
+            c.roll(Calendar.DATE, -1);
+            strDate = mSimpleDateFormat.format(c.getTime());
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return strDate;
+    }
+
+    /**
+     * 描述:获取表示当前日期的0点时间毫秒数.
+     *
+     * @return the first time of day
+     */
+    public static long getFirstTimeOfDay() {
+        Date date = null;
+        try {
+            String currentDate = getCurrentDate(dateFormatYMD);
+            date = getDateByFormat(currentDate + " 00:00:00", dateFormatYMDHMS);
+            return date.getTime();
+        } catch (Exception ignored) {
+        }
+        return -1;
+    }
+
+    /**
+     * 描述:获取表示当前日期24点时间毫秒数.
+     *
+     * @return the last time of day
+     */
+    public static long getLastTimeOfDay() {
+        Date date = null;
+        try {
+            String currentDate = getCurrentDate(dateFormatYMD);
+            date = getDateByFormat(currentDate + " 24:00:00", dateFormatYMDHMS);
+            return date.getTime();
+        } catch (Exception ignored) {
+        }
+        return -1;
+    }
+
+    /**
+     * 描述:判断是否是闰年()
+     * <p>
+     * (year能被4整除 并且 不能被100整除) 或者 year能被400整除,则该年为闰年.
+     *
+     * @param year
+     *            年代(如2012)
+     * @return boolean 是否为闰年
+     */
+    public static boolean isLeapYear(int year) {
+        return ((year % 4 == 0 && year % 400 != 0) || year % 400 == 0);
+    }
+
+    /**
+     * 描述:根据时间返回格式化后的时间的描述. 小于1小时显示多少分钟前 大于1小时显示今天+实际日期,大于今天全部显示实际时间
+     *
+     * @param strDate
+     *            the str date
+     * @param outFormat
+     *            the out format
+     * @return the string
+     */
+    public static String formatDateStr2Desc(String strDate, String outFormat) {
+
+        DateFormat df = new SimpleDateFormat(dateFormatYMDHMS);
+        Calendar c1 = Calendar.getInstance();
+        Calendar c2 = Calendar.getInstance();
+        try {
+            c2.setTime(df.parse(strDate));
+            c1.setTime(new Date());
+            int d = getOffectDay(c1.getTimeInMillis(), c2.getTimeInMillis());
+            if (d == 0) {
+                int h = getOffsetHour(c1.getTimeInMillis(), c2.getTimeInMillis());
+                if (h > 0) {
+                    return "今天" + getStringByFormat(strDate, dateFormatHM);
+                    // return h + "小时前";
+                } else if (h < 0) {
+                    // return Math.abs(h) + "小时后";
+                } else if (h == 0) {
+                    int m = getOffsetMinutes(c1.getTimeInMillis(), c2.getTimeInMillis());
+                    if (m > 0) {
+                        return m + "分钟前";
+                    } else if (m < 0) {
+                        // return Math.abs(m) + "分钟后";
+                    } else {
+                        return "刚刚";
+                    }
+                }
+
+            } else if (d > 0) {
+                if (d == 1) {
+                    // return "昨天"+getStringByFormat(strDate,outFormat);
+                } else if (d == 2) {
+                    // return "前天"+getStringByFormat(strDate,outFormat);
+                }
+            } else if (d < 0) {
+                if (d == -1) {
+                    // return "明天"+getStringByFormat(strDate,outFormat);
+                } else if (d == -2) {
+                    // return "后天"+getStringByFormat(strDate,outFormat);
+                } else {
+                    // return Math.abs(d) +
+                    // "天后"+getStringByFormat(strDate,outFormat);
+                }
+            }
+
+            String out = getStringByFormat(strDate, outFormat);
+            if (!StringUtils.isBlank(out)) {
+                return out;
+            }
+        } catch (Exception e) {
+        }
+
+        return strDate;
+    }
+
+    /**
+     * 取指定日期为星期几.
+     *
+     * @param strDate
+     *            指定日期
+     * @param inFormat
+     *            指定日期格式
+     * @return String 星期几
+     */
+    public static String getWeekNumber(String strDate, String inFormat) {
+        Assert.isTrue((StringUtils.isNoneBlank(strDate) && StringUtils.isNoneBlank(inFormat)), "strDate or inFormat can not be null!");
+
+        Date mDate = getDateByFormat(strDate, inFormat);
+        Calendar calendar = new GregorianCalendar();
+        calendar.setTime(mDate);
+
+        int intTemp = calendar.get(Calendar.DAY_OF_WEEK) - 1;
+        switch (intTemp) {
+            case 0:
+                return "星期日";
+            case 1:
+                return "星期一";
+            case 2:
+                return "星期二";
+            case 3:
+                return "星期三";
+            case 4:
+                return "星期四";
+            case 5:
+                return "星期五";
+            case 6:
+                return "星期六";
+
+            default:
+                return "";
+        }
+    }
+
+    /**
+     * 根据给定的日期判断是否为上下午.
+     *
+     * @param strDate
+     *            the str date
+     * @param format
+     *            the format
+     * @return the time quantum  Calendar.get(Calendar.HOUR_OF_DAY)
+     */
+    @SuppressWarnings("unchecked")
+    public static String getTimeQuantum(String strDate, String format) {
+        Date mDate = getDateByFormat(strDate, format);
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(mDate);
+        if (calendar.get(Calendar.AM_PM) == Calendar.AM) {
+            return "AM";
+        } else {
+            return "PM";
+        }
+    }
+
+    /**
+     * 判断当前日期是否上下午.
+     * 如果true 表示是上午 false 表示下午
+     *
+     * @return the time quantum  Calendar.get(Calendar.HOUR_OF_DAY)
+     */
+    @SuppressWarnings("unchecked")
+    public static Boolean getIfTimeNowIsAM() {
+
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(new Date());
+        if (calendar.get(Calendar.AM_PM) == Calendar.AM) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * 根据给定的毫秒数算得时间的描述.
+     *
+     * @param milliseconds
+     *            the milliseconds
+     * @return the time description
+     */
+    public static String getTimeDescription(long milliseconds) {
+        if (milliseconds > 1000) {
+            // 大于一分
+            if (milliseconds / 1000 / 60 > 1) {
+                long minute = milliseconds / 1000 / 60;
+                long second = milliseconds / 1000 % 60;
+                return minute + "分" + second + "秒";
+            } else {
+                // 显示秒
+                return milliseconds / 1000 + "秒";
+            }
+        } else {
+            return milliseconds + "毫秒";
+        }
+    }
+
+    /**
+     * 解析时间按照小时:分:秒格式输出
+     *
+     * @param iTimeSeconds 秒
+     * @return
+     */
+    public static String getTime(int iTimeSeconds) {
+        String strUserTime = iTimeSeconds / 60 + " : "
+                + ((iTimeSeconds % 60) < 10 ? ("0" + iTimeSeconds % 60) : iTimeSeconds % 60);
+        return strUserTime;
+    }
+
+    /**
+     *@Author: timo
+     *@Date: 2018/5/23 12:01
+     *@Description:
+     * 获取本周一日期
+     *
+     */
+    public static Date getMondayOFWeek(Date date){
+        int mondayPlus = getMondayPlus(date);
+        GregorianCalendar currentDate = new GregorianCalendar();
+        currentDate.setTime(date);
+//        System.out.println(" 当前时间 :"+currentDate.getTime());
+        currentDate.add(GregorianCalendar.DATE, mondayPlus);
+        return currentDate.getTime();
+    }
+
+    /**
+     *@Author: timo
+     *@Date: 2018/5/23 12:02
+     *@Description:
+     * 获取本周日日期
+     *
+     */
+    public static Date getCurrentWeekday(Date date){
+        int mondayPlus = getMondayPlus(date);
+        GregorianCalendar currentDate = new GregorianCalendar();
+        currentDate.setTime(date);
+        currentDate.add(GregorianCalendar.DATE, mondayPlus+ 6);
+        return currentDate.getTime();
+    }
+
+    /**
+     *@Author: timo
+     *@Date: 2018/5/23 12:03
+     *@Description:
+     * 获取下周一的日期
+     *
+     */
+    public static Date getNextMonday(Date date) {
+        int mondayPlus = getMondayPlus(date);
+        GregorianCalendar currentDate = new GregorianCalendar();
+        currentDate.setTime(date);
+        currentDate.add(GregorianCalendar.DATE, mondayPlus + 7);
+        return currentDate.getTime();
+    }
+
+    /**
+     *@Author: timo
+     *@Date: 2018/5/23 12:03
+     *@Description:
+     * 获取下周日的日期
+     *
+     */
+    public static Date getNextSunday(Date date) {
+        int mondayPlus = getMondayPlus(date);
+        GregorianCalendar currentDate = new GregorianCalendar();
+        currentDate.setTime(date);
+        currentDate.add(GregorianCalendar.DATE, mondayPlus + 7 + 6);
+        return currentDate.getTime();
+    }
+
+    private static int getMondayPlus(Date date) {
+        Calendar cd = Calendar.getInstance();
+        cd.setTime(date);
+        // 获得今天是一周的第几天,星期日是第一天,星期二是第二天......
+        // 因为按中国礼拜一作为第一天所以这里减1
+        int dayOfWeek = cd.get(Calendar.DAY_OF_WEEK) - 1;
+        if (dayOfWeek == 0) {
+            return -6;
+        } else {
+            return 1-dayOfWeek;
+        }
+    }
+
+    /**
+     *@Author: timo
+     *@Date: 2018/5/24 17:32
+     *@Description:
+     * 添加分钟
+     * @param date 从那个时间开始
+     * @param minute 延长的分钟
+     */
+    public static Date addMinute(Date date, Integer minute){
+        Calendar calendar = Calendar.getInstance();
+        calendar.add(Calendar.MINUTE,minute);
+        return calendar.getTime();
+    }
+
+    /**
+     * @Author: timo
+     * @Date: 2018/6/12 16:46
+     * @Description: 获取传入时间的月份的最后一天
+     * @param
+     * @return:
+     *
+     */
+    public static Date getLastDayByMonth(Date date){
+        Calendar ca = Calendar.getInstance();
+        ca.setTime(date);
+        ca.set(Calendar.DAY_OF_MONTH, ca.getActualMaximum(Calendar.DAY_OF_MONTH));
+        return ca.getTime();
+    }
+
+    public static Date getDateShort(Date date){
+        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
+        String strDate=formatter.format(date);
+        ParsePosition pos = new ParsePosition(0);
+        Date strtodate = formatter.parse(strDate, pos);
+        return strtodate;
+    }
+    public static void main(String[] args) {
+        LocalDateTime dateTime1 = LocalDateTime.of(2023,  10, 1, 12, 0);
+        LocalDateTime dateTime2 = LocalDateTime.of(2023,  10, 5, 14, 0);
+
+        // 计算两个 LocalDateTime 之间的天数差
+        long daysDifference = ChronoUnit.DAYS.between(dateTime1,  dateTime2);
+
+        // 将结果转换为 Integer 类型
+        Integer daysDifferenceInteger = (int) daysDifference;
+
+        // 输出结果
+        System.out.println("Days  difference: " + daysDifferenceInteger);
+//        System.out.println("date : "+DateUtil.getStringByOffset(new Date(),"yyyy-MM-dd HH:mm:ss", Calendar.DATE,-5));
+//        // 本周一
+//        Date nowDate = DateUtil.getDateByFormat("2018-07-02",DateUtil.dateFormatYMD);
+//        Date firsDate = getMondayOFWeek(nowDate);
+//        String firsDateStr = DateUtil.getStringByFormat(firsDate,dateFormatYMD);
+//        System.out.println("firsDateStr : "+firsDateStr);
+//
+//        // 本周日
+//        Date lastCurDate = getCurrentWeekday(firsDate);
+//        String lastCurDateStr = DateUtil.getStringByFormat(lastCurDate,dateFormatYMD);
+//        System.out.println("lastCurDateStr : "+lastCurDateStr);
+//
+//        // 下周一
+//        Date nextMonday = getNextMonday(firsDate);
+//        String nextMondayStr = DateUtil.getStringByFormat(nextMonday,dateFormatYMD);
+//        System.out.println("nextMondayStr : "+nextMondayStr);
+//        // 下周日
+//        Date nextSunday = getNextSunday(firsDate);
+//        String nextSundayStr = DateUtil.getStringByFormat(nextSunday,dateFormatYMD);
+//        System.out.println("nextSundayStr : "+nextSundayStr);
+//
+//        Date toDay = new Date();
+//        String toDayStr = DateUtil.getStringByFormat(toDay,dateFormatYMD);
+//        System.out.println("toDayStr : "+toDayStr);
+
+
+
+    }
+
+}

+ 895 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/DateUtils.java

@@ -0,0 +1,895 @@
+package com.storlead.framework.common.util;
+
+import cn.hutool.core.util.RandomUtil;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.util.StringUtils;
+
+import java.beans.PropertyEditorSupport;
+import java.sql.Timestamp;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.Month;
+import java.time.ZoneId;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+/**
+ *
+ * 类描述:时间操作定义类
+ *
+ * @Author: 张代浩
+ * @Date:2012-12-8 12:15:03
+ * @Version 1.0
+ */
+@Log4j2
+public class DateUtils extends PropertyEditorSupport {
+	// 各种时间格式
+	public static final SimpleDateFormat date_sdf = new SimpleDateFormat("yyyy-MM-dd");
+
+	public static final SimpleDateFormat date_yyyy_mm = new SimpleDateFormat("yyyy-MM");
+	// 各种时间格式
+	public static final SimpleDateFormat yyyyMMdd = new SimpleDateFormat("yyyyMMdd");
+	// 各种时间格式
+	public static final SimpleDateFormat date_sdf_wz = new SimpleDateFormat("yyyy年MM月dd日");
+	public static final SimpleDateFormat time_sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
+	public static final SimpleDateFormat yyyymmddhhmmss = new SimpleDateFormat("yyyyMMddHHmmss");
+	public static final SimpleDateFormat short_time_sdf = new SimpleDateFormat("HH:mm");
+	public static final SimpleDateFormat long_time_sdf = new SimpleDateFormat("HHmmss");
+	public static final SimpleDateFormat datetimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+	// 以毫秒表示的时间
+	private static final long DAY_IN_MILLIS = 24 * 3600 * 1000;
+	private static final long HOUR_IN_MILLIS = 3600 * 1000;
+	private static final long MINUTE_IN_MILLIS = 60 * 1000;
+	private static final long SECOND_IN_MILLIS = 1000;
+
+	// 指定模式的时间格式
+	private static SimpleDateFormat getSDFormat(String pattern) {
+		return new SimpleDateFormat(pattern);
+	}
+
+	/**
+	 * 当前日历,这里用中国时间表示
+	 *
+	 * @return 以当地时区表示的系统当前日历
+	 */
+	public static Calendar getCalendar() {
+		return Calendar.getInstance();
+	}
+
+	/**
+	 * 指定毫秒数表示的日历
+	 *
+	 * @param millis 毫秒数
+	 * @return 指定毫秒数表示的日历
+	 */
+	public static Calendar getCalendar(long millis) {
+		Calendar cal = Calendar.getInstance();
+		// --------------------cal.setTimeInMillis(millis);
+		cal.setTime(new Date(millis));
+		return cal;
+	}
+
+	// ////////////////////////////////////////////////////////////////////////////
+	// getDate
+	// 各种方式获取的Date
+	// ////////////////////////////////////////////////////////////////////////////
+
+	/**
+	 * 当前日期
+	 *
+	 * @return 系统当前时间
+	 */
+	public static Date getDate() {
+		return new Date();
+	}
+
+	/**
+	 * 指定毫秒数表示的日期
+	 *
+	 * @param millis 毫秒数
+	 * @return 指定毫秒数表示的日期
+	 */
+	public static Date getDate(long millis) {
+		return new Date(millis);
+	}
+
+	/**
+	 * 时间戳转换为字符串
+	 *
+	 * @param time
+	 * @return
+	 */
+	public static String timestamptoStr(Timestamp time) {
+		Date date = null;
+		if (null != time) {
+			date = new Date(time.getTime());
+		}
+		return date2Str(date_sdf);
+	}
+
+	/**
+	 * 字符串转换时间戳
+	 *
+	 * @param str
+	 * @return
+	 */
+	public static Timestamp str2Timestamp(String str) {
+		Date date = str2Date(str, date_sdf);
+		return new Timestamp(date.getTime());
+	}
+
+	/**
+	 * 字符串转换成日期
+	 *
+	 * @param str
+	 * @param sdf
+	 * @return
+	 */
+	public static Date str2Date(String str, SimpleDateFormat sdf) {
+		if (null == str || "".equals(str)) {
+			return null;
+		}
+		Date date = null;
+		try {
+			date = sdf.parse(str);
+			return date;
+		} catch (ParseException e) {
+			e.printStackTrace();
+		}
+		return null;
+	}
+
+	/**
+	 * 日期转换为字符串
+	 *
+	 * @return 字符串
+	 */
+	public static String date2Str(SimpleDateFormat date_sdf) {
+		Date date = getDate();
+		if (null == date) {
+			return null;
+		}
+		return date_sdf.format(date);
+	}
+
+	/**
+	 * 格式化时间
+	 *
+	 * @param date
+	 * @param format
+	 * @return
+	 */
+	public static String dateformat(String date, String format) {
+		SimpleDateFormat sformat = new SimpleDateFormat(format);
+		Date _date = null;
+		try {
+			_date = sformat.parse(date);
+		} catch (ParseException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+		return sformat.format(_date);
+	}
+
+	/**
+	 * 日期转换为字符串
+	 *
+	 * @param date   日期
+	 * @return 字符串
+	 */
+	public static String date2Str(Date date, SimpleDateFormat date_sdf) {
+		if (null == date) {
+			return null;
+		}
+		return date_sdf.format(date);
+	}
+
+	/**
+	 * 日期转换为字符串
+	 *
+	 * @param format 日期格式
+	 * @return 字符串
+	 */
+	public static String getDate(String format) {
+		Date date = new Date();
+		SimpleDateFormat sdf = new SimpleDateFormat(format);
+		return sdf.format(date);
+	}
+
+	public static String getYestoday() {
+		DateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd");
+		Calendar calendar=Calendar.getInstance();
+		calendar.set(Calendar.HOUR_OF_DAY,-24);
+		return dateFormat.format(calendar.getTime());
+	}
+
+	/**
+	 * 指定毫秒数的时间戳
+	 *
+	 * @param millis 毫秒数
+	 * @return 指定毫秒数的时间戳
+	 */
+	public static Timestamp getTimestamp(long millis) {
+		return new Timestamp(millis);
+	}
+
+	/**
+	 * 以字符形式表示的时间戳
+	 *
+	 * @param time 毫秒数
+	 * @return 以字符形式表示的时间戳
+	 */
+	public static Timestamp getTimestamp(String time) {
+		return new Timestamp(Long.parseLong(time));
+	}
+
+	/**
+	 * 系统当前的时间戳
+	 *
+	 * @return 系统当前的时间戳
+	 */
+	public static Timestamp getTimestamp() {
+		return new Timestamp(System.currentTimeMillis());
+	}
+
+	/**
+	 * 当前时间,格式 yyyy-MM-dd HH:mm:ss
+	 *
+	 * @return 当前时间的标准形式字符串
+	 */
+	public static String now() {
+		return datetimeFormat.format(getCalendar().getTime());
+	}
+
+	/**
+	 * 指定日期的时间戳
+	 *
+	 * @param date 指定日期
+	 * @return 指定日期的时间戳
+	 */
+	public static Timestamp getTimestamp(Date date) {
+		return new Timestamp(date.getTime());
+	}
+
+	/**
+	 * 指定日历的时间戳
+	 *
+	 * @param cal 指定日历
+	 * @return 指定日历的时间戳
+	 */
+	public static Timestamp getCalendarTimestamp(Calendar cal) {
+		// ---------------------return new Timestamp(cal.getTimeInMillis());
+		return new Timestamp(cal.getTime().getTime());
+	}
+
+	public static Timestamp gettimestamp() {
+		Date dt = new Date();
+		DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+		String nowTime = df.format(dt);
+		Timestamp buydate = Timestamp.valueOf(nowTime);
+		return buydate;
+	}
+
+	// ////////////////////////////////////////////////////////////////////////////
+	// getMillis
+	// 各种方式获取的Millis
+	// ////////////////////////////////////////////////////////////////////////////
+
+	/**
+	 * 系统时间的毫秒数
+	 *
+	 * @return 系统时间的毫秒数
+	 */
+	public static long getMillis() {
+		return System.currentTimeMillis();
+	}
+
+	/**
+	 * 指定日历的毫秒数
+	 *
+	 * @param cal 指定日历
+	 * @return 指定日历的毫秒数
+	 */
+	public static long getMillis(Calendar cal) {
+		// --------------------return cal.getTimeInMillis();
+		return cal.getTime().getTime();
+	}
+
+	/**
+	 * 指定日期的毫秒数
+	 *
+	 * @param date 指定日期
+	 * @return 指定日期的毫秒数
+	 */
+	public static long getMillis(Date date) {
+		return date.getTime();
+	}
+
+	/**
+	 * 指定时间戳的毫秒数
+	 *
+	 * @param ts 指定时间戳
+	 * @return 指定时间戳的毫秒数
+	 */
+	public static long getMillis(Timestamp ts) {
+		return ts.getTime();
+	}
+
+	// ////////////////////////////////////////////////////////////////////////////
+	// formatDate
+	// 将日期按照一定的格式转化为字符串
+	// ////////////////////////////////////////////////////////////////////////////
+
+	/**
+	 * 默认方式表示的系统当前日期,具体格式:年-月-日
+	 *
+	 * @return 默认日期按“年-月-日“格式显示
+	 */
+	public static String formatDate() {
+		return date_sdf.format(getCalendar().getTime());
+	}
+
+	/**
+	 * 默认方式表示的系统当前日期,具体格式:yyyy-MM-dd HH:mm:ss
+	 *
+	 * @return 默认日期按“yyyy-MM-dd HH:mm:ss“格式显示
+	 */
+	public static String formatDateTime() {
+		return datetimeFormat.format(getCalendar().getTime());
+	}
+
+	/**
+	 * 获取时间字符串
+	 */
+	public static String getDataString(SimpleDateFormat formatstr) {
+		return formatstr.format(getCalendar().getTime());
+	}
+
+	/**
+	 * 指定日期的默认显示,具体格式:年-月-日
+	 *
+	 * @param cal 指定的日期
+	 * @return 指定日期按“年-月-日“格式显示
+	 */
+	public static String formatDate(Calendar cal) {
+		return date_sdf.format(cal.getTime());
+	}
+
+	/**
+	 * 指定日期的默认显示,具体格式:年-月-日
+	 *
+	 * @param date 指定的日期
+	 * @return 指定日期按“年-月-日“格式显示
+	 */
+	public static String formatDate(Date date) {
+		return date_sdf.format(date);
+	}
+
+	/**
+	 * 指定毫秒数表示日期的默认显示,具体格式:年-月-日
+	 *
+	 * @param millis 指定的毫秒数
+	 * @return 指定毫秒数表示日期按“年-月-日“格式显示
+	 */
+	public static String formatDate(long millis) {
+		return date_sdf.format(new Date(millis));
+	}
+
+	/**
+	 * 默认日期按指定格式显示
+	 *
+	 * @param pattern 指定的格式
+	 * @return 默认日期按指定格式显示
+	 */
+	public static String formatDate(String pattern) {
+		return getSDFormat(pattern).format(getCalendar().getTime());
+	}
+
+	/**
+	 * 指定日期按指定格式显示
+	 *
+	 * @param cal     指定的日期
+	 * @param pattern 指定的格式
+	 * @return 指定日期按指定格式显示
+	 */
+	public static String formatDate(Calendar cal, String pattern) {
+		return getSDFormat(pattern).format(cal.getTime());
+	}
+
+	/**
+	 * 指定日期按指定格式显示
+	 *
+	 * @param date    指定的日期
+	 * @param pattern 指定的格式
+	 * @return 指定日期按指定格式显示
+	 */
+	public static String formatDate(Date date, String pattern) {
+		return getSDFormat(pattern).format(date);
+	}
+
+	// ////////////////////////////////////////////////////////////////////////////
+	// formatTime
+	// 将日期按照一定的格式转化为字符串
+	// ////////////////////////////////////////////////////////////////////////////
+
+	/**
+	 * 默认方式表示的系统当前日期,具体格式:年-月-日 时:分
+	 *
+	 * @return 默认日期按“年-月-日 时:分“格式显示
+	 */
+	public static String formatTime() {
+		return time_sdf.format(getCalendar().getTime());
+	}
+
+	/**
+	 * 指定毫秒数表示日期的默认显示,具体格式:年-月-日 时:分
+	 *
+	 * @param millis 指定的毫秒数
+	 * @return 指定毫秒数表示日期按“年-月-日 时:分“格式显示
+	 */
+	public static String formatTime(long millis) {
+		return time_sdf.format(new Date(millis));
+	}
+
+	/**
+	 * 指定日期的默认显示,具体格式:年-月-日 时:分
+	 *
+	 * @param cal 指定的日期
+	 * @return 指定日期按“年-月-日 时:分“格式显示
+	 */
+	public static String formatTime(Calendar cal) {
+		return time_sdf.format(cal.getTime());
+	}
+
+	/**
+	 * 指定日期的默认显示,具体格式:年-月-日 时:分
+	 *
+	 * @param date 指定的日期
+	 * @return 指定日期按“年-月-日 时:分“格式显示
+	 */
+	public static String formatTime(Date date) {
+		return time_sdf.format(date);
+	}
+
+	// ////////////////////////////////////////////////////////////////////////////
+	// formatShortTime
+	// 将日期按照一定的格式转化为字符串
+	// ////////////////////////////////////////////////////////////////////////////
+
+	/**
+	 * 默认方式表示的系统当前日期,具体格式:时:分
+	 *
+	 * @return 默认日期按“时:分“格式显示
+	 */
+	public static String formatShortTime() {
+		return short_time_sdf.format(getCalendar().getTime());
+	}
+
+	/**
+	 * 指定毫秒数表示日期的默认显示,具体格式:时:分
+	 *
+	 * @param millis 指定的毫秒数
+	 * @return 指定毫秒数表示日期按“时:分“格式显示
+	 */
+	public static String formatShortTime(long millis) {
+		return short_time_sdf.format(new Date(millis));
+	}
+
+	/**
+	 * 指定日期的默认显示,具体格式:时:分
+	 *
+	 * @param cal 指定的日期
+	 * @return 指定日期按“时:分“格式显示
+	 */
+	public static String formatShortTime(Calendar cal) {
+		return short_time_sdf.format(cal.getTime());
+	}
+
+	/**
+	 * 指定日期的默认显示,具体格式:时:分
+	 *
+	 * @param date 指定的日期
+	 * @return 指定日期按“时:分“格式显示
+	 */
+	public static String formatShortTime(Date date) {
+		return short_time_sdf.format(date);
+	}
+
+	// ////////////////////////////////////////////////////////////////////////////
+	// parseDate
+	// parseCalendar
+	// parseTimestamp
+	// 将字符串按照一定的格式转化为日期或时间
+	// ////////////////////////////////////////////////////////////////////////////
+
+	/**
+	 * 根据指定的格式将字符串转换成Date 如输入:2003-11-19 11:20:20将按照这个转成时间
+	 *
+	 * @param src     将要转换的原始字符窜
+	 * @param pattern 转换的匹配格式
+	 * @return 如果转换成功则返回转换后的日期
+	 * @throws ParseException
+	 */
+	public static Date parseDate(String src, String pattern) throws ParseException {
+		return getSDFormat(pattern).parse(src);
+
+	}
+
+	/**
+	 * 根据指定的格式将字符串转换成Date 如输入:2003-11-19 11:20:20将按照这个转成时间
+	 *
+	 * @param src     将要转换的原始字符窜
+	 * @param pattern 转换的匹配格式
+	 * @return 如果转换成功则返回转换后的日期
+	 * @throws ParseException
+	 */
+	public static Calendar parseCalendar(String src, String pattern) throws ParseException {
+
+		Date date = parseDate(src, pattern);
+		Calendar cal = Calendar.getInstance();
+		cal.setTime(date);
+		return cal;
+	}
+
+	public static String formatAddDate(String src, String pattern, int amount) throws ParseException {
+		Calendar cal;
+		cal = parseCalendar(src, pattern);
+		cal.add(Calendar.DATE, amount);
+		return formatDate(cal);
+	}
+
+	/**
+	 * 根据指定的格式将字符串转换成Date 如输入:2003-11-19 11:20:20将按照这个转成时间
+	 *
+	 * @param src     将要转换的原始字符窜
+	 * @param pattern 转换的匹配格式
+	 * @return 如果转换成功则返回转换后的时间戳
+	 * @throws ParseException
+	 */
+	public static Timestamp parseTimestamp(String src, String pattern) throws ParseException {
+		Date date = parseDate(src, pattern);
+		return new Timestamp(date.getTime());
+	}
+
+	// ////////////////////////////////////////////////////////////////////////////
+	// dateDiff
+	// 计算两个日期之间的差值
+	// ////////////////////////////////////////////////////////////////////////////
+
+	/**
+	 * 计算两个时间之间的差值,根据标志的不同而不同
+	 *
+	 * @param flag   计算标志,表示按照年/月/日/时/分/秒等计算
+	 * @param calSrc 减数
+	 * @param calDes 被减数
+	 * @return 两个日期之间的差值
+	 */
+	public static int dateDiff(char flag, Calendar calSrc, Calendar calDes) {
+
+		long millisDiff = getMillis(calSrc) - getMillis(calDes);
+
+		if (flag == 'y') {
+			return (calSrc.get(calSrc.YEAR) - calDes.get(calDes.YEAR));
+		}
+
+		if (flag == 'd') {
+			return (int) (millisDiff / DAY_IN_MILLIS);
+		}
+
+		if (flag == 'h') {
+			return (int) (millisDiff / HOUR_IN_MILLIS);
+		}
+
+		if (flag == 'm') {
+			return (int) (millisDiff / MINUTE_IN_MILLIS);
+		}
+
+		if (flag == 's') {
+			return (int) (millisDiff / SECOND_IN_MILLIS);
+		}
+
+		return 0;
+	}
+	// 计算两个日期之间的差值
+	public static int daysBetween(Date one, Date two) {
+		long difference =  (one.getTime()-two.getTime())/86400000;
+		return (int)Math.abs(difference);
+	}
+
+
+	/**
+	 * String类型 转换为Date, 如果参数长度为10 转换格式”yyyy-MM-dd“ 如果参数长度为19 转换格式”yyyy-MM-dd
+	 * HH:mm:ss“ * @param text String类型的时间值
+	 */
+	@Override
+	public void setAsText(String text) throws IllegalArgumentException {
+		if (StringUtils.hasText(text)) {
+			try {
+				if (text.indexOf(":") == -1 && text.length() == 10) {
+					setValue(this.date_sdf.parse(text));
+				} else if (text.indexOf(":") > 0 && text.length() == 19) {
+					setValue(this.datetimeFormat.parse(text));
+				} else {
+					throw new IllegalArgumentException("Could not parse date, date format is error ");
+				}
+			} catch (ParseException ex) {
+				IllegalArgumentException iae = new IllegalArgumentException("Could not parse date: " + ex.getMessage());
+				iae.initCause(ex);
+				throw iae;
+			}
+		} else {
+			setValue(null);
+		}
+	}
+
+	public static int getYear() {
+		GregorianCalendar calendar = new GregorianCalendar();
+		calendar.setTime(getDate());
+		return calendar.get(Calendar.YEAR);
+	}
+	 /***
+	  * @Description: 获取日本月的第一天(本月)
+	  * @Param:
+	  * @return:
+	  * @Author: YPZ
+	  * @Date: 2023/5/19 14:48
+	  */
+	public static Date getMonthFirstDate() {
+		// 获取当前日期
+		Date currentDate = new Date();
+		// 创建Calendar实例
+		Calendar calendar = Calendar.getInstance();
+		// 设置日期为当前日期
+		calendar.setTime(currentDate);
+		// 将日期设置为该月的第一天
+		calendar.set(Calendar.DAY_OF_MONTH, 1);
+		String strNew = new SimpleDateFormat("yyyy-MM-dd").format(calendar.getTime());
+		try {
+			Date date =new SimpleDateFormat("yyyy-MM-dd").parse(strNew);
+			return date;
+		}catch (Exception e){
+			log.error("日期转换错误");
+		}
+		// 获取本月的开始时间
+
+		return calendar.getTime();
+	}
+
+	/***
+	  * @Description: 获取本月的最后一天(本月)
+	  * @Param:
+	  * @return:
+	  * @Author: YPZ
+	  * @Date: 2023/5/19 14:48
+	  */
+	public static Date getMonthLastDate() {
+		// 获取当前日期
+		Date currentDate = new Date();
+		// 创建Calendar实例
+		Calendar calendar = Calendar.getInstance();
+		// 设置日期为当前日期
+		calendar.setTime(currentDate);
+		// 将日期设置为该月的第一天
+		calendar.set(Calendar.DAY_OF_MONTH, 1);
+
+		// 将日期设置为该月的最后一天
+		calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
+		// 获取本月的结束时间
+//		return calendar.getTime();
+		String strNew = new SimpleDateFormat("yyyy-MM-dd").format(calendar.getTime());
+		try {
+			Date date =new SimpleDateFormat("yyyy-MM-dd").parse(strNew);
+			return date;
+		}catch (Exception e){
+			log.error("日期转换错误");
+		}
+		// 获取本月的开始时间
+
+		return calendar.getTime();
+	}
+
+	/**
+   * 获取当前日期所在季度的开始日期和结束日期
+   * 季度一年四季, 第一季度:1月-3月, 第二季度:4月-6月, 第三季度:7月-9月, 第四季度:10月-12月
+   * @param isFirst  true表示查询本季度开始日期  false表示查询本季度结束日期
+   * @return
+   */
+      public static Date getStartOrEndDayOfQuarter(Boolean isFirst){
+		         LocalDate today=LocalDate.now();
+		         LocalDate resDate = LocalDate.now();
+		         if (today == null) {
+			             today = resDate;
+			         }
+		         Month month = today.getMonth();
+		         Month firstMonthOfQuarter = month.firstMonthOfQuarter();
+		         Month endMonthOfQuarter = Month.of(firstMonthOfQuarter.getValue() + 2);
+		         if (isFirst) {
+			             resDate = LocalDate.of(today.getYear(), firstMonthOfQuarter, 1);
+			         } else {
+			             resDate = LocalDate.of(today.getYear(), endMonthOfQuarter, endMonthOfQuarter.length(today.isLeapYear()));
+			         }
+		         return Date.from(resDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
+		     }
+
+	/**
+	 * 通过时间秒毫秒数判断两个时间的间隔
+	 * @param date1
+	 * @param date2
+	 * @return
+	 */
+	public static int differentDaysByMillisecond(Date date1,Date date2)
+	{
+		int days = (int) ((date2.getTime() - date1.getTime()) / (1000*3600*24));
+		return days;
+	}
+
+	public static String getUploadFileName() {
+		return date2Str(long_time_sdf)+RandomUtil.randomNumbers(4);
+	}
+
+	public static String getUploadDir() {
+		return date2Str(yyyyMMdd);
+	}
+	/**
+	* @Description:  取日期的后一天
+	* @Param:
+	* @return:
+	* @Author: YPZ
+	* @Date: 2022/7/25
+	*/
+
+	public static Date nextDate(Date date) {
+		Calendar calendar = Calendar.getInstance(); //得到日历
+		calendar.setTime(date);
+		calendar.add(Calendar.DAY_OF_MONTH, 1);  //设置为后一天
+		Date tomorrow = calendar.getTime();   //得到后一天的时间
+		return tomorrow;
+
+	}
+
+	/**
+	* @Description:  取日期的前一天
+	* @Param:
+	* @return:
+	* @Author: YPZ
+	* @Date: 2022/7/25
+	*/
+
+	public static Date preDate(Date date) {
+		Calendar calendar = Calendar.getInstance(); //得到日历
+		calendar.setTime(date);
+		calendar.add(Calendar.DAY_OF_MONTH, -1);  //设置为前一天
+		Date tomorrow = calendar.getTime();   //得到前一天的时间
+		return tomorrow;
+
+	}
+
+	/**
+	* @Description:  取日期的最后一秒
+	* @Param:
+	* @return:
+	* @Author: YPZ
+	* @Date: 2022/7/25
+	*/
+
+	public static Date dateEndTime(Date date) {
+
+		try {
+			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+			String time=sdf.format(date);//将前端的日期转成String字符串
+			time+=" 23:59:59";//字符串拼接
+			SimpleDateFormat end = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//将字符串格式化一下
+			return end.parse(time);
+		}catch (ParseException e){
+			return date;
+		}
+	}
+
+	/**
+	* @Description:  取日期的最后一秒
+	* @Param:
+	* @return:
+	* @Author: YPZ
+	* @Date: 2022/7/25
+	*/
+
+	public static Date dateEndTime(LocalDate localdate) {
+
+		ZoneId zone = ZoneId.systemDefault();
+		Instant instant = localdate.atStartOfDay().atZone(zone).toInstant();
+		Date date = Date.from(instant);
+		try {
+			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+			String time=sdf.format(date);//将前端的日期转成String字符串
+			time+=" 23:59:59";//字符串拼接
+			SimpleDateFormat end = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//将字符串格式化一下
+			return end.parse(time);
+		}catch (ParseException e){
+			return null;
+		}
+	}
+
+
+
+	/**
+	 * 判断一年的第几周
+	 * @param datetime
+	 * @return
+	 * @throws ParseException
+	 */
+	public static Integer whatWeek(String datetime) {
+		SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
+		Date date = null;
+		try {
+			date = format.parse(datetime);
+		} catch (ParseException e) {
+			e.printStackTrace();
+		}
+		Calendar calendar = Calendar.getInstance();
+		calendar.setFirstDayOfWeek(Calendar.MONDAY);
+		calendar.setTime(date);
+		Integer weekNumbe = calendar.get(Calendar.WEEK_OF_YEAR);
+		return weekNumbe;
+	}
+	public static Integer weekTest(String datetime){
+		Calendar calendar = Calendar.getInstance();
+		//设置星期一为一周开始的第一天
+		calendar.setFirstDayOfWeek(Calendar.MONDAY);
+		//获得当前的时间戳
+		calendar.setTimeInMillis(System.currentTimeMillis());
+		//获得当前日期属于今年的第几周
+		Integer weekOfYear = calendar.get(Calendar.WEEK_OF_YEAR);
+		return weekOfYear;
+	}
+	 /***
+	  * @Description: 获取日期几年前后的时间
+	  * @Param: years参照时间,年(之前为负数,之后为正数)
+	  * @return:
+	  * @Author: YPZ
+	  * @Date: 2022/11/22 14:38
+	  */
+	public static Date calYear(Date date,int years){
+		Calendar calendar = Calendar.getInstance();
+		calendar.setTime(date);
+		calendar.add(Calendar.YEAR,years);
+		return calendar.getTime();
+	}
+
+	public static void main(String[] args) {
+
+		System.out.println("本月第一天为:"+getMonthFirstDate());
+		System.out.println("本月最后一天为:"+getMonthLastDate());
+		System.out.println("本季度第一天为:"+getStartOrEndDayOfQuarter(true));
+		System.out.println("本季度最后一天为:"+getStartOrEndDayOfQuarter(false));
+//		System.out.println(getUploadDir());
+//		System.out.println(weekTest("2022-02-18"));
+//		LocalDateTime end = LocalDateTime.parse("2022-07-21 23:59:59", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+//
+//		long until = java.time.LocalDate.now().until(end, ChronoUnit.DAYS);
+//		System.out.println(until);
+//		try {
+//		LocalDate localDate1 = LocalDate.of(2022,8,15);
+//
+//		String beginTime = "2022-08-16 14:42:32";
+//		String endTime = "2022-08-17 14:42:32";
+//		SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+//		Date date1 = format.parse(beginTime);
+//		Date date2 = format.parse(endTime);
+//
+//		LocalDate localDate2 = date1.toInstant()
+//				.atZone(ZoneId.systemDefault())
+//				.toLocalDate();
+//		Boolean isBefore = localDate1.isBefore(localDate2);
+//
+//			System.out.println(isBefore);
+//		}catch (Exception e){
+//
+//		}
+
+	}
+
+}

+ 150 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/HtmlUtils.java

@@ -0,0 +1,150 @@
+package com.storlead.framework.common.util;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.beans.PropertyEditorSupport;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ *
+ * 类描述:时间操作定义类
+ *
+ * @Author:
+ * @Date:2012-12-8 12:15:03
+ * @Version 1.0
+ */
+public class HtmlUtils extends PropertyEditorSupport {
+
+
+	/**
+	 * content
+	 *
+	 */
+//	public static String userContent(String content) {
+//		// <p>段落替换为换行
+//		content = content.replaceAll("<p .*?>", "\r\n");
+//// <br><br/>替换为换行
+//		content = content.replaceAll("<br\\s*/?>", "\r\n");
+//// 去掉其它的<>之间的东西
+//		content = content.replaceAll("\\<.*?>", "");
+//		content = content.replaceAll("&nbsp;", " ");
+//// 还原HTML
+//// content = HTMLDecoder.decode(content);
+//		return content;
+//	}
+
+
+	 /***
+	  * @Description: 不处理HTML标签
+	  * @Param:
+	  * @return:
+	  * @Author: YPZ
+	  * @Date: 2022/11/29 14:32
+	  */
+	 public static String userContent(Object var) {
+		 if (Objects.isNull(var)){
+			 return "";
+		 }
+	 	 String inputString = var.toString();
+		 if (StringUtils.isNumeric(inputString)) {
+			 return inputString;
+		 }
+		 if(StringUtils.isBlank(inputString)){
+			 return inputString;
+		 }
+		 if (inputString.indexOf("http") >= 0 || inputString.indexOf("<img") >= 0) {
+			 return inputString;
+		 }
+		 return clearContent(inputString);
+	 }
+	/**
+	 * 去除inputString中的HTML/CSS/JS标签
+	 */
+	public static String clearContent(String inputString) {
+		if(StringUtils.isBlank(inputString)){
+			return inputString;
+		}
+
+		String htmlStr = inputString;
+		String textStr = "";
+		Pattern p_script;
+		Matcher m_script;
+		Pattern p_style;
+		Matcher m_style;
+		Pattern p_html;
+		Matcher m_html;
+		Pattern p_html1;
+		Matcher m_html1;
+
+		try {
+			String regEx_script = "<[//s]*?script[^>]*?>[//s//S]*?<[//s]*?///[//s]*?script[//s]*?>";
+			String regEx_style = "<[//s]*?style[^>]*?>[//s//S]*?<[//s]*?///[//s]*?style[//s]*?>";
+			String regEx_html = "<[^>]+>";
+			String regEx_html1 = "<[^>]+";
+
+			p_script = Pattern.compile(regEx_script, Pattern.CASE_INSENSITIVE);
+
+			m_script = p_script.matcher(htmlStr);
+			htmlStr = m_script.replaceAll("");
+
+			p_style = Pattern.compile(regEx_style, Pattern.CASE_INSENSITIVE);
+
+			m_style = p_style.matcher(htmlStr);
+
+			htmlStr = m_style.replaceAll("");
+
+			p_html = Pattern.compile(regEx_html, Pattern.CASE_INSENSITIVE);
+
+			m_html = p_html.matcher(htmlStr);
+
+			htmlStr = m_html.replaceAll("");
+
+			p_html1 = Pattern.compile(regEx_html1, Pattern.CASE_INSENSITIVE);
+
+			m_html1 = p_html1.matcher(htmlStr);
+
+			htmlStr = m_html1.replaceAll("");
+
+			htmlStr = htmlStr.replaceAll("&nbsp;", " ");
+			textStr = htmlStr;
+
+		} catch (Exception e) {
+			System.err.println("Html2Text: " + e.getMessage());
+		}
+		return textStr;
+	}
+
+
+
+	/**
+	 * @Description:  统计有效字符长度
+	 * @Param:
+	 * @return:
+	 * @Author: YPZ
+	 * @Date: 2022-7-4
+	 */
+
+	public static int userContentSize(String content) {
+		// <p>段落替换为换行
+		content = content.replaceAll("<p .*?>", "");
+// <br><br/>替换为换行
+		content = content.replaceAll("<br\\s*/?>", "");
+// 去掉其它的<>之间的东西
+		content = content.replaceAll("\\<.*?>", "");
+		content = content.replaceAll("&nbsp;", "");
+// 还原HTML
+// content = HTMLDecoder.decode(content);
+		return content.length();
+	}
+
+	public static void main(String[] args) {
+//		String content = "<p>一、SP21014.01,&nbsp; 网表与客户光纤那部分代码合成编译后测试仍有问题,时钟域太多,得去掉一部分;</p><p>二、SP22001.00,DDR3多通道读功能还有问题;</p><p>三、SP19021.01,&nbsp; 30000个文件情况下列表性能测试;</p>";
+		String content = "<p><span style=\"color: rgb(96, 98, 102); background-color: rgb(245, 247, 250); font-size: 13px;\">年度目标3</span></p>";
+		System.out.println(userContent(content));
+		System.out.println(userContent(content).length());
+		System.out.println(userContentSize(content));
+	}
+
+}

+ 529 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/LocalDateUtils.java

@@ -0,0 +1,529 @@
+package com.storlead.framework.common.util;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.time.*;
+import java.time.temporal.ChronoField;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalAdjusters;
+import java.util.*;
+
+import static java.time.ZoneId.SHORT_IDS;
+/**
+ * @program: sp-sales
+ * @description: jdk8日期判断工具类
+ * @author: xiaojiawei
+ * @create: 2022-01-18 09:57
+ **/
+@Slf4j
+public class LocalDateUtils {
+
+
+    public static Date LocalDate2Date(LocalDate date){
+        ZoneId zone = ZoneId.systemDefault();
+        Instant instant = date.atStartOfDay().atZone(zone).toInstant();
+        Date da = Date.from(instant);
+        return da;
+    }
+    public static LocalDate date2LocalDate(Date date){
+        Instant instant =date.toInstant();
+        ZoneId zone = ZoneId.systemDefault();
+        LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone);
+        LocalDate localDate= localDateTime.toLocalDate();
+        return localDate;
+    }
+    public static LocalDateTime date2LocalDateTime(Date date){
+        Instant instant = date.toInstant();
+        ZoneId zoneId = ZoneId.systemDefault();
+
+        LocalDateTime localDateTime = instant.atZone(zoneId).toLocalDateTime();
+        return localDateTime;
+    }
+
+    public static Date localDateTime2date(LocalDateTime localDateTime){
+        return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
+    }
+    /**
+     * 从时间戳转换成 LocalDateTime
+     * @param timestamp
+     * @return
+     */
+    private static LocalDateTime timestamp(Long timestamp){
+        Date date = new Date(timestamp);
+        return LocalDateTime.ofInstant(date.toInstant(), ZoneId.of(SHORT_IDS.get("CTT")));
+    }
+
+    public static boolean isBetween(LocalDate date ,LocalDate t1,LocalDate t2){
+        if(t1.isAfter(t2)){
+            LocalDate tmp = t1;
+            t1=t2;
+            t2=tmp;
+        }
+        if(date.isBefore(t1)){
+            return false;
+        }
+        if(date.isAfter(t2)){
+            return false;
+        }
+        return true;
+    }
+    /**
+     * @Description: 计算两个日期相隔的几周 ,e.g 本周日和下周一 相隔一周
+     * @Param: [t1, t2]
+     * @return: long  相隔几周
+     * @Author: xiaojiawei
+     * @Date: 2022/1/18
+     */
+    public static long interWeeks(LocalDate t1, LocalDate t2){
+        if(t1.isAfter(t2)){
+            LocalDate tmp = t1;
+            t1=t2;
+            t2=tmp;
+        }
+        t1 = t1.with(ChronoField.DAY_OF_WEEK, DayOfWeek.MONDAY.getValue());
+        long weeks =  ChronoUnit.WEEKS.between(t1,t2);
+        return weeks;
+    }
+    /**
+     * 以second为主进行判断
+     * 以first为开始时间,second为结束时间
+     * 判断两个时间是否在通过一个周内。
+     *
+     * @param first
+     * @param second
+     */
+    private static Boolean checkWeekBetween(LocalDateTime first, LocalDateTime second){
+        //两个时间差不超过7天,
+        Period period = Period.between(first.toLocalDate(),second.toLocalDate());
+        int years = period.getYears();
+        log.info("years:{}",years);
+        if(years > 0){
+            return false;
+        }
+        int months = period.getMonths();
+        log.info("months:{}",months);
+        if(months > 0){
+            return false;
+        }
+        int days = period.getDays();
+        log.info("days:{}",days);
+        if(days == 0){
+//          表明是同一天
+            return true;
+        }
+        if(days > 7  || days < -7){
+//            两个时间差 超出了7天
+            return false;
+        }
+        int firstDayOfWeek = first.getDayOfWeek().getValue();
+        int secondDayOfWeek = second.getDayOfWeek().getValue();
+        if(secondDayOfWeek == 1){
+            if(oneDay(firstDayOfWeek,secondDayOfWeek,days)){
+                return true;
+            }else {
+                return false;
+            }
+        }
+        if(secondDayOfWeek == 7){
+            if(sevenDay(firstDayOfWeek,secondDayOfWeek,days)){
+                return true;
+            }else {
+                return false;
+            }
+        }
+        if(otherDay(firstDayOfWeek,secondDayOfWeek,days)){
+            return true;
+        }else{
+            return false;
+        }
+    }
+
+    /**
+     * secondDayOfWeek 是所在星期的第一天
+     * 星期的第一天 数据处理
+     * @return
+     */
+    private static Boolean oneDay(int firstDayOfWeek,int secondDayOfWeek,int days){
+        if(days > 0 ){
+            // 表明 first 比second 小 不在同一周
+            return false;
+        }else {
+            //  表明 first 比second 大
+            if(secondDayOfWeek - days == firstDayOfWeek){
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 星期的第7天的时候处理数据
+     * @return
+     */
+    private static Boolean sevenDay(int firstDayOfWeek,int secondDayOfWeek,int days){
+//        second 是周日
+        if(firstDayOfWeek + days == secondDayOfWeek){
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 其他天的数据处理
+     * @return
+     */
+    private static Boolean otherDay(int firstDayOfWeek,int secondDayOfWeek,int days){
+        if(days < 0){
+            //表明 first 比 second 大
+            if((secondDayOfWeek - days) == firstDayOfWeek){
+                //两者是 一周内
+                return true;
+            }
+        }else {
+            //表明 first 比 second 小
+            if(firstDayOfWeek + days == secondDayOfWeek){
+                //两者是 一周内
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 获取几月份的第几周 [X月份第X周]
+     *
+     * @param sourceTime 这个推荐取00:00:00时间
+     * @return
+     */
+    public static Map<String, Object> getMonthNoAndWeekNo(LocalDateTime sourceTime) {
+        Map<String, Object> map = new HashMap<>();
+        String monthNoAndWeekNo = null;
+        //获取当月的第一天
+        LocalDateTime firstDayOfMonth = getFirstLocalDayOfMonth(sourceTime);
+
+        //获取月第一天开始的周一,从当月第一天开始找
+        LocalDateTime firstMondayOfMonth = firstDayOfMonth;
+        for (int i = 0; i < 6; i++) {
+            DayOfWeek dayOfWeekTemp = firstMondayOfMonth.getDayOfWeek();
+            if (dayOfWeekTemp.equals(DayOfWeek.MONDAY)) {
+                break;
+            }
+            //往后推一天
+            firstMondayOfMonth = firstMondayOfMonth.plusDays(1);
+        }
+
+        //比较当月的第一个星期一 < = 参数时间
+        if (!firstMondayOfMonth.isBefore(sourceTime)) {
+            //如果当月的第一个周一大于参数时间,则要计算到上个月份去
+            //获取上一个月的第一个周一
+            LocalDateTime lastMontLocalDateTime = sourceTime.minusMonths(1);
+            //上个月的第一天
+            LocalDateTime firstDayOfMonth_last = getFirstLocalDayOfMonth(lastMontLocalDateTime);
+            //从上个月的第一天开始找周一
+            LocalDateTime firstMondayOfMonth_last = firstDayOfMonth_last;
+            for (int i = 0; i < 6; i++) {
+                DayOfWeek dayOfWeekTemp = firstMondayOfMonth_last.getDayOfWeek();
+                if (dayOfWeekTemp.equals(DayOfWeek.MONDAY)) {
+                    break;
+                }
+                //往后推一天
+                firstMondayOfMonth_last = firstMondayOfMonth_last.plusDays(1);
+            }
+
+            //  计算两个时间间隔天数 (上月第一个周一 减去 当前时间)
+            Duration duration = Duration.between(firstMondayOfMonth_last, sourceTime);
+            long diffDays = duration.toDays(); //相差的天数
+            //第几周weekNo
+            long weekNo = (diffDays / 7) + 1;
+            //月份
+            int monthNo = firstMondayOfMonth_last.getMonth().getValue();//汉字版月份
+            String monthChinese = getMonthChinese(monthNo);
+            monthNoAndWeekNo = monthChinese + "月份" + "第" + weekNo + "周";
+            map.put("yearNo",firstMondayOfMonth_last.getYear());
+            map.put("monthNo", monthNo);
+            map.put("weekNo", weekNo);
+            map.put("monthNoAndWeekNo", monthNoAndWeekNo);
+
+        } else {
+            //当月第一个周一在当前时间之前 firstMondayOfMonth<=sourceTime
+            //计算两个时间间隔天数
+            int dayOfMonthFirstMonday = firstMondayOfMonth.getDayOfMonth();
+            int dayOfMonthSourceTime = sourceTime.getDayOfMonth();
+
+            int diffDays = dayOfMonthSourceTime - dayOfMonthFirstMonday;
+            //第几周weekNo
+            int weekNo = (diffDays / 7) + 1;
+            //月份
+            int monthNo = sourceTime.getMonth().getValue();
+            map.put("yearNo",sourceTime.getYear());
+            map.put("monthNo", monthNo);
+            map.put("weekNo", weekNo);
+            //汉字版月份
+            String monthChinese = getMonthChinese(monthNo);
+            monthNoAndWeekNo = monthChinese + "月份" + "第" + weekNo + "周";
+            map.put("monthNoAndWeekNo", monthNoAndWeekNo);
+        }
+
+        return map;
+    }
+    /**
+     * 获取汉字版月份
+     *
+     * @param monthNo 第几月
+     * @return
+     */
+    private static String getMonthChinese(int monthNo) {
+        switch (monthNo) {
+            case 1:
+                return "一";
+            case 2:
+                return "二";
+            case 3:
+                return "三";
+            case 4:
+                return "四";
+            case 5:
+                return "五";
+            case 6:
+                return "六";
+            case 7:
+                return "七";
+            case 8:
+                return "八";
+            case 9:
+                return "九";
+            case 10:
+                return "十";
+            case 11:
+                return "十一";
+            case 12:
+                return "十二";
+            default:
+                break;
+        }
+        return null;
+    }
+    /**
+     * 获取某月第一天的00:00:00
+     *
+     * @return
+     */
+    public static LocalDateTime getFirstLocalDayOfMonth(LocalDateTime localDateTime) {
+        return localDateTime.with(TemporalAdjusters.firstDayOfMonth()).with(LocalTime.MIN);
+    }
+
+    public static Date LocalDateTimeTodate(LocalDateTime localDateTime) {
+        Date date = Date.from( localDateTime.atZone( ZoneId.systemDefault()).toInstant());
+        return date;
+    }
+    /**
+     * 获取当前日期所在季度的开始日期和结束日期
+     * 季度一年四季, 第一季度:1月-3月, 第二季度:4月-6月, 第三季度:7月-9月, 第四季度:10月-12月
+     * @param isFirst  true表示查询本季度开始日期  false表示查询本季度结束日期
+     * @return
+     */
+    public static LocalDate getStartOrEndDayOfQuarter(Boolean isFirst){
+        LocalDate today=LocalDate.now();
+        LocalDate resDate = LocalDate.now();
+        if (today == null) {
+            today = resDate;
+        }
+        Month month = today.getMonth();
+        Month firstMonthOfQuarter = month.firstMonthOfQuarter();
+        Month endMonthOfQuarter = Month.of(firstMonthOfQuarter.getValue() + 2);
+        if (isFirst) {
+            resDate = LocalDate.of(today.getYear(), firstMonthOfQuarter, 1);
+        } else {
+            resDate = LocalDate.of(today.getYear(), endMonthOfQuarter, endMonthOfQuarter.length(today.isLeapYear()));
+        }
+        return resDate;
+    }
+    /**
+     * @Description: 获取当前季度,根据月转换到季度
+     * @Param:
+     * @return:
+     * @Author: YPZ
+     * @Date: 2022-6-30
+     */
+    public static int getQuarterOfYear() {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(new Date());
+        return calendar.get(Calendar.MONTH) / 3 + 1;
+    }
+
+    /**
+     * @Description: 获取时间对应季度
+     * @Param:
+     * @return:
+     * @Author: YPZ
+     * @Date: 2022-6-30
+     */
+    public static int getQuarter(LocalDateTime localDateTime) {
+        return (localDateTime.getMonthValue()-1)/3 + 1;
+    }
+    /**
+     * @Description: 根据季度获取第一天/最后一天,从季度到第一个月,再到月的第一天
+     * @Param: year,quarter,isFirst(true表示查询本季度开始日期  false表示查询本季度结束日期)
+     * @return:
+     * @Author: YPZ
+     * @Date: 2022-6-30
+     */
+    public static LocalDate getStartDayOfQuarter(int year, int quarter,Boolean isFirst) {
+
+        int startMonth = (quarter - 1) * 3;
+
+        int lastMonth = quarter * 3 - 1;
+
+        // 根据月获取开始时间
+        Calendar cal = Calendar.getInstance();
+
+        cal.set(Calendar.YEAR, year);
+        if(isFirst) {
+            cal.set(Calendar.MONTH, startMonth);
+            cal.set(Calendar.DAY_OF_MONTH, 1);
+        }else {
+            cal.set(Calendar.MONTH, lastMonth);
+            cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
+        }
+
+        cal.set(Calendar.HOUR, 0);
+        cal.set(Calendar.MINUTE, 0);
+        cal.set(Calendar.SECOND, 0);
+
+        return cal.getTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+    }
+
+     /***
+      * @Description: 获取两个localDate间的所有日期(从小到大排序)
+      * @Param:
+      * @return:
+      * @Author: YPZ
+      * @Date: 2022/9/7 17:55
+      */
+    public static  List<LocalDate> getAscDateList(LocalDate startDate,LocalDate endDate) {
+        List<LocalDate> result = new ArrayList<>();
+        if(endDate.compareTo(startDate) < 0 ){
+            return  result;
+        }
+        while (true){
+            result.add(startDate);
+            if(startDate.compareTo(endDate) >= 0){
+                break;
+            }
+            startDate = startDate.plusDays(1);
+        }
+        return result;
+    }
+    /***
+      * @Description: java获取两个LocalDate时间段内,每隔固定天数的所有日期
+      * @Param:
+      * @return:
+      * @Author: YPZ
+      * @Date: 2023/9/19 11:26
+      */
+    public static List<LocalDate> getEveryOtherDayBetweenDates(LocalDate startDate, LocalDate endDate,Integer days) {
+        List<LocalDate> result = new ArrayList<>();
+        days = days +1;
+        for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(days)) {
+            result.add(date);
+        }
+        return result;
+    }
+
+    // 静态方法,用于计算日期范围内的每隔两周的周一和周日
+    public static List<LocalDate> getMondaysAndSundays(LocalDate startDate, LocalDate endDate,int weeks) {
+        weeks = weeks+1;
+        List<LocalDate> result = new ArrayList<>();
+
+        // 初始日期调整到当前周的周一
+        startDate = startDate.with(DayOfWeek.MONDAY);
+
+        // 如果第一周的周一小于开始日期,将开始日期添加到结果列表
+        if (!startDate.isBefore(startDate)) {
+
+            result.add(startDate);
+        }
+
+        // 计算两周的时间间隔
+        long daysBetween = ChronoUnit.DAYS.between(startDate, endDate);
+
+        // 计算每隔两周的日期
+        for (long i = weeks*7; i <= daysBetween; i += weeks*7) {
+            LocalDate monday = startDate.plusDays(i);
+            result.add(monday);
+        }
+
+        return result;
+    }
+
+
+    public static void main(String[] args) {
+//        System.out.println(getStartOrEndDayOfQuarter(false));
+//        //获取当前年份
+//        Calendar calendar = Calendar.getInstance();
+//        int year = calendar.get(Calendar.YEAR);
+//        // 获取当前季度
+//        int month = calendar.get(Calendar.MONTH);
+//        int quarter = month%3==0?month/3:month/3+1;
+//        System.out.println(year);
+//        System.out.println(quarter);
+//        System.out.println(getStartDayOfQuarter(2021, 3, true));
+//        System.out.println(getStartDayOfQuarter(2021, 3, false));
+//
+//        LocalDateTime start = LocalDateTime.now();
+//        int year = start.getYear();
+//        int quarter = getQuarter(start);
+//
+//
+//        System.out.println("年度为:"+year);
+//        System.out.println("季度为:"+quarter);
+//        String dateStr = "2022-08-29";
+//
+//        LocalDate localDate = LocalDate.parse(dateStr , DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+//
+//        if(localDate.equals(localDate.with(TemporalAdjusters.lastDayOfMonth()))){
+//            System.out.println("是月底");
+//        }
+//        if(localDate.equals(localDate.with(DayOfWeek.SUNDAY))){
+//            System.out.println("是周日");
+//        }
+//
+//        String beginDateStr = "2022-08-29";
+//        LocalDate beginDate = LocalDate.parse(beginDateStr , DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+//        String endDateStr = "2022-09-09";
+//        LocalDate endDate = LocalDate.parse(endDateStr , DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+//        List<LocalDate> localDateList = getAscDateList(beginDate,endDate);
+//        localDateList.forEach(d->System.out.println(d));
+
+//        LocalDate startDate = LocalDate.of(2022, 1, 1);
+//        LocalDate endDate = LocalDate.of(2022, 1, 10);
+//        List<LocalDate> result = getEveryOtherDayBetweenDates(startDate, endDate,1);
+//        for (LocalDate date : result) {
+//            System.out.println(date);
+//        }
+
+//        LocalDate startDate = LocalDate.of(2023, 9, 16);
+//        LocalDate endDate = LocalDate.of(2023, 12, 30);
+//
+//        List<LocalDate> dateRanges = getMondaysAndSundays(startDate, endDate,2);
+//
+//        for (LocalDate vo : dateRanges) {
+//            System.out.println("Start Date: " + vo);
+//        }
+
+        LocalDate startDate = LocalDate.of(2023, 1, 2);
+        LocalDate endDate = LocalDate.of(2028, 11, 26);
+
+        for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusYears(2)) {
+            LocalDate firstDayOfMonth = date.withDayOfYear(1);
+            LocalDate lastDayOfMonth = date.withDayOfYear(date.lengthOfYear());
+            System.out.println("月份开始时间:" + firstDayOfMonth);
+            System.out.println("月份结束时间:" + lastDayOfMonth);
+            System.out.println();
+        }
+
+
+
+
+    }
+}

+ 47 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/MD5Util.java

@@ -0,0 +1,47 @@
+package com.storlead.framework.common.util;
+
+import java.security.MessageDigest;
+
+public class MD5Util {
+
+	public static String byteArrayToHexString(byte b[]) {
+		StringBuffer resultSb = new StringBuffer();
+		for (int i = 0; i < b.length; i++){
+			resultSb.append(byteToHexString(b[i]));
+		}
+		return resultSb.toString();
+	}
+
+	private static String byteToHexString(byte b) {
+		int n = b;
+		if (n < 0) {
+			n += 256;
+		}
+		int d1 = n / 16;
+		int d2 = n % 16;
+		return hexDigits[d1] + hexDigits[d2];
+	}
+
+	public static String MD5Encode(String origin, String charsetname) {
+		String resultString = null;
+		try {
+			resultString = new String(origin);
+			MessageDigest md = MessageDigest.getInstance("MD5");
+			if (charsetname == null || "".equals(charsetname)) {
+				resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
+			} else {
+				resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));
+			}
+		} catch (Exception exception) {
+		}
+		return resultString;
+	}
+
+	private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
+			"6", "7", "8", "9", "A", "B", "C", "D", "E", "F" };
+
+	public static void main(String[] args) {
+		System.out.println(MD5Encode("otnt3isebpmz", "utf-8"));
+	}
+
+}

+ 100 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/RandomCodeUtil.java

@@ -0,0 +1,100 @@
+package com.storlead.framework.common.util;
+
+import org.apache.commons.lang3.RandomUtils;
+import org.springframework.util.Assert;
+
+import java.util.Random;
+
+/**
+ * 随机数工具生成类
+ *
+ * @author blank
+ * @since 2018/5/17 上午10:36
+ */
+public class RandomCodeUtil {
+
+
+    /**
+     * 生成4位数随机验证码
+     */
+    public static int getCheckCode() {
+        Random random = new Random(System.currentTimeMillis());
+        int num = random.nextInt(9999) % 10000;
+        return num < 1000 ? 1000 + num : num;
+    }
+
+    /**
+     * 返回 1000-9999之间的4位随机数
+     *
+     * @return 1000-9999之间的4位随机数
+     * @author blank
+     * @since 2018/5/17 上午10:42
+     */
+    public static int RandomCheckCode() {
+        int num = RandomUtils.nextInt(0, 9999);
+        return num < 1000 ? 1000 + num : num;
+    }
+
+    /**
+     * 返回指定[1~9]位数的随机数
+     *
+     * @param numLength 位数
+     * @return 指定位数的随机数
+     * @author blank
+     * @since 2018/5/17 上午10:49
+     */
+    public static int randomCode(int numLength) {
+        Assert.isTrue(numLength >= 1 && numLength <= 9, " Due the Integer Number limit the numLength can neither smaller than 1 nor larger than 9!");
+
+        int startNum;
+        int endNum;
+        switch (numLength) {
+
+            case 1:
+                startNum = 0;
+                endNum = 9;
+                break;
+            case 2:
+                startNum = 10;
+                endNum = 99;
+                break;
+            case 3:
+                startNum = 100;
+                endNum = 999;
+                break;
+            case 5:
+                startNum = 10000;
+                endNum = 99999;
+                break;
+            case 6:
+                startNum = 100000;
+                endNum = 999999;
+                break;
+            case 7:
+                startNum = 1000000;
+                endNum = 9999999;
+                break;
+            case 8:
+                startNum = 10000000;
+                endNum = 99999999;
+                break;
+            case 9:
+                startNum = 100000000;
+                endNum = 999999999;
+                break;
+
+            default:
+                startNum = 1000;
+                endNum = 9999;
+                break;
+        }
+
+        return RandomUtils.nextInt(startNum, endNum);
+    }
+
+
+    public static void main(String[] args) {
+        System.out.println(randomCode(19));
+
+    }
+}

+ 26 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/RandomGenerateHelper.java

@@ -0,0 +1,26 @@
+package com.storlead.framework.common.util;
+
+import java.util.Random;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-08-09 10:33
+ */
+public class RandomGenerateHelper {
+
+    public static String generateRandomString(int length) {
+        String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+        Random random = new Random();
+        StringBuilder sb = new StringBuilder(length);
+
+        for (int i = 0; i < length; i++) {
+            int index = random.nextInt(characters.length());
+            sb.append(characters.charAt(index));
+        }
+
+        return sb.toString();
+    }
+
+}

+ 202 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/RsaUtils.java

@@ -0,0 +1,202 @@
+package com.storlead.framework.common.util;
+
+import org.apache.commons.codec.binary.Base64;
+
+import javax.crypto.Cipher;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+/**
+ * @from fhadmin.cn
+ * @description Rsa 工具类,公钥私钥生成,加解密
+ * @date 2020-05-18
+ **/
+public class RsaUtils {
+
+
+//    public static final String PUBLIC_KEY = "RSAPublicKey";
+//
+//    public static final String PRIVATE_KEY = "RSAPrivateKey";
+
+    private static final String SRC = "123456";
+    /**
+     * 公钥
+     */
+    public static final String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCOlrKBglhJkU/bfgEEv7zuo4y5RiScg3XeDTBrHHePoixDOvyvBsiAc8b33bjin9j77EzRusJT2jjJV1hnHIbu5HkMd3T5JK00gMviCiSbfCkS4/tf3CnFMWLn31YcBgdCOFoiKh/JpGEvTx192LWSKNupDkR6bgpcRvWr5Yw2swIDAQAB";
+    /**
+     * 私钥  私钥不需要在特殊处理在java中直接调用即可
+     */
+    public static final String PRIVATE_KEY = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAI6WsoGCWEmRT9t+AQS/vO6jjLlGJJyDdd4NMGscd4+iLEM6/K8GyIBzxvfduOKf2PvsTNG6wlPaOMlXWGcchu7keQx3dPkkrTSAy+IKJJt8KRLj+1/cKcUxYuffVhwGB0I4WiIqH8mkYS9PHX3YtZIo26kORHpuClxG9avljDazAgMBAAECgYA3mZWjoILytOHx0bFt+6IdX9LYz/wot643VudBbQlGDdO6p36udTOruvmj32ZfoDsJCPxvb6ak8dvgztle46XNW4tXRtjKdS9skyA4yDRHXWDZjeKpBeNc3vVl1xpf6zWoVgSsD75JMeNLPWb8+nayInDaU8kM7YZmK/MdC2reCQJBAOi1qDH7eiRgLPjR201GscRRLsFupNrc7s+eX+huKhVJi7DiSQ2ukKry6twvZI9b1O3rVVDQBaFvSJyXTl4ojMUCQQCc3ARKcVGU4h0nVr1+qCmsipNvjY2Hl6QuMcFPbfNOlIuRIANNFZUDrxHsVMkUvcdv0mWVxXi12j2qdLb19F0XAkEAw5tuthTcppbxNnWwEVTLOGnFE3Mdv5rWYk6N76IqXZpkgVq0bXu+vvNR16M+tAJNCXA3VqaFFR2lu3qztRIAwQJAer5D8Ui5MQq7C3R5tem7KpQJiOo4jJjh1XADt7bvBFeC2x401xYDVC2jlc5Gxx82N89oxIkQyySqyn6oSNBeIwJBAIyLnhPkRSSS8zQl6OuRzzdIulZFzmGqb+v9ocwoJzQz4dv795G7/61rT2pdp/1bcsXfu/vWQaiHQcuaBULKSmU=";
+
+    public static void main(String[] args) throws Exception {
+        System.out.println("\n");
+        RsaKeyPair keyPair = generateKeyPair();
+        System.out.println("公钥:" + keyPair.getPublicKey());
+        System.out.println("私钥:" + keyPair.getPrivateKey());
+        System.out.println("\n");
+        test1(keyPair);
+        System.out.println("\n");
+        test2(keyPair);
+        System.out.println("\n");
+    }
+
+    /**
+     * 公钥加密私钥解密
+     */
+    private static void test1(RsaKeyPair keyPair) throws Exception {
+        System.out.println("***************** 公钥加密私钥解密开始 *****************");
+//        String text1 = encryptByPublicKey(keyPair.getPublicKey(), RsaUtils.SRC);
+//        String text2 = decryptByPrivateKey(keyPair.getPrivateKey(), text1);
+        String text1 = encryptByPublicKey(RsaUtils.PUBLIC_KEY, RsaUtils.SRC);
+        String text2 = decryptByPrivateKey(RsaUtils.PRIVATE_KEY, text1);
+        System.out.println("加密前:" + RsaUtils.SRC);
+        System.out.println("加密后:" + text1);
+        System.out.println("解密后:" + text2);
+        if (RsaUtils.SRC.equals(text2)) {
+            System.out.println("解密字符串和原始字符串一致,解密成功");
+        } else {
+            System.out.println("解密字符串和原始字符串不一致,解密失败");
+        }
+        System.out.println("***************** 公钥加密私钥解密结束 *****************");
+    }
+
+    /**
+     * 私钥加密公钥解密
+     * @throws Exception /
+     */
+    private static void test2(RsaKeyPair keyPair) throws Exception {
+        System.out.println("***************** 私钥加密公钥解密开始 *****************");
+        String text1 = encryptByPrivateKey(keyPair.getPrivateKey(), RsaUtils.SRC);
+        String text2 = decryptByPublicKey(keyPair.getPublicKey(), text1);
+        System.out.println("加密前:" + RsaUtils.SRC);
+        System.out.println("加密后:" + text1);
+        System.out.println("解密后:" + text2);
+        if (RsaUtils.SRC.equals(text2)) {
+            System.out.println("解密字符串和原始字符串一致,解密成功");
+        } else {
+            System.out.println("解密字符串和原始字符串不一致,解密失败");
+        }
+        System.out.println("***************** 私钥加密公钥解密结束 *****************");
+    }
+
+    /**
+     * 公钥解密
+     * @from fhadmin.cn
+     * @param publicKeyText 公钥
+     * @param text 待解密的信息
+     * @return /
+     * @throws Exception /
+     */
+    public static String decryptByPublicKey(String publicKeyText, String text) throws Exception {
+        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText));
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+        PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
+        Cipher cipher = Cipher.getInstance("RSA");
+        cipher.init(Cipher.DECRYPT_MODE, publicKey);
+        byte[] result = cipher.doFinal(Base64.decodeBase64(text));
+        return new String(result);
+    }
+
+    /**
+     * 私钥加密
+     * @from fhadmin.cn
+     * @param privateKeyText 私钥
+     * @param text 待加密的信息
+     * @return /
+     * @throws Exception /
+     */
+    public static String encryptByPrivateKey(String privateKeyText, String text) throws Exception {
+        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
+        Cipher cipher = Cipher.getInstance("RSA");
+        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
+        byte[] result = cipher.doFinal(text.getBytes());
+        return Base64.encodeBase64String(result);
+    }
+
+    /**
+     * 私钥解密
+     * @from fhadmin.cn
+     * @param privateKeyText 私钥
+     * @param text 待解密的文本
+     * @return /
+     * @throws Exception /
+     */
+    public static String decryptByPrivateKey(String privateKeyText, String text) throws Exception {
+        PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5);
+        Cipher cipher = Cipher.getInstance("RSA");
+        cipher.init(Cipher.DECRYPT_MODE, privateKey);
+        byte[] result = cipher.doFinal(Base64.decodeBase64(text));
+        return new String(result);
+    }
+
+    /**
+     * 公钥加密
+     * @from fhadmin.cn
+     * @param publicKeyText 公钥
+     * @param text 待加密的文本
+     * @return /
+     */
+    public static String encryptByPublicKey(String publicKeyText, String text) throws Exception {
+        X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText));
+        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+        PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec2);
+        Cipher cipher = Cipher.getInstance("RSA");
+        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+        byte[] result = cipher.doFinal(text.getBytes());
+        return Base64.encodeBase64String(result);
+    }
+
+    /**
+     * 构建RSA密钥对
+     * @from fhadmin.cn
+     * @return /
+     * @throws NoSuchAlgorithmException /
+     */
+    public static RsaKeyPair generateKeyPair() throws NoSuchAlgorithmException {
+        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+        keyPairGenerator.initialize(1024);
+        KeyPair keyPair = keyPairGenerator.generateKeyPair();
+        RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
+        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
+        String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded());
+        String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded());
+        RsaKeyPair pair = new RsaKeyPair(publicKeyString, privateKeyString);
+        return pair;
+    }
+
+
+    /**
+     * RSA密钥对对象
+     */
+    public static class RsaKeyPair {
+
+        private final String publicKey;
+        private final String privateKey;
+
+        public RsaKeyPair(String publicKey, String privateKey) {
+            this.publicKey = publicKey;
+            this.privateKey = privateKey;
+        }
+
+        public String getPublicKey() {
+            return publicKey;
+        }
+
+        public String getPrivateKey() {
+            return privateKey;
+        }
+
+    }
+}

+ 106 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/SnowFlake.java

@@ -0,0 +1,106 @@
+package com.storlead.framework.common.util;
+
+/**
+ * Twitter的分布式自增ID雪花算法snowflake
+ * @author MENG
+ * @create 2018-08-23 10:21
+ **/
+public class SnowFlake {
+
+    /**
+     * 起始的时间戳
+     */
+    private final static long START_STMP = 1480166465631L;
+
+    /**
+     * 每一部分占用的位数
+     */
+    private final static long SEQUENCE_BIT = 12; //序列号占用的位数
+    private final static long MACHINE_BIT = 5;   //机器标识占用的位数
+    private final static long DATACENTER_BIT = 5;//数据中心占用的位数
+
+    /**
+     * 每一部分的最大值
+     */
+    private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);
+    private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
+    private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
+
+    /**
+     * 每一部分向左的位移
+     */
+    private final static long MACHINE_LEFT = SEQUENCE_BIT;
+    private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
+    private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;
+
+    private long datacenterId;  //数据中心
+    private long machineId;     //机器标识
+    private long sequence = 0L; //序列号
+    private long lastStmp = -1L;//上一次时间戳
+
+    public SnowFlake(long datacenterId, long machineId) {
+        if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
+            throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
+        }
+        if (machineId > MAX_MACHINE_NUM || machineId < 0) {
+            throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
+        }
+        this.datacenterId = datacenterId;
+        this.machineId = machineId;
+    }
+
+    /**
+     * 产生下一个ID
+     *
+     * @return
+     */
+    public synchronized long nextId() {
+        long currStmp = getNewstmp();
+        if (currStmp < lastStmp) {
+            throw new RuntimeException("Clock moved backwards.  Refusing to generate id");
+        }
+
+        if (currStmp == lastStmp) {
+            //相同毫秒内,序列号自增
+            sequence = (sequence + 1) & MAX_SEQUENCE;
+            //同一毫秒的序列数已经达到最大
+            if (sequence == 0L) {
+                currStmp = getNextMill();
+            }
+        } else {
+            //不同毫秒内,序列号置为0
+            sequence = 0L;
+        }
+
+        lastStmp = currStmp;
+
+        return (currStmp - START_STMP) << TIMESTMP_LEFT //时间戳部分
+                | datacenterId << DATACENTER_LEFT       //数据中心部分
+                | machineId << MACHINE_LEFT             //机器标识部分
+                | sequence;                             //序列号部分
+    }
+
+    private long getNextMill() {
+        long mill = getNewstmp();
+        while (mill <= lastStmp) {
+            mill = getNewstmp();
+        }
+        return mill;
+    }
+
+    private long getNewstmp() {
+        return System.currentTimeMillis();
+    }
+
+    public static void main(String[] args) {
+        SnowFlake snowFlake = new SnowFlake(1, 1);
+        long start = System.currentTimeMillis();
+        for (int i = 0; i < 1000; i++) {
+            System.out.println(snowFlake.nextId());
+        }
+
+        System.out.println(System.currentTimeMillis() - start);
+
+
+    }
+}

+ 95 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/SpringContextUtils.java

@@ -0,0 +1,95 @@
+package com.storlead.framework.common.util;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+
+@Component
+public class SpringContextUtils implements ApplicationContextAware {
+
+	/**
+	 * 上下文对象实例
+	 */
+	private static ApplicationContext applicationContext;
+
+	@Override
+	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+		SpringContextUtils.applicationContext = applicationContext;
+	}
+
+	/**
+	 * 获取applicationContext
+	 *
+	 * @return
+	 */
+	public static ApplicationContext getApplicationContext() {
+		return applicationContext;
+	}
+
+	/**
+	  * 获取HttpServletRequest
+	 */
+	public static HttpServletRequest getHttpServletRequest() {
+		return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
+	}
+
+	public static String getDomain(){
+		HttpServletRequest request = getHttpServletRequest();
+		StringBuffer url = request.getRequestURL();
+		return url.delete(url.length() - request.getRequestURI().length(), url.length()).toString();
+	}
+
+	public static String getOrigin(){
+		HttpServletRequest request = getHttpServletRequest();
+		return request.getHeader("Origin");
+	}
+
+	/**
+	 * 通过name获取 Bean.
+	 *
+	 * @param name
+	 * @return
+	 */
+	public static Object getBean(String name) {
+		return getApplicationContext().getBean(name);
+	}
+
+	/**
+	 * 通过class获取Bean.
+	 *
+	 * @param clazz
+	 * @param       <T>
+	 * @return
+	 */
+	public static <T> T getBean(Class<T> clazz) {
+		return getApplicationContext().getBean(clazz);
+	}
+
+	/**
+	 * 通过name,以及Clazz返回指定的Bean
+	 *
+	 * @param name
+	 * @param clazz
+	 * @param       <T>
+	 * @return
+	 */
+	public static <T> T getBean(String name, Class<T> clazz) {
+		return getApplicationContext().getBean(name, clazz);
+	}
+
+	/**
+	 * 获取当前环境
+	 *
+	 * @return 对应 profile name [dev,test,prod]
+	 * @author blank
+	 * @since 2018-10-10 上午11:24
+	 */
+	public static String getActiveProfile() {
+		return applicationContext.getEnvironment().getActiveProfiles()[0];
+	}
+}

+ 103 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/SqlInjectionUtil.java

@@ -0,0 +1,103 @@
+package com.storlead.framework.common.util;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * sql注入处理工具类
+ *
+ * @author zhoujf
+ */
+@Slf4j
+public class SqlInjectionUtil {
+	final static String xssStr = "'|and |exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|or |+|,";
+
+	/**
+	 * sql注入过滤处理,遇到注入关键字抛异常
+	 *
+	 * @param value
+	 * @return
+	 */
+	public static void filterContent(String value) {
+		if (value == null || "".equals(value)) {
+			return;
+		}
+		value = value.toLowerCase();// 统一转为小写
+		String[] xssArr = xssStr.split("\\|");
+		for (int i = 0; i < xssArr.length; i++) {
+			if (value.indexOf(xssArr[i]) > -1) {
+				log.error("请注意,值可能存在SQL注入风险!---> {}", value);
+				throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
+			}
+		}
+		return;
+	}
+
+	/**
+	 * sql注入过滤处理,遇到注入关键字抛异常
+	 *
+	 * @param values
+	 * @return
+	 */
+	public static void filterContent(String[] values) {
+		String[] xssArr = xssStr.split("\\|");
+		for (String value : values) {
+			if (value == null || "".equals(value)) {
+				return;
+			}
+			value = value.toLowerCase();// 统一转为小写
+			for (int i = 0; i < xssArr.length; i++) {
+				if (value.indexOf(xssArr[i]) > -1) {
+					log.error("请注意,值可能存在SQL注入风险!---> {}", value);
+					throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
+				}
+			}
+		}
+		return;
+	}
+
+	/**
+	 * @特殊方法(不通用) 仅用于字典条件SQL参数,注入过滤
+	 * @param value
+	 * @return
+	 */
+	@Deprecated
+	public static void specialFilterContent(String value) {
+		String specialXssStr = "exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|+|";
+		String[] xssArr = specialXssStr.split("\\|");
+		if (value == null || "".equals(value)) {
+			return;
+		}
+		value = value.toLowerCase();// 统一转为小写
+		for (int i = 0; i < xssArr.length; i++) {
+			if (value.indexOf(xssArr[i]) > -1) {
+				log.error("请注意,值可能存在SQL注入风险!---> {}", value);
+				throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
+			}
+		}
+		return;
+	}
+
+
+	/**
+	 * @特殊方法(不通用) 仅用于Online报表SQL解析,注入过滤
+	 * @param value
+	 * @return
+	 */
+	@Deprecated
+	public static void specialFilterContentForOnlineReport(String value) {
+		String specialXssStr = "exec |insert |delete |update |drop |chr |mid |master |truncate |char |declare |";
+		String[] xssArr = specialXssStr.split("\\|");
+		if (value == null || "".equals(value)) {
+			return;
+		}
+		value = value.toLowerCase();// 统一转为小写
+		for (int i = 0; i < xssArr.length; i++) {
+			if (value.indexOf(xssArr[i]) > -1) {
+				log.error("请注意,值可能存在SQL注入风险!---> {}", value);
+				throw new RuntimeException("请注意,值可能存在SQL注入风险!--->" + value);
+			}
+		}
+		return;
+	}
+
+}

+ 75 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/StringUtil.java

@@ -0,0 +1,75 @@
+package com.storlead.framework.common.util;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * @program: sp-sales
+ * @description: String工具类
+ * @author: xiaojiawei
+ * @create: 2022-09-29 11:24
+ **/
+public class StringUtil extends StringUtils {
+
+    public static String trimSpace(String str, String space) {
+        if (str.startsWith(space)) {
+            str = str.substring(space.length());
+        }
+        if (str.endsWith(space)) {
+            str = str.substring(0, str.length() - space.length());
+        }
+        return str;
+    }
+
+    public static String removePrefixAndSuffix(String str, String prefix, String suffix) {
+        if (str.startsWith(prefix)) {
+            str = str.substring(prefix.length());
+        }
+        if (str.endsWith(suffix)) {
+            str = str.substring(0, str.length() - suffix.length());
+        }
+        return str;
+    }
+
+    /**
+     * 判断两个字符串是否相等,除去html标签 两端空格和换行
+     * @param s1 s1
+     * @param s2 s2
+     * @return: @return boolean
+     * @author: xiajiawei
+     * @date: 2022-09-29
+     */
+    public static  boolean ignoreHtmlSpaceEnterEquals(String s1,String s2){
+        if(s1==null&&s2==null){
+            return true;
+        }
+        if(s1 == null||s2==null){
+            return false;
+        }
+        s1 = HtmlUtils.userContent(s1)
+                .replace("/r", "")
+                .replace("/n", "")
+                .trim();
+        s2 = HtmlUtils.userContent(s2)
+                .replace("/r", "")
+                .replace("/n", "")
+                .trim();
+        return Objects.equals(s1,s2);
+    }
+
+    public static List<Long> stringToLongList(String commaSeparatedString) {
+        List<Long> longList = new ArrayList<>();
+        if (commaSeparatedString != null && !commaSeparatedString.isEmpty()) {
+            String[] strings = commaSeparatedString.split(",");
+            for (String s : strings) {
+                // 去除字符串两侧的空白字符,并尝试转换为Long
+                Long number = Long.parseLong(s.trim());
+                longList.add(number);
+            }
+        }
+        return longList;
+    }
+}

+ 29 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/SystemUtils.java

@@ -0,0 +1,29 @@
+package com.storlead.framework.common.util;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @program: storlead-storlead-Platform
+ * @description:
+ * @author: chenkq
+ * @create: 2025-06-17 15:47
+ */
+public class SystemUtils {
+    /**
+     * 在filter里输出json格式
+     * @param json  json的字符串
+     * */
+    public static void printJson(HttpServletResponse response, String json){
+        try {
+            response.setContentType("application/json");
+            response.setCharacterEncoding("UTF-8");
+            response.getWriter().print(json);
+            response.getWriter().flush();
+            response.getWriter().close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+    }
+}

+ 219 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/WordUtilsTest.java

@@ -0,0 +1,219 @@
+package com.storlead.framework.common.util;
+
+import freemarker.template.Configuration;
+import freemarker.template.DefaultObjectWrapper;
+import freemarker.template.Template;
+import freemarker.template.Version;
+import org.springframework.core.io.ClassPathResource;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.net.URLEncoder;
+import java.util.List;
+import java.util.Map;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ * @author running
+ * @description
+ * @date 2020/9/22
+ */
+
+
+public class WordUtilsTest {
+
+    private static Configuration configuration = null;
+
+    static {
+        configuration = new Configuration(new Version("2.3.30"));
+        configuration.setDefaultEncoding("utf-8");
+        //以下配置只能在本地使用,linux环境下无法获取地址,注掉
+//        try {
+//            ResourceLoader resourceLoader = new DefaultResourceLoader();
+        //指定模板目录在类路径:WEB-INF/classes
+//            Resource resource = resourceLoader.getResource("/");
+//            File file = resource.getFile();
+        //设置要解析的模板所在的目录,并加载模板文件
+//            configuration.setDirectoryForTemplateLoading(file);
+        ///设置包装器,并将对象包装为数据模型
+        configuration.setObjectWrapper(new DefaultObjectWrapper(new Version("2.3.30")));
+//        } catch (IOException e) {
+//            e.printStackTrace();
+//        }
+    }
+
+    private WordUtilsTest() {
+        throw new AssertionError();
+    }
+
+    /**
+     * 导出单个word
+     *
+     * @param map      数据
+     * @param title    文件名
+     * @param ftlFile  模板文件
+     * @param response 响应
+     */
+    public static void exportWord(Map map, String title, String ftlFile, HttpServletResponse response) {
+
+        File file = null;
+        InputStream fin = null;
+        ServletOutputStream out = null;
+        try {
+            //Template freemarkerTemplate = configuration.getTemplate(ftlFile, "UTF-8");
+            ClassPathResource classPathResource = new ClassPathResource(ftlFile);
+            InputStream inputStream = classPathResource.getInputStream();
+            InputStreamReader reader = new InputStreamReader(inputStream);
+            Template freemarkerTemplate = new Template(ftlFile, reader, configuration);
+            // 调用工具类的createDoc方法生成Word文档
+            String fileName = title + ".doc";
+            file = createDoc(map, freemarkerTemplate, fileName);
+            fin = new FileInputStream(file);
+
+            response.setCharacterEncoding("utf-8");
+            response.setContentType("application/msword");
+            // 设置浏览器以下载的方式处理该文件名
+
+            response.setHeader("Content-Disposition", "attachment;filename="
+                    .concat(String.valueOf(URLEncoder.encode(fileName, "UTF-8"))));
+
+            out = response.getOutputStream();
+            // 缓冲区
+            byte[] buffer = new byte[1024];
+            int bytesToRead = -1;
+            // 通过循环将读入的Word文件的内容输出到浏览器中
+            while ((bytesToRead = fin.read(buffer)) != -1) {
+                out.write(buffer, 0, bytesToRead);
+            }
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                if (fin != null) {
+                    fin.close();
+                }
+                if (out != null) {
+                    out.close();
+                }
+                // 删除临时文件
+                if (file != null) {
+                    file.delete();
+                }
+            } catch (Exception e2) {
+                e2.printStackTrace();
+            }
+        }
+    }
+
+
+    /**
+     * 压缩包方式导出多个word
+     * 由于一次请求浏览器只能响应一次,想导出多个必须打包,亲测for循环导出只能导一个
+     * 如果想做到分别单独下载,那就得用插件啦,这里不提供插件的做法
+     * 思路:生成临时目录-在临时目录生成word-将临时目录打zip包-zip文件下载-删除临时目录和zip包,
+     * 回收系统资源
+     *
+     * @param mapList
+     * @param titleList
+     * @param ftlFile
+     */
+    public static void exportWordBatch(List<Map<String, Object>> mapList, List<String> titleList, String ftlFile,
+                                       HttpServletResponse response, HttpServletRequest request) {
+        File file = null;
+        File zipfile = null;
+        File directory = null;
+        InputStream fin = null;
+        ServletOutputStream out = null;
+        response.setCharacterEncoding("utf-8");
+        response.setContentType("application/octet-stream");
+        response.addHeader("Content-Disposition", "attachment;filename=" + System.currentTimeMillis() + ".zip");
+
+        try {
+            //以此方式,在Linux环境下也可获取到模板文件
+            ClassPathResource classPathResource = new ClassPathResource(ftlFile);
+            InputStream inputStream = classPathResource.getInputStream();
+            InputStreamReader reader = new InputStreamReader(inputStream);
+            Template freemarkerTemplate = new Template(ftlFile, reader, configuration);
+            out = response.getOutputStream();
+            //根据当前时间创建临时目录
+            String path = request.getRealPath("/resources/word/" + System.currentTimeMillis());
+            directory = new File(path);
+            directory.mkdirs();
+            for (int i = 0; i < mapList.size(); i++) {
+                Map<String, Object> map = mapList.get(i);
+                String title = titleList.get(i);
+                // 调用工具类的createDoc方法在临时目录下生成Word文档
+                file = createDoc(map, freemarkerTemplate, directory.getPath() + "/" + title + ".doc");
+            }
+            //压缩目录
+            ZipUtil.createZip(path, path + "zip.zip");
+            //根据路径获取刚生成的zip包文件
+            zipfile = new File(path + "zip.zip");
+            fin = new FileInputStream(zipfile);
+            // 缓冲区
+            byte[] buffer = new byte[1024];
+            int bytesToRead = -1;
+            // 通过循环将读入的Word文件的内容输出到浏览器中
+            while ((bytesToRead = fin.read(buffer)) != -1) {
+                out.write(buffer, 0, bytesToRead);
+            }
+            response.flushBuffer();
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                if (fin != null) {
+                    fin.close();
+                }
+                if (out != null) {
+                    out.close();
+                }
+                if (zipfile != null) {
+                    zipfile.delete();
+                }
+                if (directory != null) {
+                    //递归删除目录及目录下文件
+                    ZipUtil.deleteFile(directory);
+                }
+            } catch (Exception e2) {
+                e2.printStackTrace();
+            }
+
+        }
+    }
+
+    //生成word文档方法
+    private static File createDoc(Map<?, ?> dataMap, Template template, String filename) {
+
+        File f = new File(filename);
+        Template t = template;
+        Writer w = null;
+        FileOutputStream fos = null;
+        try {
+            // 这个地方不能使用FileWriter因为需要指定编码类型否则生成的Word文档会因为有无法识别的编码而无法打开
+            fos = new FileOutputStream(f);
+            w = new OutputStreamWriter(fos, UTF_8);
+            //不要偷懒写成下面酱紫: 否则无法关闭fos流,打zip包时存取被拒抛异常
+            //w = new OutputStreamWriter(new FileOutputStream(f), "utf-8");
+            t.process(dataMap, w);
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            throw new RuntimeException(ex);
+        } finally {
+            try {
+                fos.close();
+                w.close();
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+
+        }
+        return f;
+    }
+
+}
+

+ 211 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/ZipUtil.java

@@ -0,0 +1,211 @@
+package com.storlead.framework.common.util;
+
+import java.io.*;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * @author running
+ * @description
+ * @date 2020/9/22
+ */
+public class ZipUtil {
+
+    public static void zip(String inputFileName, String zipFileName)
+            throws Exception {
+        zip(zipFileName, new File(inputFileName));
+    }
+
+    private static void zip(String zipFileName, File inputFile)
+            throws Exception {
+        ZipOutputStream out = new ZipOutputStream(new FileOutputStream(
+                zipFileName));
+        zip(out, inputFile, "");
+        System.out.println("zip done");
+        out.close();
+    }
+
+    private static void zip(ZipOutputStream out, File f, String base)
+            throws Exception {
+        if (f.isDirectory()) {
+            File[] fl = f.listFiles();
+            out.putNextEntry(new ZipEntry(base + "/"));
+            base = base.length() == 0 ? "" : base + "/";
+            for (int i = 0; i < fl.length; i++) {
+                zip(out, fl[i], base + fl[i].getName());
+            }
+        } else {
+            out.putNextEntry(new ZipEntry(base));
+            FileInputStream in = new FileInputStream(f);
+            int b;
+            //System.out.println(base);
+            while ((b = in.read()) != -1) {
+                out.write(b);
+            }
+            in.close();
+        }
+    }
+
+    /**
+     * 创建ZIP文件
+     *
+     * @param sourcePath 文件或文件夹路径
+     * @param zipPath    生成的zip文件存在路径(包括文件名)
+     */
+    public static void createZip(String sourcePath, String zipPath) {
+        FileOutputStream fos = null;
+        ZipOutputStream zos = null;
+        try {
+            fos = new FileOutputStream(zipPath);
+            zos = new ZipOutputStream(fos);
+            writeZip(new File(sourcePath), "", zos);
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                if (zos != null) {
+                    zos.close();
+                    //压缩成功后,删除打包前的文件
+                    deleteFile(new File(sourcePath));
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private static void writeZip(File file, String parentPath,
+                                 ZipOutputStream zos) {
+        if (file.exists()) {
+            // 处理文件夹
+            if (file.isDirectory()) {
+                parentPath += file.getName() + File.separator;
+                File[] files = file.listFiles();
+                for (File f : files) {
+                    writeZip(f, parentPath, zos);
+                }
+            } else {
+                FileInputStream fis = null;
+                try {
+                    fis = new FileInputStream(file);
+                    ZipEntry ze = new ZipEntry(parentPath + file.getName());
+                    zos.putNextEntry(ze);
+                    byte[] content = new byte[1024];
+                    int len;
+                    while ((len = fis.read(content)) != -1) {
+                        zos.write(content, 0, len);
+                        zos.flush();
+                    }
+                } catch (IOException e) {
+                    e.printStackTrace();
+                } finally {
+                    try {
+                        if (fis != null) {
+                            fis.close();
+                        }
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+    }
+
+    public static void copyResource(List<String> oldResPath, String newResPath) {
+        for (int m = 0; m < oldResPath.size(); m++) {
+            try {
+                // 如果文件夹不存在 则建立新文件夹
+                (new File(newResPath)).mkdirs();
+                File a = new File(oldResPath.get(m));
+                // 如果已经是具体文件,读取
+                if (a.isFile()) {
+                    FileInputStream input = new FileInputStream(a);
+                    FileOutputStream output = new FileOutputStream(newResPath + "/" + (a.getName()).toString());
+                    byte[] b = new byte[1024 * 4];
+                    int len;
+                    while ((len = input.read(b)) != -1) {
+                        output.write(b, 0, len);
+                    }
+                    output.flush();
+                    output.close();
+                    input.close();
+                    // 如果文件夹下还存在文件,遍历,直到得到具体的文件
+                } else {
+                    String[] file = a.list();
+                    File temp = null;
+                    for (int i = 0; i < file.length; i++) {
+                        if (oldResPath.get(m).endsWith(File.separator)) {
+                            temp = new File(oldResPath.get(m) + file[i]);
+                        } else {
+                            temp = new File(oldResPath.get(m) + File.separator + file[i]);
+                        }
+
+                        if (temp.isFile()) {
+                            FileInputStream input = new FileInputStream(temp);
+                            FileOutputStream output = new FileOutputStream(newResPath + "/" + (temp.getName()).toString());
+                            byte[] b = new byte[1024 * 4];
+                            int len;
+                            while ((len = input.read(b)) != -1) {
+                                output.write(b, 0, len);
+                            }
+                            output.flush();
+                            output.close();
+                            input.close();
+                        }
+                        if (temp.isDirectory()) {
+                            List<String> oldChildPath = new ArrayList<String>();
+                            oldChildPath.add(oldResPath.get(m) + "/" + file[i]);
+                            newResPath = newResPath + "/" + file[i];
+                            // 如果是子文件夹 递归循环
+                            copyResource(oldChildPath, newResPath);
+                        }
+                    }
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * 删除文件夹
+     *
+     * @param file
+     */
+    public static void deleteFile(File file) {
+        // 判断文件是否存在
+        if (file.exists()) {
+            // 判断是否是文件
+            if (file.isFile()) {
+                file.delete();
+            } else if (file.isDirectory()) {
+                // 否则如果它是一个目录
+                // 声明目录下所有的文件 files[];
+                File files[] = file.listFiles();
+                // 遍历目录下所有的文件
+                for (int i = 0; i < files.length; i++) {
+                    // 把每个文件 用这个方法进行迭代
+                    deleteFile(files[i]);
+                }
+            }
+            file.delete();
+        }
+    }
+
+    /**
+     * 时间格式化
+     *
+     * @return
+     */
+    public static String dateToString() {
+        Date d = new Date();
+        SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss");
+        String time = formatter.format(d);
+        return time;
+    }
+}
+

+ 87 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/encryptor/AccessKeyEncryptor.java

@@ -0,0 +1,87 @@
+package com.storlead.framework.common.util.encryptor;
+
+/**
+ * @program: sp-salary-system
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-27 18:41
+ */
+import cn.hutool.core.util.StrUtil;
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.util.Base64;
+
+public class AccessKeyEncryptor {
+
+    private static final String ALGORITHM = "AES";
+    private static final int KEY_SIZE = 128;
+
+    private SecretKey secretKey;
+
+    public AccessKeyEncryptor(String secret) {
+        if (StrUtil.isBlank(secret)) {
+            secret = "-storlead$209912";
+        }
+        this.secretKey = new SecretKeySpec(secret.getBytes(), ALGORITHM);
+    }
+
+    public static AccessKeyEncryptor getAccessKeyEncryptor(String secret) {
+        return new AccessKeyEncryptor(secret);
+    }
+
+    /**
+     * 加密
+     * @param accessKey
+     * @return
+     * @throws Exception
+     */
+    public String encrypt(String accessKey) {
+        try {
+            Cipher cipher = Cipher.getInstance(ALGORITHM);
+            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
+            byte[] encryptedBytes = cipher.doFinal(accessKey.getBytes());
+            return Base64.getEncoder().encodeToString(encryptedBytes);
+        }catch (Exception e) {
+            return "";
+        }
+    }
+
+    /**
+     * 解密
+     * @param encryptedAccessKey
+     * @return
+     * @throws Exception
+     */
+    public String decrypt(String encryptedAccessKey) {
+        try {
+            Cipher cipher = Cipher.getInstance(ALGORITHM);
+            cipher.init(Cipher.DECRYPT_MODE, secretKey);
+            byte[] decodedBytes = Base64.getDecoder().decode(encryptedAccessKey);
+            byte[] decryptedBytes = cipher.doFinal(decodedBytes);
+            return new String(decryptedBytes);
+        }catch (Exception e) {
+            return encryptedAccessKey;
+        }
+    }
+
+
+    public static void main(String[] args) {
+        try {
+            String secret = "";
+            AccessKeyEncryptor encryptor = new AccessKeyEncryptor(secret);
+
+            String accessKey = "-Chenkq1992";
+            System.out.println("Original AccessKey: " + accessKey);
+
+            String encryptedAccessKey = encryptor.encrypt(accessKey);
+            System.out.println("Encrypted AccessKey: " + encryptedAccessKey);
+
+            String decryptedAccessKey = encryptor.decrypt(encryptedAccessKey);
+            System.out.println("Decrypted AccessKey: " + decryptedAccessKey);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+}

+ 78 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/generate/CodeGenerate.java

@@ -0,0 +1,78 @@
+package com.storlead.framework.common.util.generate;
+
+import cn.hutool.core.util.StrUtil;
+import com.storlead.framework.common.constant.CodeGenerateInterface;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * @program: sp-project-system
+ * @description:
+ * @author: chenkq
+ * @create: 2021-09-11 11:13
+ */
+public class CodeGenerate {
+
+
+    public static void main(String[] args) {
+        for (int i = 0; i < 200; i++) {
+            System.out.println(CodeGenerate.generate10Code(CodeGenerateInterface.ORDER_COST_CODE));
+        }
+    }
+
+//    public static String Generate8Code(String moduleKey) {
+//        String id = IdWorker.nextId().toString();
+//        System.out.println(id);
+//        String code = StrUtil.sub(id, 8, id.length());
+//        System.out.println(code);
+//        return moduleKey + code;
+//    }
+//
+//    public static String Generate12Code(String moduleKey) {
+//        String id = IdWorker.nextId().toString();
+//        System.out.println(id);
+//        String code = StrUtil.sub(id, id.length()-12, id.length());
+//        System.out.println(code);
+//        return moduleKey + code;
+//    }
+
+    public static String GenerateFullCode() {
+        String code = IdWorker.nextId().toString();
+        return code;
+    }
+
+    /**
+     * 生成模块对于的Code
+     *
+     * @param moduleKey  模块Key
+     * @return encodeStr 生成的唯一的Code
+     * @throws Exception
+     */
+    public static String generate10Code(String moduleKey) {
+        String id = IdWorker.nextId().toString();
+        String code = StrUtil.sub(id, id.length() - 6, id.length());
+        SimpleDateFormat sdf1 = new SimpleDateFormat("yyMM");
+        String time = sdf1.format(new Date(System.currentTimeMillis()));
+        String encodeStr = CodeGenerateInterface.CODE_KEY + moduleKey + time + code;
+        return encodeStr;
+    }
+
+    public static String generate14Code(String moduleKey) {
+        String id = IdWorker.nextId().toString();
+        String code = StrUtil.sub(id, id.length() - 10, id.length());
+        SimpleDateFormat sdf1 = new SimpleDateFormat("yyMM");
+        String time = sdf1.format(new Date(System.currentTimeMillis()));
+        String encodeStr = CodeGenerateInterface.CODE_KEY + moduleKey + time + code;
+        return encodeStr;
+    }
+
+    public static String generate12Code(String moduleKey) {
+        String id = IdWorker.nextId().toString();
+        String code = StrUtil.sub(id, id.length() - 8, id.length());
+        SimpleDateFormat sdf1 = new SimpleDateFormat("yyMM");
+        String time = sdf1.format(new Date(System.currentTimeMillis()));
+        String encodeStr = CodeGenerateInterface.CODE_KEY + moduleKey + time + code;
+        return encodeStr;
+    }
+}

+ 29 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/generate/DefaultIdentifierGenerator.java

@@ -0,0 +1,29 @@
+package com.storlead.framework.common.util.generate;
+
+/**
+ * @program: sp-project-system
+ * @description:
+ * @author: chenkq
+ * @create: 2021-09-11 14:18
+ */
+public class DefaultIdentifierGenerator implements IdentifierGenerator {
+
+    private final Sequence sequence;
+
+    public DefaultIdentifierGenerator() {
+        this.sequence = new Sequence();
+    }
+
+    public DefaultIdentifierGenerator(long workerId, long dataCenterId) {
+        this.sequence = new Sequence(workerId, dataCenterId);
+    }
+
+    public DefaultIdentifierGenerator(Sequence sequence) {
+        this.sequence = sequence;
+    }
+
+    @Override
+    public Long nextId() {
+        return this.sequence.nextId();
+    }
+}

+ 36 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/generate/IdWorker.java

@@ -0,0 +1,36 @@
+package com.storlead.framework.common.util.generate;
+
+import java.time.format.DateTimeFormatter;
+import java.util.UUID;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * @program: sp-project-system
+ * @description: 编码生成
+ * @author: chenkq
+ * @create: 2021-09-11 09:30
+ */
+public class IdWorker {
+    private static IdentifierGenerator IDENTIFIER_GENERATOR = new DefaultIdentifierGenerator();
+    public static final DateTimeFormatter MILLISECOND = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
+
+    public IdWorker() {
+    }
+
+    public static void initSequence(long workerId, long dataCenterId) {
+        IDENTIFIER_GENERATOR = new DefaultIdentifierGenerator(workerId, dataCenterId);
+    }
+
+    public static void setIdentifierGenerator(IdentifierGenerator identifierGenerator) {
+        IDENTIFIER_GENERATOR = identifierGenerator;
+    }
+
+    public static String get32UUID() {
+        ThreadLocalRandom random = ThreadLocalRandom.current();
+        return (new UUID(random.nextLong(), random.nextLong())).toString().replace("-", "");
+    }
+
+    public static Long nextId() {
+        return IDENTIFIER_GENERATOR.nextId();
+    }
+}

+ 10 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/generate/IdentifierGenerator.java

@@ -0,0 +1,10 @@
+package com.storlead.framework.common.util.generate;
+
+public interface IdentifierGenerator {
+
+    Long nextId();
+
+    default String nextUUID() {
+        return IdWorker.get32UUID();
+    }
+}

+ 126 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/generate/Sequence.java

@@ -0,0 +1,126 @@
+package com.storlead.framework.common.util.generate;
+
+import cn.hutool.core.date.SystemClock;
+import cn.hutool.core.lang.Assert;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.management.ManagementFactory;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * @program: sp-project-system
+ * @description: 雪花算法工具类
+ * @author: chenkq
+ * @create: 2021-09-11 14:13
+ */
+public class Sequence {
+
+    private static final Logger logger = LoggerFactory.getLogger(Sequence.class);
+    private final long twepoch = 1288834974657L;
+    private final long workerIdBits = 5L;
+    private final long datacenterIdBits = 5L;
+    private final long maxWorkerId = 31L;
+    private final long maxDatacenterId = 31L;
+    private final long sequenceBits = 12L;
+    private final long workerIdShift = 12L;
+    private final long datacenterIdShift = 17L;
+    private final long timestampLeftShift = 22L;
+    private final long sequenceMask = 4095L;
+    private final long workerId;
+    private final long datacenterId;
+    private long sequence = 0L;
+    private long lastTimestamp = -1L;
+
+    public Sequence() {
+        this.datacenterId = getDatacenterId(31L);
+        this.workerId = getMaxWorkerId(this.datacenterId, 31L);
+    }
+
+    public Sequence(long workerId, long datacenterId) {
+        Assert.isFalse(workerId > 31L || workerId < 0L, String.format("worker Id can't be greater than %d or less than 0", 31L), new Object[0]);
+        Assert.isFalse(datacenterId > 31L || datacenterId < 0L, String.format("datacenter Id can't be greater than %d or less than 0", 31L), new Object[0]);
+        this.workerId = workerId;
+        this.datacenterId = datacenterId;
+    }
+
+    protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
+        StringBuilder mpid = new StringBuilder();
+        mpid.append(datacenterId);
+        String name = ManagementFactory.getRuntimeMXBean().getName();
+        if (StringUtils.isNotBlank(name)) {
+            mpid.append(name.split("@")[0]);
+        }
+
+        return (long)(mpid.toString().hashCode() & '\uffff') % (maxWorkerId + 1L);
+    }
+
+    protected static long getDatacenterId(long maxDatacenterId) {
+        long id = 0L;
+
+        try {
+            InetAddress ip = InetAddress.getLocalHost();
+            NetworkInterface network = NetworkInterface.getByInetAddress(ip);
+            if (network == null) {
+                id = 1L;
+            } else {
+                byte[] mac = network.getHardwareAddress();
+                if (null != mac) {
+                    id = (255L & (long)mac[mac.length - 2] | 65280L & (long)mac[mac.length - 1] << 8) >> 6;
+                    id %= maxDatacenterId + 1L;
+                }
+            }
+        } catch (Exception var7) {
+            logger.warn(" getDatacenterId: " + var7.getMessage());
+        }
+
+        return id;
+    }
+
+    public synchronized long nextId() {
+        long timestamp = this.timeGen();
+        if (timestamp < this.lastTimestamp) {
+            long offset = this.lastTimestamp - timestamp;
+            if (offset > 5L) {
+                throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", offset));
+            }
+
+            try {
+                this.wait(offset << 1);
+                timestamp = this.timeGen();
+                if (timestamp < this.lastTimestamp) {
+                    throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", offset));
+                }
+            } catch (Exception var6) {
+                throw new RuntimeException(var6);
+            }
+        }
+
+        if (this.lastTimestamp == timestamp) {
+            this.sequence = this.sequence + 1L & 4095L;
+            if (this.sequence == 0L) {
+                timestamp = this.tilNextMillis(this.lastTimestamp);
+            }
+        } else {
+            this.sequence = ThreadLocalRandom.current().nextLong(1L, 3L);
+        }
+
+        this.lastTimestamp = timestamp;
+        return timestamp - 1288834974657L << 22 | this.datacenterId << 17 | this.workerId << 12 | this.sequence;
+    }
+
+    protected long tilNextMillis(long lastTimestamp) {
+        long timestamp;
+        for(timestamp = this.timeGen(); timestamp <= lastTimestamp; timestamp = this.timeGen()) {
+        }
+
+        return timestamp;
+    }
+
+    protected long timeGen() {
+        return SystemClock.now();
+    }
+}

+ 20 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/validation/NeContains.java

@@ -0,0 +1,20 @@
+package com.storlead.framework.common.validation;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+import java.lang.annotation.*;
+
+@Constraint(validatedBy = NotContainsValidator.class) // 绑定校验器
+@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface  NeContains {
+    String message() default "邮箱格式不正确";
+
+    // 用来指定不能包含的字符串
+    String value();
+
+    Class<?>[] groups() default {};
+
+    Class<? extends Payload>[] payload() default {};
+}

+ 31 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/validation/NeContainsValidator.java

@@ -0,0 +1,31 @@
+package com.storlead.framework.common.validation;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-12-11 11:04
+ */
+public class NeContainsValidator implements ConstraintValidator<NeContains, String> {
+
+    private String forbiddenString;
+
+    @Override
+    public void initialize(NeContains constraintAnnotation) {
+        // 获取注解中定义的不能包含的字符串
+        this.forbiddenString = constraintAnnotation.value();
+    }
+
+    @Override
+    public boolean isValid(String value, ConstraintValidatorContext context) {
+        // 空值直接返回 true,不参与校验
+        if (value == null) {
+            return false;
+        }
+        // 判断字符串是否包含指定的字符串
+        return value.indexOf(forbiddenString) > 0;
+    }
+}

+ 28 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/validation/NotContains.java

@@ -0,0 +1,28 @@
+package com.storlead.framework.common.validation;
+
+import javax.validation.Constraint;
+import javax.validation.Payload;
+
+import java.lang.annotation.*;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-10-17 10:49
+ */
+
+@Constraint(validatedBy = NotContainsValidator.class) // 绑定校验器
+@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface NotContains {
+    String message() default "字符串不能包含指定的子字符串";
+
+    // 用来指定不能包含的字符串
+    String value();
+
+    Class<?>[] groups() default {};
+
+    Class<? extends Payload>[] payload() default {};
+}

+ 31 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/validation/NotContainsValidator.java

@@ -0,0 +1,31 @@
+package com.storlead.framework.common.validation;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-10-17 10:52
+ */
+public class NotContainsValidator implements ConstraintValidator<NotContains, String> {
+
+    private String forbiddenString;
+
+    @Override
+    public void initialize(NotContains constraintAnnotation) {
+        // 获取注解中定义的不能包含的字符串
+        this.forbiddenString = constraintAnnotation.value();
+    }
+
+    @Override
+    public boolean isValid(String value, ConstraintValidatorContext context) {
+        // 空值直接返回 true,不参与校验
+        if (value == null) {
+            return true;
+        }
+        // 判断字符串是否包含指定的字符串
+        return !value.contains(forbiddenString);
+    }
+}

+ 55 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/vo/FeignResult.java

@@ -0,0 +1,55 @@
+package com.storlead.framework.common.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @program: sp-sales
+ * @description:
+ * @author: chenkq
+ * @create: 2024-03-17 11:26
+ */
+
+@Data
+public class FeignResult<T> implements Serializable{
+    private static final long serialVersionUID = 8225645185075835261L;
+
+    /**
+     * 是否处理成功
+     */
+    private boolean isSuccess;
+    /**
+     * 状态码, 默认1000是成功
+     */
+    protected int code;
+    /**
+     * 响应信息, 来说明响应情况
+     */
+    protected String msg;
+    /**
+     * 响应的具体数据
+     */
+    private T data;
+
+    public FeignResult() {
+        this.isSuccess = true;
+        this.code = 200;
+        this.msg = "成功";
+        this.data = null;
+    }
+
+    public FeignResult(int code,String msg, T data) {
+        this.code = code;
+        this.msg = msg;
+        this.data = data;
+    }
+
+    public static <T> FeignResult<T> failure(String msg) {
+        return new FeignResult<>(0,msg, null);
+    }
+
+    public static <T> FeignResult<T> success(String msg) {
+        return new FeignResult<>(0,msg, null);
+    }
+}

+ 119 - 0
storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/vo/UserVo.java

@@ -0,0 +1,119 @@
+package com.storlead.framework.common.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDate;
+import java.util.Date;
+
+/**
+ * @program: sp-sales
+ * @description:
+ * @author: chenkq
+ * @create: 2022-07-06 18:45
+ */
+@Data
+public class UserVo {
+
+    private Long id;
+
+    @ApiModelProperty(value = "用户名")
+    private String username;
+
+    private String password;
+
+    private String xworkUserId;
+
+    @ApiModelProperty(value = "头像")
+    private String avatar;
+
+    @ApiModelProperty(value = "性别")
+    private String sex;
+
+    @ApiModelProperty(value = "昵称")
+    private String nickName;
+
+    @ApiModelProperty(value = "姓名")
+    private String realName;
+
+    @ApiModelProperty(value = "姓名")
+    private String mobile;
+
+    @ApiModelProperty(value = "email")
+    private String email;
+
+    @ApiModelProperty(value = "岗位ID")
+    private Long jobId;
+
+    @ApiModelProperty(value = "部门ID")
+    private Long deptId;
+
+    @ApiModelProperty(value = "分公司ID")
+    private Long subCompanyId;
+
+    @ApiModelProperty(value = "公司ID")
+    private Long companyId;
+
+    @ApiModelProperty(value = "直接上级ID")
+    private Long managerId;
+
+    @ApiModelProperty(value = "领导路由")
+    private String managers;
+
+    private String idNum;
+    private String address;
+
+    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd")
+    @DateTimeFormat(pattern="yyyy-MM-dd")
+    private LocalDate birthday;
+
+    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd")
+    @DateTimeFormat(pattern="yyyy-MM-dd")
+    private LocalDate entryDate;
+
+    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd")
+    @DateTimeFormat(pattern="yyyy-MM-dd")
+    private LocalDate permanentDate;
+
+    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd")
+    @DateTimeFormat(pattern="yyyy-MM-dd")
+    private LocalDate resignDate;
+    private String bankName;
+    private String bankCardNum;
+
+    @ApiModelProperty(value = "状态 0:试用期 1:正式期 2:临时 3:试用延期 5:离职流程")
+    private String status;
+    private Integer isShenzhenCensus;
+    private String census;
+    private String ethnic;
+    private String marriage;
+    private String education;
+    private Integer enabled;
+    private Long createBy;
+    /**创建日期*/
+    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+    private Long updateBy;
+    /**创建日期*/
+    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+
+    @ApiModelProperty(value = "是否负责人:0 否,1分公司负责人,2部门负责人")
+    private Integer isLeader;
+
+    @ApiModelProperty(value = "是否是超管")
+    private Boolean isAdmin;
+
+    @ApiModelProperty(value = "数据权限")
+    private String dataScope;
+
+    @ApiModelProperty(value = "测试")
+    private Integer test;
+
+    @ApiModelProperty(value = "组织路由")
+    private String orgRouteCode;
+}

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