Kaynağa Gözat

集成星火和jenkinsfile

1811872455@163.com 1 ay önce
ebeveyn
işleme
2a109bc89e
31 değiştirilmiş dosya ile 1389 ekleme ve 741 silme
  1. 51 44
      .idea/workspace.xml
  2. 284 0
      Jenkinsfile
  3. 70 0
      storlead-ai-api/src/main/java/com/storlead/ai/config/properties/SparkAiProperties.java
  4. 1 1
      storlead-ai-api/src/main/java/com/storlead/ai/controller/AiChatController.java
  5. 4 0
      storlead-ai-api/src/main/java/com/storlead/ai/core/AiProviderType.java
  6. 7 0
      storlead-ai-api/src/main/java/com/storlead/ai/factory/AiServiceFactoryManager.java
  7. 37 0
      storlead-ai-api/src/main/java/com/storlead/ai/factory/impl/SparkAiServiceFactory.java
  8. 4 4
      storlead-ai-api/src/main/java/com/storlead/ai/service/AiChatService.java
  9. 360 0
      storlead-ai-api/src/main/java/com/storlead/ai/service/impl/SparkAiService.java
  10. 1 2
      storlead-ai-api/src/main/java/com/storlead/ai/service/impl/StorleadAiService.java
  11. 85 27
      storlead-ai-api/src/main/java/com/storlead/ai/websocket/AiChatWebSocketHandler.java
  12. 6 9
      storlead-ai-api/src/main/java/com/storlead/ai/websocket/WebSocketConfig.java
  13. 79 167
      storlead-ai-api/src/main/resources/application-dev.yml
  14. 128 146
      storlead-ai-api/src/main/resources/application-prod.yml
  15. 0 14
      storlead-ai-api/src/main/resources/application.yml
  16. 65 0
      storlead-ai-api/target/classes/META-INF/spring-configuration-metadata.json
  17. 79 167
      storlead-ai-api/target/classes/application-dev.yml
  18. 128 146
      storlead-ai-api/target/classes/application-prod.yml
  19. 0 14
      storlead-ai-api/target/classes/application.yml
  20. BIN
      storlead-ai-api/target/classes/com/storlead/ai/config/properties/SparkAiProperties.class
  21. BIN
      storlead-ai-api/target/classes/com/storlead/ai/config/properties/StorleadAiProperties.class
  22. BIN
      storlead-ai-api/target/classes/com/storlead/ai/controller/AiChatController.class
  23. BIN
      storlead-ai-api/target/classes/com/storlead/ai/core/AiProviderType.class
  24. BIN
      storlead-ai-api/target/classes/com/storlead/ai/factory/AiServiceFactoryManager.class
  25. BIN
      storlead-ai-api/target/classes/com/storlead/ai/factory/impl/SparkAiServiceFactory.class
  26. BIN
      storlead-ai-api/target/classes/com/storlead/ai/factory/impl/StorleadAiServiceFactory.class
  27. BIN
      storlead-ai-api/target/classes/com/storlead/ai/service/AiChatService.class
  28. BIN
      storlead-ai-api/target/classes/com/storlead/ai/service/impl/SparkAiService.class
  29. BIN
      storlead-ai-api/target/classes/com/storlead/ai/service/impl/StorleadAiService.class
  30. BIN
      storlead-ai-api/target/classes/com/storlead/ai/websocket/AiChatWebSocketHandler.class
  31. BIN
      storlead-ai-api/target/classes/com/storlead/ai/websocket/WebSocketConfig.class

+ 51 - 44
.idea/workspace.xml

@@ -5,25 +5,31 @@
   </component>
   <component name="ChangeListManager">
     <list default="true" id="2dc641fc-1465-479e-874d-97069c194ded" name="Changes" comment="ai项目">
-      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/config/properties/StorleadAiProperties.java" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/factory/impl/StorleadAiServiceFactory.java" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/service/impl/StorleadAiService.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/Jenkinsfile" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/config/properties/SparkAiProperties.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/factory/impl/SparkAiServiceFactory.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/service/impl/SparkAiService.java" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/config/properties/OpenAiProperties.java" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/config/properties/OpenAiProperties.java" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/controller/AiChatController.java" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/controller/AiChatController.java" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/core/AiProviderType.java" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/core/AiProviderType.java" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/factory/AiServiceFactoryManager.java" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/factory/AiServiceFactoryManager.java" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/factory/impl/OpenAiServiceFactory.java" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/factory/impl/OpenAiServiceFactory.java" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/service/AiChatService.java" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/service/AiChatService.java" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/service/impl/StorleadAiService.java" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/service/impl/StorleadAiService.java" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/websocket/AiChatWebSocketHandler.java" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/websocket/AiChatWebSocketHandler.java" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/websocket/WebSocketConfig.java" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/websocket/WebSocketConfig.java" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/storlead-ai-api/src/main/resources/application-dev.yml" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/resources/application-dev.yml" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/storlead-ai-api/src/main/resources/application-prod.yml" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/resources/application-prod.yml" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/storlead-ai-api/src/main/resources/application.yml" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/resources/application.yml" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/storlead-ai-api/target/classes/META-INF/spring-configuration-metadata.json" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/target/classes/META-INF/spring-configuration-metadata.json" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/storlead-ai-api/target/classes/application-dev.yml" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/target/classes/application-dev.yml" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/config/properties/OpenAiProperties.class" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/config/properties/OpenAiProperties.class" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/storlead-ai-api/target/classes/application-prod.yml" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/target/classes/application-prod.yml" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/storlead-ai-api/target/classes/application.yml" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/target/classes/application.yml" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/controller/AiChatController.class" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/controller/AiChatController.class" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/core/AiProviderType.class" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/core/AiProviderType.class" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/factory/AiServiceFactoryManager.class" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/factory/AiServiceFactoryManager.class" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/factory/impl/OpenAiServiceFactory.class" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/factory/impl/OpenAiServiceFactory.class" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/service/AiChatService.class" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/service/AiChatService.class" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/websocket/AiChatWebSocketHandler.class" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/websocket/AiChatWebSocketHandler.class" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/websocket/WebSocketConfig.class" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/websocket/WebSocketConfig.class" afterDir="false" />
     </list>
     <option name="SHOW_DIALOG" value="false" />
     <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -56,40 +62,40 @@
   <component name="ProjectViewState">
     <option name="showLibraryContents" value="true" />
   </component>
-  <component name="PropertiesComponent">{
-  &quot;keyToString&quot;: {
-    &quot;RequestMappingsPanelOrder0&quot;: &quot;0&quot;,
-    &quot;RequestMappingsPanelOrder1&quot;: &quot;1&quot;,
-    &quot;RequestMappingsPanelWidth0&quot;: &quot;75&quot;,
-    &quot;RequestMappingsPanelWidth1&quot;: &quot;75&quot;,
-    &quot;RunOnceActivity.CodyAccountHistoryMigration&quot;: &quot;true&quot;,
-    &quot;RunOnceActivity.CodyConvertUrlToCodebaseName&quot;: &quot;true&quot;,
-    &quot;RunOnceActivity.CodyHistoryLlmMigration&quot;: &quot;true&quot;,
-    &quot;RunOnceActivity.CodyProjectSettingsMigration&quot;: &quot;true&quot;,
-    &quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;,
-    &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
-    &quot;WebServerToolWindowFactoryState&quot;: &quot;false&quot;,
-    &quot;jdk.selected.JAVA_MODULE&quot;: &quot;ms-17&quot;,
-    &quot;last_opened_file_path&quot;: &quot;D:/chenkq-work/git/storlead-ai-platform/storlead-ai-api/src/main/resources&quot;,
-    &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
-    &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
-    &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
-    &quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
-    &quot;project.structure.last.edited&quot;: &quot;Modules&quot;,
-    &quot;project.structure.proportion&quot;: &quot;0.0&quot;,
-    &quot;project.structure.side.proportion&quot;: &quot;0.0&quot;,
-    &quot;settings.editor.selected.configurable&quot;: &quot;MavenSettings&quot;,
-    &quot;spring.configuration.checksum&quot;: &quot;1259999022d84b755f5d094d936c15a0&quot;,
-    &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
+  <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",
+    "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>
   <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" />
+      <recent name="D:\chenkq-work\git\storlead-ai-platform\storlead-ai-api\src\main\resources" />
     </key>
   </component>
   <component name="RunManager">
@@ -132,7 +138,8 @@
       <workItem from="1758591153350" duration="8846000" />
       <workItem from="1758780969521" duration="11096000" />
       <workItem from="1758849538252" duration="14257000" />
-      <workItem from="1759021657878" duration="3410000" />
+      <workItem from="1759021657878" duration="17861000" />
+      <workItem from="1759109339436" duration="13889000" />
     </task>
     <task id="LOCAL-00001" summary="ai项目">
       <option name="closed" value="true" />
@@ -142,7 +149,15 @@
       <option name="project" value="LOCAL" />
       <updated>1758276829909</updated>
     </task>
-    <option name="localTasksCounter" value="2" />
+    <task id="LOCAL-00002" summary="ai项目">
+      <option name="closed" value="true" />
+      <created>1759026860684</created>
+      <option name="number" value="00002" />
+      <option name="presentableId" value="LOCAL-00002" />
+      <option name="project" value="LOCAL" />
+      <updated>1759026860684</updated>
+    </task>
+    <option name="localTasksCounter" value="3" />
     <servers />
   </component>
   <component name="TypeScriptGeneratedFilesManager">
@@ -196,14 +211,6 @@
           <line>1</line>
           <option name="timeStamp" value="13" />
         </line-breakpoint>
-        <line-breakpoint enabled="true" type="java-line">
-          <url>file://$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/service/impl/OpenAiService.java</url>
-          <line>64</line>
-          <properties>
-            <option name="lambda-ordinal" value="-1" />
-          </properties>
-          <option name="timeStamp" value="19" />
-        </line-breakpoint>
       </breakpoints>
     </breakpoint-manager>
   </component>

+ 284 - 0
Jenkinsfile

@@ -0,0 +1,284 @@
+properties([
+    parameters([
+        [$class: 'ChoiceParameter',
+            choiceType: 'PT_SINGLE_SELECT',
+            description: '选择要部署 API',
+            name: 'modulePrefix',
+            randomName: 'choice-parameter-5631314439613978',
+            script: [
+                $class: 'GroovyScript',
+                fallbackScript: [
+                    classpath: [],
+                    sandbox: true,
+                    script:
+                        'return[\'Could not get Env\']'
+                ]
+//                ,
+//                script: [
+//                    classpath: [],
+//                    sandbox: true,
+//                    script:
+//                        'return["java"]'
+//                ]
+            ]
+        ],
+        [$class: 'CascadeChoiceParameter',
+            choiceType: 'PT_SINGLE_SELECT',
+            description: '选择要部署的项目',
+            name: 'module',
+            randomName: 'choice-parameter-5631314456178619',
+            referencedParameters: 'modulePrefix',
+            script: [
+                $class: 'GroovyScript',
+                fallbackScript: [
+                    classpath: [],
+                    sandbox: true,
+                    script:
+                        'return[\'Could not get Environment from Env Param\']'
+                ],
+                script: [
+                    classpath: [],
+                    sandbox: true,
+                    script:
+                            'return["storlead-ai-api"]'
+                ]
+            ]
+        ]
+
+
+    ])
+])
+
+pipeline {
+    options {
+        timestamps()
+    }
+    environment {
+         GROUPID = readMavenPom().getGroupId()//com.storlead
+         ARTIFACTID = readMavenPom().getArtifactId()//sp-project
+         VERSION = readMavenPom().getVersion()//1.0
+    }
+   // 参数
+    parameters {
+        choice(
+            name: 'profile',
+            choices: ['prod'],
+            description: '选择要部署的配置文件'
+        )
+    }
+    agent any
+    stages {
+          stage('处理参数') {
+              steps {
+                  script {
+                      echo "处理参数"
+                      script {
+                          //服务器配置
+                          MODULE_PREFIX = "${params.modulePrefix}"
+                          SERVER_SSH_PORT = 53023
+                          SERVER_HOST = "node1.storlead.com"
+                          SWARM_INIT_REPLICAS_NUM = 1
+
+                          //服务配置
+                          API_PORT = 9200
+                          API_REMOTE_DEBUG_PORT = 9201
+                          API_NAMESPACE_RESTFUL = "${params.module}"
+
+                          //DOCKER配置
+                          DOCKER_REGISTRY = "reg-aliyun"
+                          DOCKER_HOST = "registry.cn-shenzhen.aliyuncs.com"
+                          //Docker私服登陆url https://registry-vpc.cn-shenzhen.aliyuncs.com
+                          DOCKER_LOGIN_REGISTRY = "https://${DOCKER_HOST}"
+
+                          BUILD_PREFIX = "storlead-huawei"
+                          //镜像完整名称 registry-vpc.cn-shenzhen.aliyuncs.com/storlead/sp-project-management-prod:1.0
+                          DOCKER_IMG_NAME = "${DOCKER_HOST}/${BUILD_PREFIX}/${ARTIFACTID}-${params.module}-${params.profile}:${VERSION}"
+                          //镜像名 storlead/sp-project-management-prod:1.0
+                          IMG_NAME = "${BUILD_PREFIX}/${ARTIFACTID}-${params.module}-${params.profile}:${VERSION}"
+
+                          PUSH_DOCK_IMG = true
+                          SERVER_HOST_NAME =  "test1"
+                          echo "为指定 module 设定对应 服务器及端口"
+                          echo "处理 ${params.module} 项目"
+                          if (params.modulePrefix == "java") {
+                              echo "---- gpt"
+                              script {
+                                  if (params.profile == "test") {
+                                      SWARM_INIT_REPLICAS_NUM = 1
+                                      SERVER_HOST = "test1.storlead.com"
+                                      API_PORT = 8780
+                                      API_REMOTE_DEBUG_PORT = 8781
+                                      SERVER_HOST_NAME =  "test1"
+                                  } else if (params.profile == "prod") {
+                                      SWARM_INIT_REPLICAS_NUM = 1
+                                      SERVER_HOST = "110.41.82.21"
+                                      API_PORT = 8780
+                                      API_REMOTE_DEBUG_PORT = 8781
+                                      SERVER_HOST_NAME =  "node1"
+                                  }
+                              }
+                          }
+
+                          echo "处理MODULE_NAMESPACE_RESTFUL"
+                          //把 module 名 从 sp-xxx 变成 xxx 也就是 项目 的 namespace
+                          if (API_NAMESPACE_RESTFUL == "storlead-ai") {
+                              API_NAMESPACE_RESTFUL = "/router/rest"
+                          }
+                          echo "处理镜像推送"
+                      }
+                  }
+              }
+          }
+
+          stage('Maven 编译') {
+              when {
+                  environment name: 'modulePrefix', value: 'java'
+              }
+              agent {
+                  docker {
+                      image 'maven:3-amazoncorretto-11'
+                      //让docker 使用 host 宿主机的 m2仓库 使用root用户来运行 以让指定的~/.m2/config/setting.xml 阿里加速下载maven 依赖生效
+                      args '-v /app:/app:z -v $HOME/.m2:/root/.m2:z -u root'
+                      reuseNode true
+                  }
+              }
+              steps {
+                  echo "编译 API 代码"
+                  sh "mvn clean package -U -am -pl ${modulePrefix}/${params.module} -P${params.profile} -Dmaven.test.skip=true"
+              }
+          }
+
+          // 项目打包到镜像并推送到镜像仓库
+          stage('构建Docker镜像') {
+              when {
+                  environment name: 'modulePrefix', value: 'java'
+              }
+              steps {
+                  echo "构建Dcoker镜像-------------------"
+                  script {
+                      docker.withRegistry(DOCKER_LOGIN_REGISTRY, DOCKER_REGISTRY) {
+
+                          BASE_IMAGE = 'eclipse-temurin:11'
+                          //获取最新的镜像底包
+                          sh "docker pull ${BASE_IMAGE}"
+
+                          sh """
+                          echo '
+                          FROM ${BASE_IMAGE}
+
+                          COPY ${modulePrefix}/${params.module}/target/${params.module}.jar app.jar
+                          EXPOSE ${API_PORT}
+                          EXPOSE ${API_REMOTE_DEBUG_PORT}
+                          HEALTHCHECK --interval=1m --timeout=10s CMD curl -f http://localhost:${API_PORT}/${API_NAMESPACE_RESTFUL}/actuator
+                          ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom", "-Djava.awt.headless=true","-Dspring.profiles.active=${params.profile}", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:${API_REMOTE_DEBUG_PORT}","-jar","/app.jar"]' > Dockerfile
+                          docker build -t ${DOCKER_IMG_NAME} .
+                         """
+                          sh "docker push ${DOCKER_IMG_NAME}"
+                      }
+                  }
+              }
+          }
+
+          stage('启动服务') {
+              when {
+                  environment name: 'modulePrefix', value: 'java'
+              }
+              steps {
+                  withCredentials([
+                          usernamePassword(credentialsId: 'storleadHW', usernameVariable: 'usernameHW', passwordVariable: 'passwordHW'),
+                          usernamePassword(credentialsId: 'storlead', usernameVariable: 'username', passwordVariable: 'password'),
+                          usernamePassword(credentialsId: 'reg-aliyun', usernameVariable: 'aliUsername', passwordVariable: 'aliPassword'),
+                  ]) {
+                      script {
+
+                          echo "启动服务"
+
+                          def remote = [:]
+                          remote.name = "${SERVER_HOST}"
+                          remote.host = "${SERVER_HOST}"
+                          remote.port = 53023
+                          remote.user = username
+                          remote.password = password
+                          remote.allowAnyHosts = true
+
+                          //因为使用 docker swarm 发布 只能在 manage node 执行部署
+                          if (params.profile == "dev") {
+                              remote.port = 22
+                              remote.user = "root"
+                          }
+
+                          if (params.profile == "prod") {
+                              remote.port = 22
+                              remote.user = "root"
+                              remote.user = usernameHW
+                              remote.password = passwordHW
+                          }
+
+                          if (PUSH_DOCK_IMG) {
+                              //docker 认证
+                              sshCommand remote: remote, command: "docker login -u ${aliUsername} -p ${aliPassword} ${DOCKER_LOGIN_REGISTRY}"
+                              //如果选择推送才使用远程的版本 如果不选择推送到远程则直接运行本机刚打包的镜像版本即可
+                              sshCommand remote: remote, command: "docker pull ${DOCKER_IMG_NAME}"
+                          }
+                          sh """
+                          echo '
+  version: "3.7"
+  services:
+    ${ARTIFACTID}-${params.module}-${params.profile}:
+      image: ${DOCKER_IMG_NAME}
+      deploy:
+        replicas: ${SWARM_INIT_REPLICAS_NUM}
+        placement:
+          constraints:  
+            - 'node.hostname == ${SERVER_HOST_NAME}'
+        update_config:
+          parallelism: 1
+          delay: 1m
+        restart_policy:
+          condition: on-failure
+          # 最多连续重试3次如果仍然失败则放弃重试
+          max_attempts: 3
+      healthcheck:
+        test: ["CMD", "curl", "-f", "http://localhost:${API_PORT}/router/rest/actuator"]
+        interval: 1m
+        timeout: 5s
+        retries: 3
+        start_period: 30s
+      environment:
+        - "TZ=Asia/Shanghai"
+        - "SPRING_PROFILES_ACTIVE=${params.profile}"
+      volumes:
+        - /app:/app
+      ports:
+        - "${API_PORT}:${API_PORT}"
+        - "${API_REMOTE_DEBUG_PORT}:${API_REMOTE_DEBUG_PORT}"
+      networks:
+        - vonedao_net
+      extra_hosts:
+        - "test1.storlead.com:172.18.194.168"
+        - "node1:110.41.82.21"
+        - "node2:110.41.174.46"
+        - "node3:121.37.226.174"
+  networks:
+    vonedao_net:
+       external: true
+                          ' > ${ARTIFACTID}-${params.module}-${params.profile}-stack.yml
+                         """
+  //                        if (params.profile =="dev") {
+  //                            sh "docker stack deploy -c ${ARTIFACTID}-${params.profile}-stack.yml ${ARTIFACTID}-${params.profile}"
+
+  //                            sh "docker ps -f name=${ARTIFACTID} -q | xargs --no-run-if-empty docker container stop"
+  //                            sh "docker container ls -a -fname=${ARTIFACTID} -q | xargs -r docker container rm"
+  //                            sh "docker run -d --restart=always -p ${API_PORT}:${API_PORT} -p ${API_REMOTE_DEBUG_PORT}:${API_REMOTE_DEBUG_PORT} -v /app:/app --name ${ARTIFACTID} ${DOCKER_IMG_NAME}"
+  //                        } else {
+                          // 全部都以swarm 模式启动
+                          sshPut remote: remote, from: "${ARTIFACTID}-${params.module}-${params.profile}-stack.yml", into: "/docker/sp/${ARTIFACTID}-${params.module}-${params.profile}-stack.yml"
+                          sshCommand remote: remote, command: "docker stack deploy --resolve-image=always --with-registry-auth -c /docker/sp/${ARTIFACTID}-${params.module}-${params.profile}-stack.yml ${params.module}-${params.profile}"
+  //                        }
+                      }
+                  }
+              }
+          }
+
+      }
+  }

+ 70 - 0
storlead-ai-api/src/main/java/com/storlead/ai/config/properties/SparkAiProperties.java

@@ -0,0 +1,70 @@
+package com.storlead.ai.config.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * @program: storlead-ai-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2025-09-28 16:47
+ */
+@Component
+@ConfigurationProperties(prefix = "ai.providers.sparkai")
+@Data
+public class SparkAiProperties {
+    /**
+     * 是否启用OpenAI
+     */
+    private boolean enabled = false;
+
+    /**
+     * API密钥
+     */
+    private String apiKey;
+
+    /**
+     * API基础URL
+     */
+    private String baseUrl = "https://spark-api-open.xf-yun.com";
+
+    /**
+     * 默认模型
+     */
+    private String defaultModel = "v3.5";
+
+    /**
+     * 支持的模型列表
+     */
+    private List<String> supportedModels = List.of(
+
+    );
+
+    /**
+     * 请求超时时间(秒)
+     */
+    private int timeoutSeconds = 30;
+
+    /**
+     * 最大重试次数
+     */
+    private int maxRetries = 3;
+
+    /**
+     * 默认温度参数
+     */
+    private double defaultTemperature = 0.7;
+
+    /**
+     * 默认最大令牌数
+     */
+    private int defaultMaxTokens = 1000;
+
+    /**
+     * 是否启用流式响应
+     */
+    private boolean streamEnabled = true;
+}

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

@@ -45,7 +45,7 @@ public class AiChatController {
     @GetMapping("/chat")
     @Operation(summary = "同步聊天", description = "使用指定的AI提供商进行一次性聊天")
     public Mono<ChatResponse> chat(@RequestBody ChatRequest request) {
-        String provider = "storleadai";
+        String provider = "sparkai";
         return aiChatService.chat(provider, request);
     }
 

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

@@ -14,6 +14,10 @@ public enum AiProviderType {
      */
     OPENAI("openai", "OpenAI"),
 
+    /**
+     * 调休
+     */
+    SPARKAI("sparkai", "SparkAI"),
     /**
      * 百度千帆大模型
      */

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

@@ -35,6 +35,8 @@ public class AiServiceFactoryManager {
     @Resource
     private StorleadAiServiceFactory storeladAiFactory;
 
+    @Resource
+    private SparkAiServiceFactory sparkAiServiceFactory;
     /**
      * 初始化注册所有可用的工厂
      */
@@ -52,6 +54,11 @@ public class AiServiceFactoryManager {
             registerFactory(storeladAiFactory);
         }
 
+        // 注册可用的工厂
+        if (sparkAiServiceFactory != null) {
+            registerFactory(sparkAiServiceFactory);
+        }
+
         logger.info("AI服务工厂管理器初始化完成,已注册 {} 个工厂", factories.size());
         factories.keySet().forEach(type ->
                 logger.info("- 已注册工厂: {}", type.getDisplayName()));

+ 37 - 0
storlead-ai-api/src/main/java/com/storlead/ai/factory/impl/SparkAiServiceFactory.java

@@ -0,0 +1,37 @@
+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.SparkAiService;
+import com.storlead.ai.service.impl.StorleadAiService;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * @program: storlead-ai-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2025-09-26 10:23
+ */
+@Component
+@ConditionalOnProperty(prefix = "ai.providers.sparkai", name = "enabled", havingValue = "true")
+public class SparkAiServiceFactory extends AiServiceFactory {
+
+    @Resource
+    private SparkAiService sparkAiService;
+
+    @Override
+    public AiService createAiService() {
+        return sparkAiService;
+    }
+
+    @Override
+    public AiProviderType getSupportedType() {
+        return AiProviderType.SPARKAI;
+    }
+}
+

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

@@ -97,10 +97,10 @@ public class AiChatService {
                     request.getMessage() != null ? request.getMessage().length() : 0);
 
             AiService aiService = factoryManager.getAiService(providerType);
-            return aiService.chatStream(request)
-                    .doOnNext(chunk -> {
-                        logger.info("{}",chunk);
-                    });
+            return aiService.chatStream(request);
+//                    .doOnNext(chunk -> {
+//                        logger.info("--{}--",chunk);
+//                    });
         } catch (Exception e) {
             logger.error("流式聊天服务异常 - 提供商: {}", providerType.getDisplayName(), e);
             return Flux.error(e);

+ 360 - 0
storlead-ai-api/src/main/java/com/storlead/ai/service/impl/SparkAiService.java

@@ -0,0 +1,360 @@
+package com.storlead.ai.service.impl;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.storlead.ai.config.properties.SparkAiProperties;
+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.exception.AiServiceException;
+import com.storlead.ai.util.HttpClientUtil;
+import org.apache.hc.core5.net.URIBuilder;
+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 javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.net.URI;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * @program: storlead-ai-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2025-09-28 16:46
+ */
+@Service
+@ConditionalOnProperty(prefix = "ai.providers.sparkai", name = "enabled", havingValue = "true")
+public class SparkAiService implements AiService {
+
+    private static final Logger logger = LoggerFactory.getLogger(OpenAiService.class);
+
+    @Autowired
+    private SparkAiProperties 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);
+
+        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;
+    }
+
+    public static String getAuthUrl(String hostUrl, String apiKey, String apiSecret) throws Exception {
+        URL url = new URL(hostUrl);
+        // 时间
+        SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
+        format.setTimeZone(TimeZone.getTimeZone("GMT"));
+        String date = format.format(new Date());
+        // 拼接
+        String preStr = "host: " + url.getHost() + "\n" +
+                "date: " + date + "\n" +
+                "GET " + url.getPath() + " HTTP/1.1";
+        // System.err.println(preStr);
+        // SHA256加密
+        Mac mac = Mac.getInstance("hmacsha256");
+        SecretKeySpec spec = new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), "hmacsha256");
+        mac.init(spec);
+
+        byte[] hexDigits = mac.doFinal(preStr.getBytes(StandardCharsets.UTF_8));
+        // Base64加密
+        String sha = Base64.getEncoder().encodeToString(hexDigits);
+        // System.err.println(sha);
+        // 拼接
+        String authorization = String.format("api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", apiKey, "hmac-sha256", "host date request-line", sha);
+        // 拼接地址
+        URI uri = new URIBuilder()
+                .setScheme("https")
+                .setHost(url.getHost())
+                .setPath(url.getPath())
+                .addParameter("authorization", Base64.getEncoder().encodeToString(authorization.getBytes(StandardCharsets.UTF_8)))
+                .addParameter("date", date)
+                .addParameter("host", url.getHost())
+                .build();
+
+        String finalUrl = uri.toString();
+
+        return finalUrl;
+    }
+
+    /**
+     * 构建消息列表
+     */
+    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.STROLEAAI);
+            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);
+        }
+    }
+}
+

+ 1 - 2
storlead-ai-api/src/main/java/com/storlead/ai/service/impl/StorleadAiService.java

@@ -82,7 +82,6 @@ public class StorleadAiService implements AiService {
         logger.debug("开始构建流式请求");
 
         Map<String, Object> requestBody = buildStreamRequestBody(request);
-        logger.debug("流式请求体: {}", requestBody);
 
         return webClient.post()
                 .uri("/v1/chat/completions")
@@ -262,7 +261,7 @@ public class StorleadAiService implements AiService {
             }
 
             ChatResponse response = ChatResponse.success(content, model);
-            response.setProviderType(AiProviderType.OPENAI);
+            response.setProviderType(AiProviderType.STROLEAAI);
             response.setMetadata(metadata);
 
             return response;

+ 85 - 27
storlead-ai-api/src/main/java/com/storlead/ai/websocket/AiChatWebSocketHandler.java

@@ -55,30 +55,27 @@ public class AiChatWebSocketHandler implements WebSocketHandler {
         logger.info("WebSocket连接建立: {} (用户: {})", sessionId, userId);
 
         // 发送连接成功消息 - 修复方法调用
-        sendMessage(session, WebSocketMessage.systemMessage(Map.of(
-                "event", "connected",
-                "sessionId", sessionId,
-                "userId", userId,
-                "supportedProviders", aiChatService.getSupportedProviders().stream()
-                        .map(type -> Map.of(
-                                "code", type.getCode(),
-                                "name", type.getDisplayName(),
-                                "available", aiChatService.isProviderAvailable(type)
-                        ))
-                        .toList()
-        )));
+        // sendMessage(session, WebSocketMessage.systemMessage(Map.of(
+        //         "event", "connected",
+        //         "sessionId", sessionId,
+        //         "userId", userId,
+        //         "supportedProviders", aiChatService.getSupportedProviders().stream()
+        //                 .map(type -> Map.of(
+        //                         "code", type.getCode(),
+        //                         "name", type.getDisplayName(),
+        //                         "available", aiChatService.isProviderAvailable(type)
+        //                 ))
+        //                 .toList()
+        //        )));
     }
 
     @Override
-    public void handleMessage(WebSocketSession session, org.springframework.web.socket.WebSocketMessage<?> payload) throws Exception {
+    public void handleMessage(WebSocketSession session, org.springframework.web.socket.WebSocketMessage<?> payload) {
         String sessionId = session.getId();
-
         try {
-
             String provider = sessionProvider.get(sessionId);
             String model = sessionModel.get(sessionId);
             String message = payload.getPayload().toString();
-
 //            String messageType = messageNode.get("type").asText();
 //            logger.debug("收到消息: {} - {}", sessionId, messageType);
             handleChatRequest(session, message,provider,model);
@@ -166,7 +163,7 @@ public class AiChatWebSocketHandler implements WebSocketHandler {
             }
 
             // 开始流式聊天
-            startStreamChat(session, sessionId, providerType, chatRequest);
+            startStreamChatNoStruct(session, sessionId, providerType, chatRequest);
 
         } catch (Exception e) {
             logger.error("处理聊天请求失败: {}", sessionId, e);
@@ -180,13 +177,12 @@ public class AiChatWebSocketHandler implements WebSocketHandler {
     private void startStreamChat(WebSocketSession session, String sessionId,
                                  AiProviderType providerType, ChatRequest chatRequest) {
 
-        logger.debug("开始流式聊天: {} - 提供商: {}", sessionId, providerType.getDisplayName());
-
+//        logger.debug("开始流式聊天: {} - 提供商: {}", sessionId, providerType.getDisplayName());
         // 订阅流式响应 - 修复方法调用
-        Disposable subscription = aiChatService.chatStream(providerType, chatRequest)
-                .doOnSubscribe(sub -> {
-                    logger.debug("流式聊天订阅成功: {}", sessionId);
-                })
+        Disposable subscription = aiChatService. chatStream(providerType, chatRequest)
+//                .doOnSubscribe(sub -> {
+//                    logger.debug("流式聊天订阅成功: {}", sessionId);
+//                })
                 .subscribe(
                         // 处理每个数据块 - 修复方法调用
                         chunk -> {
@@ -196,13 +192,13 @@ public class AiChatWebSocketHandler implements WebSocketHandler {
                         },
                         // 处理错误
                         error -> {
-                            logger.error("流式聊天错误: {} - 提供商: {}", sessionId, providerType.getDisplayName(), error);
+//                            logger.error("流式聊天错误: {} - 提供商: {}", sessionId, providerType.getDisplayName(), error);
                             sendError(session, "聊天失败: " + error.getMessage());
                             sessionManager.cancelStreamSubscription(sessionId);
                         },
                         // 处理完成 - 修复方法调用
                         () -> {
-                            logger.debug("流式聊天完成: {} - 提供商: {}", sessionId, providerType.getDisplayName());
+//                            logger.debug("流式聊天完成: {} - 提供商: {}", sessionId, providerType.getDisplayName());
                             sendMessage(session, WebSocketMessage.chatComplete());
                             sessionManager.cancelStreamSubscription(sessionId);
                         }
@@ -212,6 +208,48 @@ public class AiChatWebSocketHandler implements WebSocketHandler {
         sessionManager.setStreamSubscription(sessionId, subscription);
     }
 
+
+    /**
+     * 开始流式聊天:无结构
+     */
+    private void startStreamChatNoStruct(WebSocketSession session, String sessionId,
+                                 AiProviderType providerType, ChatRequest chatRequest) {
+
+//        logger.debug("开始流式聊天: {} - 提供商: {}", sessionId, providerType.getDisplayName());
+        // 订阅流式响应 - 修复方法调用
+        Disposable subscription = aiChatService.chatStream(providerType, chatRequest)
+//                .doOnSubscribe(sub -> {
+//                    logger.debug("流式聊天订阅成功: {}", sessionId);
+//                })
+                .subscribe(
+                        // 处理每个数据块 - 修复方法调用
+                        chunk -> {
+                            if (chunk != null && !chunk.trim().isEmpty()) {
+                                if (chunk.equals("<think>") || chunk.equals("</think>")) {
+                                    return;
+                                }
+                                sendMessage(session,chunk);
+                            }
+                        },
+                        // 处理错误
+                        error -> {
+//                            logger.error("流式聊天错误: {} - 提供商: {}", sessionId, providerType.getDisplayName(), error);
+                            sendError(session, "聊天失败: " + error.getMessage());
+                            sessionManager.cancelStreamSubscription(sessionId);
+                        },
+                        // 处理完成 - 修复方法调用
+                        () -> {
+//                            logger.debug("流式聊天完成: {} - 提供商: {}", sessionId, providerType.getDisplayName());
+                            sendMessage(session, "[done]");
+                            sessionManager.cancelStreamSubscription(sessionId);
+                        }
+                );
+
+        // 保存订阅以便后续取消
+        sessionManager.setStreamSubscription(sessionId, subscription);
+    }
+
+
     /**
      * 处理取消聊天
      */
@@ -300,6 +338,26 @@ public class AiChatWebSocketHandler implements WebSocketHandler {
         }
     }
 
+    /**
+     * 发送消息到WebSocket客户端
+     */
+    private void sendMessage(WebSocketSession session, String message) {
+        if (session == null || !session.isOpen()) {
+            logger.warn("尝试向已关闭的WebSocket会话发送消息: {}",
+                    session != null ? session.getId() : "null");
+            return;
+        }
+        try {
+            session.sendMessage(new TextMessage(message));
+        } catch (IOException e) {
+            logger.error("发送WebSocket消息失败: {}", session.getId(), e);
+            // 如果发送失败,可能连接已断开,执行清理
+            cleanup(session.getId());
+        } catch (Exception e) {
+            logger.error("序列化WebSocket消息失败: {}", session.getId(), e);
+        }
+    }
+
     /**
      * 发送错误消息
      */
@@ -334,8 +392,8 @@ public class AiChatWebSocketHandler implements WebSocketHandler {
     private String extractModel(WebSocketSession session) {
         try {
             String query = session.getUri().getQuery();
-            if (query != null && query.contains("provider=")) {
-                String[] parts = query.split("provider=");
+            if (query != null && query.contains("model=")) {
+                String[] parts = query.split("model=");
                 if (parts.length > 1) {
                     String userId = parts[1].split("&")[0];
                     return java.net.URLDecoder.decode(userId, "UTF-8");

+ 6 - 9
storlead-ai-api/src/main/java/com/storlead/ai/websocket/WebSocketConfig.java

@@ -19,23 +19,20 @@ public class WebSocketConfig implements WebSocketConfigurer {
     @Autowired
     private AiChatWebSocketHandler aiChatWebSocketHandler;
 
-    String[] allowedOrigins = {
-            "http://localhost:18090",
-            "https://localhost:18090",
-            "http://127.0.0.1:18090",
-            "https://127.0.0.1:18090"
-    };
-
     @Override
     public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
         // 注册原生WebSocket处理器
         registry.addHandler(aiChatWebSocketHandler, "/ws/ai/chat")
-                .setAllowedOrigins(allowedOrigins)  // 使用具体的域名列表
+                .setAllowedOrigins("*")  // 使用具体的域名列表
                 .withSockJS();           // 启用SockJS支持,提供备用传输
 
         // 注册纯WebSocket处理器(不使用SockJS)
         registry.addHandler(aiChatWebSocketHandler, "/ws/ai/chat/native")
-                .setAllowedOrigins(allowedOrigins)  // 使用具体的域名列表
                 .setAllowedOrigins("*");
+
+        // 注册纯WebSocket处理器(不使用SockJS)
+        registry.addHandler(aiChatWebSocketHandler, "/xh/gpt/chatWebSocket")
+                .setAllowedOrigins("*");
+
     }
 }

+ 79 - 167
storlead-ai-api/src/main/resources/application-dev.yml

@@ -1,5 +1,5 @@
 server:
-  port: 18090
+  port: 8780
   tomcat:
     max-swallow-size: -1
     max-upload-size: 200MB
@@ -56,17 +56,17 @@ spring:
 #        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
+#  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: /**
@@ -74,68 +74,68 @@ spring:
     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
+#  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
+#          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/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_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
+#          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://192.168.1.77:11434
+      base-url: http://47.112.196.2:11434
       default-model: deepseek-r1:14b
       timeout-seconds: 30
       max-retries: 3
@@ -146,11 +146,25 @@ ai:
         - deepseek-r1:14b
         - deepseek-r1:8b
         - gpt-oss:20b
+    sparkai:
+      enabled: true
+      api-key: BVIjgfeTziSCXYGnfMwa:FyuMizscwweRWOcXHWew
+      base-url: https://spark-api-open.xf-yun.com
+      default-model: Lite
+      timeout-seconds: 30
+      max-retries: 3
+      default-temperature: 0.7
+      default-max-tokens: 1000
+      stream-enabled: true
+      supported-models:
+        - Lite
+        - Pro
+        - 4.0Ultra
     # StorledAI配置
     storleadai:
       enabled: true
       api-key:
-      base-url: http://192.168.1.77:11434
+      base-url: http://47.112.196.2:11434
       default-model: deepseek-r1:14b
       timeout-seconds: 30
       max-retries: 3
@@ -163,64 +177,6 @@ ai:
         - 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:
@@ -247,47 +203,3 @@ logging:
               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

+ 128 - 146
storlead-ai-api/src/main/resources/application-prod.yml

@@ -1,13 +1,12 @@
-#开发模式
-debug: false
 server:
-  port: 18094
+  port: 8780
   tomcat:
     max-swallow-size: -1
     max-upload-size: 200MB
-    basedir: /mnt/vdb/storlead/sales/temp
+    basedir: /app/temp
   servlet:
-    context-path: /sales
+    context-path: /router/rest
+
   compression:
     enabled: true
     min-response-size: 1024
@@ -48,120 +47,141 @@ spring:
     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
+  #  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://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
+#  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
+    sparkai:
+      enabled: true
+      api-key: BVIjgfeTziSCXYGnfMwa:FyuMizscwweRWOcXHWew
+      base-url: https://spark-api-open.xf-yun.com
+      default-model: Lite
+      timeout-seconds: 30
+      max-retries: 3
+      default-temperature: 0.7
+      default-max-tokens: 1000
+      stream-enabled: true
+      supported-models:
+        - Lite
+        - Pro
+        - 4.0Ultra
+    # StorledAI配置
+    storleadai:
+      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
 
-#应用专用配置
-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/
+    path: /app/sp/${spring.application.name}/log
   level:
     root: info
     io:
@@ -182,42 +202,4 @@ logging:
             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
+      storlead: debug

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

@@ -65,22 +65,8 @@ swagger:
     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
 
 

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

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

+ 79 - 167
storlead-ai-api/target/classes/application-dev.yml

@@ -1,5 +1,5 @@
 server:
-  port: 18090
+  port: 8780
   tomcat:
     max-swallow-size: -1
     max-upload-size: 200MB
@@ -56,17 +56,17 @@ spring:
 #        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
+#  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: /**
@@ -74,68 +74,68 @@ spring:
     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
+#  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
+#          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/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_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
+#          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://192.168.1.77:11434
+      base-url: http://47.112.196.2:11434
       default-model: deepseek-r1:14b
       timeout-seconds: 30
       max-retries: 3
@@ -146,11 +146,25 @@ ai:
         - deepseek-r1:14b
         - deepseek-r1:8b
         - gpt-oss:20b
+    sparkai:
+      enabled: true
+      api-key: BVIjgfeTziSCXYGnfMwa:FyuMizscwweRWOcXHWew
+      base-url: https://spark-api-open.xf-yun.com
+      default-model: Lite
+      timeout-seconds: 30
+      max-retries: 3
+      default-temperature: 0.7
+      default-max-tokens: 1000
+      stream-enabled: true
+      supported-models:
+        - Lite
+        - Pro
+        - 4.0Ultra
     # StorledAI配置
     storleadai:
       enabled: true
       api-key:
-      base-url: http://192.168.1.77:11434
+      base-url: http://47.112.196.2:11434
       default-model: deepseek-r1:14b
       timeout-seconds: 30
       max-retries: 3
@@ -163,64 +177,6 @@ ai:
         - 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:
@@ -247,47 +203,3 @@ logging:
               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

+ 128 - 146
storlead-ai-api/target/classes/application-prod.yml

@@ -1,13 +1,12 @@
-#开发模式
-debug: false
 server:
-  port: 18094
+  port: 8780
   tomcat:
     max-swallow-size: -1
     max-upload-size: 200MB
-    basedir: /mnt/vdb/storlead/sales/temp
+    basedir: /app/temp
   servlet:
-    context-path: /sales
+    context-path: /router/rest
+
   compression:
     enabled: true
     min-response-size: 1024
@@ -48,120 +47,141 @@ spring:
     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
+  #  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://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
+#  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
+    sparkai:
+      enabled: true
+      api-key: BVIjgfeTziSCXYGnfMwa:FyuMizscwweRWOcXHWew
+      base-url: https://spark-api-open.xf-yun.com
+      default-model: Lite
+      timeout-seconds: 30
+      max-retries: 3
+      default-temperature: 0.7
+      default-max-tokens: 1000
+      stream-enabled: true
+      supported-models:
+        - Lite
+        - Pro
+        - 4.0Ultra
+    # StorledAI配置
+    storleadai:
+      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
 
-#应用专用配置
-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/
+    path: /app/sp/${spring.application.name}/log
   level:
     root: info
     io:
@@ -182,42 +202,4 @@ logging:
             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
+      storlead: debug

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

@@ -65,22 +65,8 @@ swagger:
     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/config/properties/SparkAiProperties.class


BIN
storlead-ai-api/target/classes/com/storlead/ai/config/properties/StorleadAiProperties.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/factory/AiServiceFactoryManager.class


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


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


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


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


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


BIN
storlead-ai-api/target/classes/com/storlead/ai/websocket/AiChatWebSocketHandler.class


BIN
storlead-ai-api/target/classes/com/storlead/ai/websocket/WebSocketConfig.class