Compare commits

...

13 Commits

Author SHA1 Message Date
huangyongxing 9c0a8e2d57 4.7学习笔记 7 days ago
huangyongxing 2f351aeb1d 项目案例优化及笔记总结 1 week ago
huangyongxing 38e807600e 智能客服项目案例 2 weeks ago
huangyongxing 357bdcc002 4.1项目案例 2 weeks ago
huangyongxing d8a23e51b8 3.31学习笔记 2 weeks ago
huangyongxing a4008af72a 3.30学习笔记 2 weeks ago
huangyongxing d04a111382 3.28学习笔记 2 weeks ago
huangyongxing 2c9ea554f3 3.27学习笔记 3 weeks ago
huangyongxing 5759ec7be7 3.27股票学习笔记 3 weeks ago
huangyongxing 174aa0bc84 3.27股票学习笔记 3 weeks ago
huangyongxing d049131b30 3.27股票学习笔记 3 weeks ago
huangyongxing 4f70f59221 3.27学习笔记 3 weeks ago
huangyongxing 7404518e03 3.27学习笔记 3 weeks ago
  1. BIN
      黄永兴学习笔记/3.26黄永兴.docx
  2. BIN
      黄永兴学习笔记/3.27黄永兴-股票学习笔记.docx
  3. BIN
      黄永兴学习笔记/3.27黄永兴.docx
  4. BIN
      黄永兴学习笔记/3.28黄永兴-股票学习笔记.docx
  5. BIN
      黄永兴学习笔记/3.28黄永兴.docx
  6. BIN
      黄永兴学习笔记/3.30黄永兴-软件功能总结.docx
  7. BIN
      黄永兴学习笔记/3.30黄永兴.docx
  8. BIN
      黄永兴学习笔记/3.31黄永兴-软件功能总结.docx
  9. BIN
      黄永兴学习笔记/3.31黄永兴.docx
  10. BIN
      黄永兴学习笔记/4.1黄永兴-项目案例笔记.docx
  11. BIN
      黄永兴学习笔记/4.4黄永兴.docx
  12. BIN
      黄永兴学习笔记/4.7CosyVoice 高并发调用实战笔记.docx
  13. BIN
      黄永兴学习笔记/Spring AI Alibaba 智能客服+语音合成项目笔记.docx
  14. BIN
      黄永兴学习笔记/SpringAIAlibaba.zip
  15. 33
      黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/.gitignore
  16. 3
      黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/.mvn/wrapper/maven-wrapper.properties
  17. 100
      黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/pom.xml
  18. 13
      黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/src/main/java/com/example/springaiembedding/SpringAiEmbeddingApplication.java
  19. 54
      黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/src/main/java/com/example/springaiembedding/controller/EmbeddingController.java
  20. 22
      黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/src/main/java/com/example/springaiembedding/controller/RagController.java
  21. 87
      黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/src/main/java/com/example/springaiembedding/service/RagService.java
  22. 93
      黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/src/main/java/com/example/springaiembedding/service/TextSimilarityService.java
  23. 28
      黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/src/main/java/com/example/springaiembedding/tool/DocumentLoader.java
  24. 21
      黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/src/main/resources/application.properties
  25. 8
      黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/src/main/resources/knowledge.txt
  26. 13
      黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/src/test/java/com/example/springaiembedding/SpringAiEmbeddingApplicationTests.java
  27. 33
      黄永兴学习笔记/StudySpringAI/SpringAIQuickStart/.gitignore
  28. 3
      黄永兴学习笔记/StudySpringAI/SpringAIQuickStart/.mvn/wrapper/maven-wrapper.properties
  29. 104
      黄永兴学习笔记/StudySpringAI/SpringAIQuickStart/pom.xml
  30. 13
      黄永兴学习笔记/StudySpringAI/SpringAIQuickStart/src/main/java/com/example/springaiquickstart/SpringAiQuickStartApplication.java
  31. 127
      黄永兴学习笔记/StudySpringAI/SpringAIQuickStart/src/main/java/com/example/springaiquickstart/controller/ChatController.java
  32. 13
      黄永兴学习笔记/StudySpringAI/SpringAIQuickStart/src/main/resources/application.properties
  33. 224
      黄永兴学习笔记/StudySpringAI/SpringAIQuickStart/src/main/resources/static/index.html
  34. 13
      黄永兴学习笔记/StudySpringAI/SpringAIQuickStart/src/test/java/com/example/springaiquickstart/SpringAiQuickStartApplicationTests.java
  35. 8
      黄永兴学习笔记/StudySpringAI/StudySpringAI.iml
  36. 3
      黄永兴学习笔记/智能客服案例/SpringAIAlibaba/.mvn/wrapper/maven-wrapper.properties
  37. 10
      黄永兴学习笔记/智能客服案例/SpringAIAlibaba/SpringAIAlibaba.iml
  38. 133
      黄永兴学习笔记/智能客服案例/SpringAIAlibaba/pom.xml
  39. 13
      黄永兴学习笔记/智能客服案例/SpringAIAlibaba/src/main/java/com/example/springaialibaba/SpringAiAlibabaApplication.java
  40. 230
      黄永兴学习笔记/智能客服案例/SpringAIAlibaba/src/main/java/com/example/springaialibaba/controller/CustomerServiceController.java
  41. 97
      黄永兴学习笔记/智能客服案例/SpringAIAlibaba/src/main/java/com/example/springaialibaba/controller/Main.java
  42. 118
      黄永兴学习笔记/智能客服案例/SpringAIAlibaba/src/main/java/com/example/springaialibaba/controller/SpeechSynthesisController.java
  43. 14
      黄永兴学习笔记/智能客服案例/SpringAIAlibaba/src/main/java/com/example/springaialibaba/pojo/StreamResponse.java
  44. 107
      黄永兴学习笔记/智能客服案例/SpringAIAlibaba/src/main/java/com/example/springaialibaba/service/AudioGenerationService.java
  45. 82
      黄永兴学习笔记/智能客服案例/SpringAIAlibaba/src/main/java/com/example/springaialibaba/service/SpeechSynthesisService.java
  46. 14
      黄永兴学习笔记/智能客服案例/SpringAIAlibaba/src/main/resources/application.yml
  47. 263
      黄永兴学习笔记/智能客服案例/SpringAIAlibaba/src/main/resources/static/index.html
  48. 13
      黄永兴学习笔记/智能客服案例/SpringAIAlibaba/src/test/java/com/example/springaialibaba/SpringAiAlibabaApplicationTests.java
  49. 14
      黄永兴学习笔记/智能客服案例/SpringAIAlibaba/target/classes/application.yml
  50. BIN
      黄永兴学习笔记/智能客服案例/SpringAIAlibaba/target/classes/com/example/springaialibaba/SpringAiAlibabaApplication.class
  51. BIN
      黄永兴学习笔记/智能客服案例/SpringAIAlibaba/target/classes/com/example/springaialibaba/controller/CustomerServiceController.class
  52. BIN
      黄永兴学习笔记/智能客服案例/SpringAIAlibaba/target/classes/com/example/springaialibaba/controller/Main$1.class
  53. BIN
      黄永兴学习笔记/智能客服案例/SpringAIAlibaba/target/classes/com/example/springaialibaba/controller/Main.class
  54. BIN
      黄永兴学习笔记/智能客服案例/SpringAIAlibaba/target/classes/com/example/springaialibaba/controller/TimeUtils.class
  55. BIN
      黄永兴学习笔记/智能客服案例/SpringAIAlibaba/target/classes/com/example/springaialibaba/pojo/StreamResponse.class
  56. BIN
      黄永兴学习笔记/智能客服案例/SpringAIAlibaba/target/classes/com/example/springaialibaba/service/SpeechSynthesisService.class
  57. 263
      黄永兴学习笔记/智能客服案例/SpringAIAlibaba/target/classes/static/index.html
  58. 0
      黄永兴学习笔记/智能客服案例/SpringAIAlibaba/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
  59. 4
      黄永兴学习笔记/智能客服案例/SpringAIAlibaba/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
  60. BIN
      黄永兴学习笔记/智能客服案例/SpringAIAlibaba/target/test-classes/com/example/springaialibaba/SpringAiAlibabaApplicationTests.class

BIN
黄永兴学习笔记/3.26黄永兴.docx

BIN
黄永兴学习笔记/3.27黄永兴-股票学习笔记.docx

BIN
黄永兴学习笔记/3.27黄永兴.docx

BIN
黄永兴学习笔记/3.28黄永兴-股票学习笔记.docx

BIN
黄永兴学习笔记/3.28黄永兴.docx

BIN
黄永兴学习笔记/3.30黄永兴-软件功能总结.docx

BIN
黄永兴学习笔记/3.30黄永兴.docx

BIN
黄永兴学习笔记/3.31黄永兴-软件功能总结.docx

BIN
黄永兴学习笔记/3.31黄永兴.docx

BIN
黄永兴学习笔记/4.1黄永兴-项目案例笔记.docx

BIN
黄永兴学习笔记/4.4黄永兴.docx

BIN
黄永兴学习笔记/4.7CosyVoice 高并发调用实战笔记.docx

BIN
黄永兴学习笔记/Spring AI Alibaba 智能客服+语音合成项目笔记.docx

BIN
黄永兴学习笔记/SpringAIAlibaba.zip

33
黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/.gitignore

@ -0,0 +1,33 @@
HELP.md
target/
.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

3
黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/.mvn/wrapper/maven-wrapper.properties

@ -0,0 +1,3 @@
wrapperVersion=3.3.4
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.14/apache-maven-3.9.14-bin.zip

100
黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/pom.xml

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.13</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>SpringAIEmbedding</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SpringAIEmbedding</name>
<description>SpringAIEmbedding</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
<spring-ai.version>1.1.3</spring-ai.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring AI Zhipuai 模型依赖 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-zhipuai</artifactId>
</dependency>
<!-- Spring AI Chat 客户端依赖 中包括TokenTextSplitter、TextReader、Document 等工具类 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-client-chat</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
<!-- 声明仓库,用于获取 Spring AI 以及相关预发布版本 -->
<repositories>
<!-- Spring Milestones 仓库 -->
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<!-- Spring Snapshots 仓库 -->
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

13
黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/src/main/java/com/example/springaiembedding/SpringAiEmbeddingApplication.java

@ -0,0 +1,13 @@
package com.example.springaiembedding;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringAiEmbeddingApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAiEmbeddingApplication.class, args);
}
}

54
黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/src/main/java/com/example/springaiembedding/controller/EmbeddingController.java

@ -0,0 +1,54 @@
package com.example.springaiembedding.controller;
import com.example.springaiembedding.service.TextSimilarityService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.Map;
@RestController
@RequestMapping("/ai")
public class EmbeddingController {
@Autowired
private EmbeddingModel embeddingModel;
//对用户传入的文本进行向量化处理测试embedding模型
@RequestMapping("/embedding")
public Map<String,Object> embedding(
@RequestParam(value = "message",defaultValue = "给我讲个笑话") String message
){
// 对用户传入的文本进行向量化处理
float[] embedding = embeddingModel.embed(message);
return Map.of("message",message,
"vector",embedding);
}
@Resource
private TextSimilarityService textSimilarityService;
/**
* 查找相似文本接口
* @param message 查询文本
* @return 相似文本+相似度
*/
@GetMapping("/similarity")
public Map<String, Double> findSimilarText(@RequestParam String message) {
// 查询Top3相似文本
return textSimilarityService.findSimilarTexts(message, 3);
}
}

22
黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/src/main/java/com/example/springaiembedding/controller/RagController.java

@ -0,0 +1,22 @@
package com.example.springaiembedding.controller;
import com.example.springaiembedding.service.RagService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/ai/rag")
public class RagController {
@Resource
private RagService ragService;
// 问答接口
@GetMapping("/query")
public String query(@RequestParam String question) {
return ragService.generateAnswer(question);
}
}

87
黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/src/main/java/com/example/springaiembedding/service/RagService.java

@ -0,0 +1,87 @@
package com.example.springaiembedding.service;
import com.example.springaiembedding.tool.DocumentLoader;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.EmbeddingResponse;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class RagService {
@Resource
private EmbeddingModel embeddingModel;
@Resource
private ChatModel chatModel;
// 内存存储知识库片段 对应向量
private Map<String, float[]> knowledgeVectors = new HashMap<>();
// 项目启动时自动加载并向量化知识库
@PostConstruct
public void initKnowledgeBase() throws IOException {
// 1. 加载并拆分文档
List<String> chunks = DocumentLoader.loadAndSplit("knowledge.txt");
// 2. 批量向量化一次请求生成所有片段向量
EmbeddingResponse embeddingResponse = embeddingModel.embedForResponse(chunks);
// 3. 存储片段与向量的映射
for (int i = 0; i < chunks.size(); i++) {
knowledgeVectors.put(chunks.get(i), embeddingResponse.getResults().get(i).getOutput());
}
}
// 检索与用户问题最相似的 Top N 片段
private List<String> retrieveSimilarChunks(String query, int topN) {
// 1. 用户问题向量化
float[] queryVector = embeddingModel.embed(query);
// 2. 计算与所有知识库片段的相似度
Map<String, Double> similarityMap = new HashMap<>();
//Entry是什么Map.Entry<String, float[]> entry 是一个 Map.Entry 对象用于遍历 Map 中的键值对
for (Map.Entry<String, float[]> entry : knowledgeVectors.entrySet()) {
similarityMap.put(entry.getKey(), CosineSimilarityCalculator.calculate(queryVector, entry.getValue()));
}
// 3. 按相似度降序排序 Top N
return similarityMap.entrySet().stream()
.sorted(Collections.reverseOrder(Map.Entry.comparingByValue()))
.limit(topN)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
// 生成最终回答
public String generateAnswer(String query) {
// 1. 检索相似片段 Top 2
List<String> similarChunks = retrieveSimilarChunks(query, 2);
// 2. 拼接 Prompt约束模型基于知识库回答
StringBuilder prompt = new StringBuilder();
prompt.append("请基于以下知识库内容回答用户问题,禁止编造信息:\n");
for (String chunk : similarChunks) {
prompt.append("- ").append(chunk).append("\n");
}
prompt.append("\n用户问题:").append(query);
// 3. 调用 Chat 模型生成回答
ChatClient chatClient = ChatClient.builder(chatModel).build();
return chatClient.prompt().user(prompt.toString()).call().content();
}
public class CosineSimilarityCalculator {
// 计算两个 float[] 向量的余弦相似度
public static double calculate(float[] vectorA, float[] vectorB) {
double dotProduct = 0.0;
double normA = 0.0;
double normB = 0.0;
for (int i = 0; i < vectorA.length; i++) {
dotProduct += vectorA[i] * vectorB[i];
normA += Math.pow(vectorA[i], 2);
normB += Math.pow(vectorB[i], 2);
}
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
}
}
}

93
黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/src/main/java/com/example/springaiembedding/service/TextSimilarityService.java

@ -0,0 +1,93 @@
package com.example.springaiembedding.service;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.EmbeddingResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class TextSimilarityService {
@Autowired
private EmbeddingModel embeddingModel;
// 模拟本地文本库可替换为数据库/向量库
private final List<String> TEXT_LIBRARY = Arrays.asList(
"我爱Java编程",
"SpringBoot是最流行的后端框架",
"人工智能改变世界",
"大模型应用开发",
"我喜欢学习编程",
"向量数据库用于存储Embedding",
"SpringAI简化大模型开发"
);
/**
* 查找最相似的文本
* @param queryText 查询文本
* @param topN 返回前N条结果
* @return 相似文本+相似度
*/
public Map<String, Double> findSimilarTexts(String queryText, int topN) {
// 1. 将查询文本转为向量
//float[] queryVector = embeddingModel.embed(queryText);
EmbeddingResponse queryEmbedding = embeddingModel.embedForResponse(List.of(queryText));
float[] queryVector = queryEmbedding.getResults().get(0).getOutput();
// 2. 将文本库所有文本转为向量
// 2. 将文本库所有文本转为向量
Map<String, float[]> textVectorMap = new HashMap<>();
for (String text : TEXT_LIBRARY) {
float[] textVector = embeddingModel.embed(text);
//put是将文本和向量存储到Map中
textVectorMap.put(text, textVector);
}
// 3. 计算相似度并排序
Map<String, Double> similarityMap = new HashMap<>();
for (Map.Entry<String, float[]> entry : textVectorMap.entrySet()) {
double similarity = CosineSimilarityCalculator.calculate(queryVector, entry.getValue());
similarityMap.put(entry.getKey(), similarity);
}
// 4. 按相似度降序排序取TopN
return similarityMap.entrySet().stream()
.sorted(Collections.reverseOrder(Map.Entry.comparingByValue()))
.limit(topN)
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(oldValue, newValue) -> oldValue,
LinkedHashMap::new
));
}
/**
* 余弦相似度工具类计算文本向量相似度
*/
public class CosineSimilarityCalculator {
/**
* 计算两个向量的余弦相似度
* @param vectorA 向量A
* @param vectorB 向量B
* @return 相似度 0~1值越大越相似
*/
public static double calculate(float[] vectorA, float[] vectorB) {
double dotProduct = 0.0;
double normA = 0.0;
double normB = 0.0;
for (int i = 0; i < vectorA.length; i++) {
dotProduct += vectorA[i] * vectorB[i];
normA += Math.pow(vectorA[i], 2);
normB += Math.pow(vectorB[i], 2);
}
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
}
}
}

28
黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/src/main/java/com/example/springaiembedding/tool/DocumentLoader.java

@ -0,0 +1,28 @@
package com.example.springaiembedding.tool;
import org.springframework.core.io.ClassPathResource;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
// 文档加载工具类
public class DocumentLoader {
// 加载并拆分知识库文档
public static List<String> loadAndSplit(String resourcePath) throws IOException {
ClassPathResource resource = new ClassPathResource(resourcePath); // 从类路径加载资源
BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream())); // 按行读取
String content = reader.lines().collect(Collectors.joining("\n")); // 读取文件内容将所有行连接起来一个字符串
// "---" 拆分片段过滤空内容
List<String> chunks = new ArrayList<>();
for (String chunk : content.split("---")) {
String trimmed = chunk.trim();
if (!trimmed.isEmpty()) chunks.add(trimmed);
}
return chunks;
}
}

21
黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/src/main/resources/application.properties

@ -0,0 +1,21 @@
spring.application.name=SpringAIEmbedding
server.port=8080
## ????AI ????
# Embedding ??
spring.ai.zhipuai.api-key=c721340a438942d0942148b48a22e50e.5VCKagzmQdyHpwyc
spring.ai.zhipuai.base-url=https://open.bigmodel.cn/api/paas
# Embedding ??
spring.ai.zhipuai.embedding-model=embedding-2
# Chat ??
spring.ai.zhipuai.chat.options.model=GLM-4.7-Flash
# ??????
logging.pattern.console=%-5level %logger - %msg%n

8
黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/src/main/resources/knowledge.txt

@ -0,0 +1,8 @@
---
Spring AI 是一个用于简化大模型应用开发的框架,支持智谱AI、OpenAI 等多种大模型厂商。
---
智谱AI Embedding 模型可将文本转换为向量,用于语义检索、相似匹配等场景。
---
RAG(检索增强生成)通过检索本地知识库内容,辅助大模型生成更准确的回答,避免幻觉。
---
Spring AI 提供 EmbeddingModel 和 ChatModel 接口,让开发者快速接入大模型能力。

13
黄永兴学习笔记/StudySpringAI/SpringAIEmbedding/src/test/java/com/example/springaiembedding/SpringAiEmbeddingApplicationTests.java

@ -0,0 +1,13 @@
package com.example.springaiembedding;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SpringAiEmbeddingApplicationTests {
@Test
void contextLoads() {
}
}

33
黄永兴学习笔记/StudySpringAI/SpringAIQuickStart/.gitignore

@ -0,0 +1,33 @@
HELP.md
target/
.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

3
黄永兴学习笔记/StudySpringAI/SpringAIQuickStart/.mvn/wrapper/maven-wrapper.properties

@ -0,0 +1,3 @@
wrapperVersion=3.3.4
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.14/apache-maven-3.9.14-bin.zip

104
黄永兴学习笔记/StudySpringAI/SpringAIQuickStart/pom.xml

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.13</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>SpringAIQuickStart</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SpringAIQuickStart</name>
<description>SpringAIQuickStart</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring AI Deepseek 模型依赖 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-deepseek</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- 声明仓库,用于获取 Spring AI 以及相关预发布版本 -->
<repositories>
<!-- Spring Milestones 仓库 -->
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<!-- Spring Snapshots 仓库 -->
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

13
黄永兴学习笔记/StudySpringAI/SpringAIQuickStart/src/main/java/com/example/springaiquickstart/SpringAiQuickStartApplication.java

@ -0,0 +1,13 @@
package com.example.springaiquickstart;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringAiQuickStartApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAiQuickStartApplication.class, args);
}
}

127
黄永兴学习笔记/StudySpringAI/SpringAIQuickStart/src/main/java/com/example/springaiquickstart/controller/ChatController.java

@ -0,0 +1,127 @@
package com.example.springaiquickstart.controller;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.deepseek.DeepSeekChatModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import java.util.Arrays;
import java.util.List;
@RestController
@RequestMapping("/ai")
public class ChatController {
@Autowired
private DeepSeekChatModel chatModel;
// 与模型直接对话返回字符串响应
@GetMapping("/generate")
public String generate(@RequestParam(value = "message",defaultValue = "你好,你是谁?") String message) {
System.out.println("message: " + message);
// 与模型直接对话调用chatModel的call方法生成响应
String response = chatModel.call(message);
System.out.println("response: " + response);
return response;
}
// 与模型对话流式返回内容
@GetMapping("/generateStream1")
public Flux<ChatResponse> generateStream1(@RequestParam(value = "message",defaultValue = "你好,你是谁?") String message) {
System.out.println("message: " + message);
// 与模型对话流式返回内容
Prompt prompt = new Prompt(message);
Flux<ChatResponse> stream = chatModel.stream(prompt);
System.out.println("stream: " + stream);
return stream;
}
// 与模型对话流式返回内容转换为字符串流
// 解决中文乱码问题
// 用lambda表达式简化
@GetMapping("/generateStream2")
public Flux<String> generateStream2(
@RequestParam(value = "message",defaultValue = "你好,你是谁?") String message,
HttpServletResponse response
) {
//设置字符编码为UTF-8,解决中文乱码问题
response.setCharacterEncoding("UTF-8");
System.out.println("message: " + message);
// 与模型对话流式返回内容
Prompt prompt = new Prompt(message);
Flux<ChatResponse> stream = chatModel.stream(prompt);
// 转换为字符串流用lambda表达式简化
Flux<String> result = stream.map(ChatResponse ->
ChatResponse.getResult().getOutput().getText()
);
// 转换为字符串流用方法引用简化
// Flux<String> result = stream.map(ChatResponse::getResult)
// .map(Generation::getOutput)
// .map(AssistantMessage::getText);
System.out.println("result: " + result);
return result;
}
//运行时设置模型参数
@GetMapping("/runtimeOptions")
public Flux<String> runtimeOptions(
@RequestParam(value = "message",defaultValue = "你好,你是谁?") String message,
@RequestParam(value = "temperature",required = false) Double temp,
HttpServletResponse response
) {
//设置字符编码为UTF-8,解决中文乱码问题
response.setCharacterEncoding("UTF-8");
// 构建系统提示
SystemMessage systemMessage = new SystemMessage("你是一个资深Java开发工程师,回答要简洁专业");
// 构建用户消息
UserMessage userMessage = new UserMessage(message);
// 构建历史消息多轮对话时加入
List<Message> messages = Arrays.asList(systemMessage, userMessage);
// 封装成 Prompt还可以设置模型参数
Prompt prompt = new Prompt(messages,
ChatOptions.builder()
.temperature(temp)
.maxTokens(1000)
.build()
);
// 流式调用
Flux<ChatResponse> stream = chatModel.stream(prompt);
// 转换为字符串流用lambda表达式简化
Flux<String> result = stream.map(ChatResponse ->
ChatResponse.getResult().getOutput().getText()
);
System.out.println("result: " + result);
return result;
}
}

13
黄永兴学习笔记/StudySpringAI/SpringAIQuickStart/src/main/resources/application.properties

@ -0,0 +1,13 @@
spring.application.name=SpringAIQuickStart
server.port=8080
#?? Deepseek ???????url?????????
spring.ai.deepseek.base-url=https://api.deepseek.com
spring.ai.deepseek.api-key=sk-ccbfe09f433148129cd98df6150653e8
spring.ai.deepseek.chat.options.model=deepseek-chat
#??0-2???0????????2???????
spring.ai.deepseek.chat.options.temperature=0.8

224
黄永兴学习笔记/StudySpringAI/SpringAIQuickStart/src/main/resources/static/index.html

@ -0,0 +1,224 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Spring AI 聊天助手</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#3b82f6',
secondary: '#64748b',
neutral: '#f8fafc',
dark: '#1e293b'
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif']
}
}
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.chat-message {
@apply p-4 rounded-lg mb-4 max-w-[80%];
}
.user-message {
@apply bg-primary text-white self-end;
}
.ai-message {
@apply bg-neutral border border-gray-200 text-dark self-start;
}
}
</style>
</head>
<body class="bg-gray-50 min-h-screen flex flex-col">
<!-- 顶部导航栏 -->
<header class="bg-white shadow-sm">
<div class="container mx-auto px-4 py-4 flex justify-between items-center">
<div class="flex items-center space-x-2">
<i class="fa fa-robot text-primary text-2xl"></i>
<h1 class="text-xl font-bold text-dark">Spring AI 聊天助手</h1>
</div>
<div class="text-sm text-secondary">
<span id="status" class="flex items-center">
<span class="w-2 h-2 bg-green-500 rounded-full mr-2"></span>
在线
</span>
</div>
</div>
</header>
<!-- 主内容区 -->
<main class="flex-1 container mx-auto px-4 py-8 max-w-4xl">
<!-- 聊天区域 -->
<div class="bg-white rounded-xl shadow-md overflow-hidden">
<!-- 聊天头部 -->
<div class="bg-primary text-white p-4">
<h2 class="text-lg font-semibold">AI 助手</h2>
<p class="text-sm opacity-80">基于 DeepSeek 模型</p>
</div>
<!-- 聊天消息区 -->
<div id="chat-messages" class="p-4 h-[500px] overflow-y-auto flex flex-col space-y-4">
<!-- 欢迎消息 -->
<div class="chat-message ai-message">
<div class="flex items-start">
<div class="w-8 h-8 rounded-full bg-primary text-white flex items-center justify-center mr-3 flex-shrink-0">
<i class="fa fa-robot"></i>
</div>
<div>
<p class="font-semibold mb-1">AI 助手</p>
<p>你好!我是基于 DeepSeek 模型的 AI 助手,有什么可以帮助你的吗?</p>
</div>
</div>
</div>
</div>
<!-- 输入区域 -->
<div class="p-4 border-t">
<form id="chat-form" class="flex space-x-2">
<input
type="text"
id="message-input"
placeholder="输入消息..."
class="flex-1 border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
>
<button
type="submit"
id="send-button"
class="bg-primary text-white rounded-lg px-6 py-2 hover:bg-primary/90 transition-colors flex items-center space-x-2"
>
<span>发送</span>
<i class="fa fa-paper-plane"></i>
</button>
</form>
</div>
</div>
<!-- 功能说明 -->
<div class="mt-8 bg-white rounded-xl shadow-md p-6">
<h3 class="text-lg font-semibold mb-4">功能说明</h3>
<ul class="space-y-2 text-secondary">
<li class="flex items-center">
<i class="fa fa-check-circle text-green-500 mr-2"></i>
支持自然语言对话
</li>
<li class="flex items-center">
<i class="fa fa-check-circle text-green-500 mr-2"></i>
基于 DeepSeek 大语言模型
</li>
<li class="flex items-center">
<i class="fa fa-check-circle text-green-500 mr-2"></i>
实时响应生成
</li>
<li class="flex items-center">
<i class="fa fa-check-circle text-green-500 mr-2"></i>
简洁美观的用户界面
</li>
</ul>
</div>
</main>
<!-- 页脚 -->
<footer class="bg-white shadow-sm mt-8">
<div class="container mx-auto px-4 py-4 text-center text-secondary text-sm">
<p>© 2026 Spring AI 聊天助手 | 基于 DeepSeek 模型</p>
</div>
</footer>
<script>
document.addEventListener('DOMContentLoaded', function() {
const chatForm = document.getElementById('chat-form');
const messageInput = document.getElementById('message-input');
const chatMessages = document.getElementById('chat-messages');
const sendButton = document.getElementById('send-button');
const status = document.getElementById('status');
// 发送消息
chatForm.addEventListener('submit', function(e) {
e.preventDefault();
const message = messageInput.value.trim();
if (message) {
// 添加用户消息到聊天区
addMessage('user', message);
messageInput.value = '';
// 禁用发送按钮
sendButton.disabled = true;
sendButton.classList.add('opacity-50');
status.innerHTML = '<span class="w-2 h-2 bg-yellow-500 rounded-full mr-2"></span>处理中...';
// 调用后端 API
fetch(`/ai/generate?message=${encodeURIComponent(message)}`)
.then(response => response.text())
.then(data => {
// 添加 AI 响应到聊天区
addMessage('ai', data);
})
.catch(error => {
console.error('Error:', error);
addMessage('ai', '抱歉,处理请求时出错,请稍后再试。');
})
.finally(() => {
// 恢复发送按钮状态
sendButton.disabled = false;
sendButton.classList.remove('opacity-50');
status.innerHTML = '<span class="w-2 h-2 bg-green-500 rounded-full mr-2"></span>在线';
});
}
});
// 添加消息到聊天区
function addMessage(type, content) {
const messageDiv = document.createElement('div');
messageDiv.className = `chat-message ${type === 'user' ? 'user-message' : 'ai-message'} self-${type === 'user' ? 'end' : 'start'}`;
if (type === 'user') {
messageDiv.innerHTML = `
<div class="flex items-start justify-end">
<div>
<p class="font-semibold mb-1 text-right"></p>
<p>${content}</p>
</div>
<div class="w-8 h-8 rounded-full bg-gray-300 text-dark flex items-center justify-center ml-3 flex-shrink-0">
<i class="fa fa-user"></i>
</div>
</div>
`;
} else {
messageDiv.innerHTML = `
<div class="flex items-start">
<div class="w-8 h-8 rounded-full bg-primary text-white flex items-center justify-center mr-3 flex-shrink-0">
<i class="fa fa-robot"></i>
</div>
<div>
<p class="font-semibold mb-1">AI 助手</p>
<p>${content}</p>
</div>
</div>
`;
}
chatMessages.appendChild(messageDiv);
chatMessages.scrollTop = chatMessages.scrollHeight;
}
// 回车键发送消息
messageInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
chatForm.dispatchEvent(new Event('submit'));
}
});
});
</script>
</body>
</html>

13
黄永兴学习笔记/StudySpringAI/SpringAIQuickStart/src/test/java/com/example/springaiquickstart/SpringAiQuickStartApplicationTests.java

@ -0,0 +1,13 @@
package com.example.springaiquickstart;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SpringAiQuickStartApplicationTests {
@Test
void contextLoads() {
}
}

8
黄永兴学习笔记/StudySpringAI/StudySpringAI.iml

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="GENERAL_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

3
黄永兴学习笔记/智能客服案例/SpringAIAlibaba/.mvn/wrapper/maven-wrapper.properties

@ -0,0 +1,3 @@
wrapperVersion=3.3.4
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.14/apache-maven-3.9.14-bin.zip

10
黄永兴学习笔记/智能客服案例/SpringAIAlibaba/SpringAIAlibaba.iml

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="AdditionalModuleElements">
<content url="file://$MODULE_DIR$" dumb="true">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
</content>
</component>
</module>

133
黄永兴学习笔记/智能客服案例/SpringAIAlibaba/pom.xml

@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.13</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>SpringAIAlibaba</artifactId>
<version>1.1.2.0</version>
<name>SpringAIAlibaba</name>
<description>SpringAIAlibaba</description>
<properties>
<java.version>17</java.version>
<!-- 阿里云DashScope SDK版本(官方要求>=2.15.0) -->
<dashscope.version>2.22.13</dashscope.version>
<!-- RxJava版本(适配DashScope SDK) -->
<rxjava.version>2.2.21</rxjava.version>
</properties>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-bom</artifactId>
<version>1.1.2.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.1.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-extensions-bom</artifactId>
<version>1.1.2.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Spring AI Alibaba Agent Framework -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-agent-framework</artifactId>
<version>1.1.2.0</version>
</dependency>
<!-- DashScope ChatModel 支持(如果使用其他模型,请跳转 Spring AI 文档选择对应的 starter) -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>1.1.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring WebFlux 流式核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor.addons</groupId>
<artifactId>reactor-adapter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 1. 阿里云DashScope官方SDK(解决所有dashscope包报红) -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dashscope-sdk-java</artifactId>
<version>${dashscope.version}</version>
</dependency>
<!-- 2. RxJava 2.x(解决io.reactivex.Flowable报红) -->
<dependency>
<groupId>io.reactivex.rxjava2</groupId>
<artifactId>rxjava</artifactId>
<version>${rxjava.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

13
黄永兴学习笔记/智能客服案例/SpringAIAlibaba/src/main/java/com/example/springaialibaba/SpringAiAlibabaApplication.java

@ -0,0 +1,13 @@
package com.example.springaialibaba;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringAiAlibabaApplication {
public static void main(String[] args) {
SpringApplication.run(SpringAiAlibabaApplication.class, args);
}
}

230
黄永兴学习笔记/智能客服案例/SpringAIAlibaba/src/main/java/com/example/springaialibaba/controller/CustomerServiceController.java

@ -0,0 +1,230 @@
package com.example.springaialibaba.controller;
import com.alibaba.cloud.ai.dashscope.agent.DashScopeAgent;
import com.alibaba.dashscope.app.*;
import com.example.springaialibaba.pojo.StreamResponse;
import com.example.springaialibaba.service.SpeechSynthesisService;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.reactivex.Flowable;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.nio.ByteBuffer;
import java.util.Base64;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import reactor.adapter.rxjava.RxJava2Adapter;
import reactor.adapter.rxjava.RxJava3Adapter;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Slf4j
@RestController
@RequiredArgsConstructor //使用RequiredArgsConstructor注解自动注入final类型的字段
@RequestMapping("/customer")
public class CustomerServiceController {
//注入Spring AI Alibaba Agent
//@Autowired
private final DashScopeAgent dashScopeAgent;
private final SpeechSynthesisService speechSynthesisService;
/**
* 智能客服接口非流式同步获取结果
* @param question 用户问题
* @return 知识库标准答案
*/
@GetMapping("/service")
public String customerServiceController(@RequestParam("question") String question) {
try {
/*
DashScopeAgentOptions 是工作流 / 智能体的调用参数它只负责触发工作流不负责修改大模型的推理参数
因为 Spring AI Alibaba 的自动配置在工作流Agent模式下不生效
*/
//
/* ChatResponse response = dashScopeAgent.call(
new Prompt(question, DashScopeAgentOptions.builder()
.appId("df04e1abf24a416c8702260e88863ac4")
.build()
)
);
return response.getResult().getOutput().getText();*/
ApplicationParam param = ApplicationParam.builder()
// 若没有配置环境变量可用百炼API Key将下行替换为.apiKey("sk-xxx")但不建议在生产环境中直接将API Key硬编码到代码中以减少API Key泄露风险
.apiKey(System.getenv("sk-f7d2253302b547c7a2fe257673cb854b"))
.appId("df04e1abf24a416c8702260e88863ac4")
.prompt(question)
.build();
Application application = new Application();
ApplicationResult result = application.call(param);
return result.getOutput().getText();
} catch (Exception e) {
log.error("客服调用失败", e);
return "抱歉,智能客服暂时无法响应,请联系人工客服。";
}
}
// 官方原生流式调用1:1对照文档
@GetMapping(value = "/service2", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> customerService2Controller(
@RequestParam("question") String question,
HttpServletResponse response
) {
//设置字符编码为UTF-8,解决中文乱码问题
response.setCharacterEncoding("UTF-8");
ObjectMapper objectMapper = new ObjectMapper();
AtomicInteger counter = new AtomicInteger(0);
try {
// 官方原版写法
ApplicationParam param = ApplicationParam.builder()
.apiKey("sk-f7d2253302b547c7a2fe257673cb854b")
.appId("df04e1abf24a416c8702260e88863ac4")
.prompt(question)
.flowStreamMode(FlowStreamMode.MESSAGE_FORMAT)
.build();
Application application = new Application();
// 官方流式调用
Flowable<ApplicationResult> resultFlowable = application.streamCall(param);
// RxJava适配器 flowable 转换为 flux Mono是单元素的Flux
return RxJava2Adapter.flowableToFlux(resultFlowable)
.flatMap(res -> {
String content = extractContent(res);
if (content != null && !content.trim().isEmpty()) {
try {
String id = UUID.randomUUID().toString().substring(0, 8);
int seq = counter.incrementAndGet();
String jsonData = objectMapper.writeValueAsString(new StreamResponse(id, seq, content));
return Mono.just(jsonData);
} catch (Exception e) {
log.error("JSON序列化失败", e);
return Mono.empty();
}
}
return Mono.empty();
});
} catch (Exception e) {
log.error("客服调用失败", e);
return Flux.error(e);
}
}
// 语音合成接口 - 单向流式返回音频流数据
@GetMapping(value = "/synthesize", produces = "audio/wav")
public StreamingResponseBody synthesizeSpeech(@RequestParam("text") String text) {
log.info("开始语音合成,文本: {}", text);
return outputStream -> {
CountDownLatch latch = new CountDownLatch(1);
try {
Flux<byte[]> audioFlux = speechSynthesisService.streamSynthesize(text);
audioFlux.subscribe(
bytes -> {
try {
log.info("Controller收到音频数据,大小: {} 字节,写入输出流", bytes.length);
outputStream.write(bytes);
outputStream.flush();
log.info("已刷新输出流");
} catch (Exception e) {
log.error("写入音频数据失败", e);
}
},
error -> {
log.error("音频流处理错误", error);
latch.countDown();
},
() -> {
log.info("音频数据传输完成");
latch.countDown();
}
);
log.info("等待音频流完成...");
latch.await();
log.info("音频流完成,结束请求");
} catch (Exception e) {
log.error("语音合成失败", e);
}
};
}
// SSE推送音频数据接口
@GetMapping(value = "/synthesize-sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> synthesizeSpeechSSE(@RequestParam("text") String text) {
log.info("开始SSE语音合成,文本: {}", text);
Flux<byte[]> audioFlux = speechSynthesisService.streamSynthesize(text);
AtomicInteger seq = new AtomicInteger(0);
return audioFlux
.map(bytes -> {
String base64Data = Base64.getEncoder().encodeToString(bytes);
int currentSeq = seq.incrementAndGet();
String jsonData = String.format("{\"seq\":%d,\"data\":\"%s\",\"size\":%d}",
currentSeq, base64Data, bytes.length);
log.info("SSE推送音频数据块 [{}], 大小: {} 字节", currentSeq, bytes.length);
return "data: " + jsonData + "\n\n";
})
.doOnComplete(() -> {
log.info("SSE音频推送完成");
})
.doOnError(error -> {
log.error("SSE音频推送错误", error);
});
}
// ApplicationResult 中提取内容
private String extractContent(ApplicationResult res) {
if (res == null || res.getOutput() == null) return null;
if (res.getOutput().getWorkflowMessage() != null) {
WorkflowMessage workflowMessage = res.getOutput().getWorkflowMessage();
if (workflowMessage != null && workflowMessage.getMessage() != null) {
String nodeId = workflowMessage.getNodeId();
String content = workflowMessage.getMessage().getContent();
// 区分不同的nodeId
if ("Output_emZG".equals(nodeId)) {
// 流程输出节点返回内容
return content;
} else if ("End_mOtD".equals(nodeId)) {
// 结束节点不返回内容
log.debug("流式传输结束,收到完整结果,不返回");
return null;
} else {
// 其他节点返回内容
return content;
}
}
}
return null;
}
}

97
黄永兴学习笔记/智能客服案例/SpringAIAlibaba/src/main/java/com/example/springaialibaba/controller/Main.java

@ -0,0 +1,97 @@
package com.example.springaialibaba.controller;
import com.alibaba.dashscope.audio.tts.SpeechSynthesisResult;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisAudioFormat;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisParam;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesizer;
import com.alibaba.dashscope.common.ResultCallback;
import com.alibaba.dashscope.utils.Constants;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
class TimeUtils {
private static final DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
public static String getTimestamp() {
return LocalDateTime.now().format(formatter);
}
}
public class Main {
private static String[] textArray = {"流式文本语音合成SDK,",
"可以将输入的文本", "合成为语音二进制数据,", "相比于非流式语音合成,",
"流式合成的优势在于实时性", "更强。用户在输入文本的同时",
"可以听到接近同步的语音输出,", "极大地提升了交互体验,",
"减少了用户等待时间。", "适用于调用大规模", "语言模型(LLM),以",
"流式输入文本的方式", "进行语音合成的场景。"};
private static String model = "cosyvoice-v3-flash"; // 模型
private static String voice = "longanyang"; // 音色
public static void streamAudioDataToSpeaker() {
// 配置回调函数
ResultCallback<SpeechSynthesisResult> callback = new ResultCallback<SpeechSynthesisResult>() {
@Override
public void onEvent(SpeechSynthesisResult result) {
// System.out.println("收到消息: " + result);
if (result.getAudioFrame() != null) {
// 此处实现处理音频数据的逻辑
System.out.println(TimeUtils.getTimestamp() + " 收到音频");
}
}
@Override
public void onComplete() {
System.out.println(TimeUtils.getTimestamp() + " 收到Complete,语音合成结束");
}
@Override
public void onError(Exception e) {
System.out.println("出现异常:" + e.toString());
}
};
// 请求参数
SpeechSynthesisParam param =
SpeechSynthesisParam.builder()
// 新加坡和北京地域的API Key不同获取API Keyhttps://help.aliyun.com/zh/model-studio/get-api-key
// 若没有配置环境变量请用百炼API Key将下行替换为.apiKey("sk-xxx")
.apiKey("sk-f7d2253302b547c7a2fe257673cb854b")
.model(model)
.voice(voice)
.format(SpeechSynthesisAudioFormat
.PCM_22050HZ_MONO_16BIT) // 流式合成使用PCM或者MP3
.build();
SpeechSynthesizer synthesizer = new SpeechSynthesizer(param, callback);
// 带Callback的call方法将不会阻塞当前线程
try {
for (String text : textArray) {
// 发送文本片段在回调接口的onEvent方法中实时获取二进制音频
synthesizer.streamingCall(text);
}
// 等待结束流式语音合成
synthesizer.streamingComplete();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
// 任务结束关闭websocket连接
synthesizer.getDuplexApi().close(1000, "bye");
}
// 首次发送文本时需建立 WebSocket 连接因此首包延迟会包含连接建立的耗时
System.out.println(
"[Metric] requestId为:"
+ synthesizer.getLastRequestId()
+ ",首包延迟(毫秒)为:"
+ synthesizer.getFirstPackageDelay());
}
public static void main(String[] args) {
// 以下为北京地域url若使用新加坡地域的模型需将url替换为wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference
Constants.baseWebsocketApiUrl = "wss://dashscope.aliyuncs.com/api-ws/v1/inference";
streamAudioDataToSpeaker();
System.exit(0);
}
}

118
黄永兴学习笔记/智能客服案例/SpringAIAlibaba/src/main/java/com/example/springaialibaba/controller/SpeechSynthesisController.java

@ -0,0 +1,118 @@
package com.example.springaialibaba.controller;
import com.alibaba.dashscope.audio.tts.SpeechSynthesisParam;
import com.alibaba.dashscope.audio.tts.SpeechSynthesizer;
import com.alibaba.dashscope.audio.tts.SpeechSynthesisResult;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.alibaba.dashscope.utils.Constants;
import io.reactivex.Flowable;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.adapter.rxjava.RxJava2Adapter;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Base64;
@Slf4j
@RestController
@RequestMapping("/speech")
public class SpeechSynthesisController {
private static final String MODEL = "cosyvoice-v3-flash";
private static final String VOICE = "longanyang";
static {
// 北京地域url若使用新加坡地域的模型需将url替换为wss://dashscope-intl.aliyuncs.com/api-ws/v1/inference
Constants.baseWebsocketApiUrl = "wss://dashscope.aliyuncs.com/api-ws/v1/inference";
}
@GetMapping(value = "/synthesize", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public void synthesizeAudio(
@RequestParam("text") String text,
HttpServletResponse response
) throws NoApiKeyException, IOException {
// 设置响应头
response.setContentType("audio/wav");
response.setHeader("Content-Disposition", "attachment; filename=output.wav");
// 请求参数
SpeechSynthesisParam param = SpeechSynthesisParam.builder()
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
.model(MODEL)
.voice(VOICE)
.build();
SpeechSynthesizer synthesizer = new SpeechSynthesizer(param, null);
// 流式获取音频数据
Flowable<SpeechSynthesisResult> resultFlowable = synthesizer.callAsFlowable(text);
// 处理音频数据
OutputStream outputStream = response.getOutputStream();
resultFlowable.blockingForEach(result -> {
if (result.getAudioFrame() != null) {
try {
// 音频数据是Base64编码的需要解码
byte[] audioData = Base64.getDecoder().decode(result.getAudioFrame().getAudio());
outputStream.write(audioData);
outputStream.flush();
log.debug("写入音频数据: {} bytes", audioData.length);
} catch (IOException e) {
log.error("写入音频数据失败", e);
}
}
});
// 关闭连接和输出流
synthesizer.getDuplexApi().close(1000, "bye");
outputStream.close();
// 打印首包延迟
log.info("[Metric] requestId为:{}", synthesizer.getLastRequestId());
log.info("首包延迟(毫秒)为:{}", synthesizer.getFirstPackageDelay());
}
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamAudio(
@RequestParam("text") String text
) throws NoApiKeyException {
// 请求参数
SpeechSynthesisParam param = SpeechSynthesisParam.builder()
.apiKey(System.getenv("DASHSCOPE_API_KEY"))
.model(MODEL)
.voice(VOICE)
.build();
SpeechSynthesizer synthesizer = new SpeechSynthesizer(param, null);
// 流式获取音频数据
Flowable<SpeechSynthesisResult> resultFlowable = synthesizer.callAsFlowable(text);
// 转换为Flux并返回
return RxJava2Adapter.flowableToFlux(resultFlowable)
.flatMap(result -> {
if (result.getAudioFrame() != null) {
String audioBase64 = result.getAudioFrame().getAudio();
return Mono.just("data: " + audioBase64 + "\n\n");
}
return Mono.empty();
})
.doOnTerminate(() -> {
// 关闭连接
synthesizer.getDuplexApi().close(1000, "bye");
log.info("[Metric] requestId为:{}", synthesizer.getLastRequestId());
log.info("首包延迟(毫秒)为:{}", synthesizer.getFirstPackageDelay());
});
}
}

14
黄永兴学习笔记/智能客服案例/SpringAIAlibaba/src/main/java/com/example/springaialibaba/pojo/StreamResponse.java

@ -0,0 +1,14 @@
package com.example.springaialibaba.pojo;
// 流式响应数据结构
public class StreamResponse {
public String id;
public int seq;
public String text;
public StreamResponse(String id, int seq, String text) {
this.id = id;
this.seq = seq;
this.text = text;
}
}

107
黄永兴学习笔记/智能客服案例/SpringAIAlibaba/src/main/java/com/example/springaialibaba/service/AudioGenerationService.java

@ -0,0 +1,107 @@
package com.example.springaialibaba.service;
import com.alibaba.dashscope.audio.tts.SpeechSynthesisResult;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisParam;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesizer;
import com.alibaba.dashscope.common.ResultCallback;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.alibaba.dashscope.utils.Constants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@Service
public class AudioGenerationService {
private static final String MODEL = "cosyvoice-v3-flash";
private static final String VOICE = "longanyang";
static {
// 设置WebSocket API地址北京地域
Constants.baseWebsocketApiUrl = "wss://dashscope.aliyuncs.com/api-ws/v1/inference";
}
/**
* 生成文本的音频数据
* @param text 要转换为音频的文本
* @return 音频数据的Flux流
*/
public Flux<ByteBuffer> generateAudio(String text) {
return Mono.fromCallable(() -> {
try {
SpeechSynthesisParam param = SpeechSynthesisParam.builder()
.apiKey("sk-f7d2253302b547c7a2fe257673cb854b")
.model(MODEL)
.voice(VOICE)
.build();
SpeechSynthesizer synthesizer = new SpeechSynthesizer(param, null);
List<ByteBuffer> audioChunks = new ArrayList<>();
synthesizer.callAsFlowable(text).blockingForEach(result -> {
if (result.getAudioFrame() != null) {
ByteBuffer audioData = ByteBuffer.wrap(result.getAudioFrame());
audioChunks.add(audioData);
log.debug("收到音频数据块,大小: {} bytes", result.getAudioFrame().length);
}
});
synthesizer.getDuplexApi().close(1000, "bye");
log.info("音频生成完成,首包延迟: {} ms", synthesizer.getFirstPackageDelay());
return Flux.fromIterable(audioChunks);
} catch (NoApiKeyException e) {
log.error("API Key错误", e);
return Flux.error(e);
} catch (Exception e) {
log.error("音频生成失败", e);
return Flux.error(e);
}
}).flatMapMany(flux -> flux);
}
/**
* 生成单个音频数据块用于实时流式传输
* @param text 要转换为音频的文本
* @return 音频数据的Mono
*/
public Mono<ByteBuffer> generateAudioChunk(String text) {
return Mono.fromCallable(() -> {
try {
SpeechSynthesisParam param = SpeechSynthesisParam.builder()
.apiKey("sk-f7d2253302b547c7a2fe257673cb854b")
.model(MODEL)
.voice(VOICE)
.build();
SpeechSynthesizer synthesizer = new SpeechSynthesizer(param, null);
ByteBuffer audioData = null;
synthesizer.callAsFlowable(text).blockingForEach(result -> {
if (result.getAudioFrame() != null) {
audioData = ByteBuffer.wrap(result.getAudioFrame());
log.debug("生成音频数据块,大小: {} bytes", result.getAudioFrame().length);
}
});
synthesizer.getDuplexApi().close(1000, "bye");
return audioData;
} catch (Exception e) {
log.error("音频生成失败", e);
throw new RuntimeException("音频生成失败", e);
}
});
}
}

82
黄永兴学习笔记/智能客服案例/SpringAIAlibaba/src/main/java/com/example/springaialibaba/service/SpeechSynthesisService.java

@ -0,0 +1,82 @@
package com.example.springaialibaba.service;
import com.alibaba.dashscope.audio.tts.SpeechSynthesisResult;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesisParam;
import com.alibaba.dashscope.audio.ttsv2.SpeechSynthesizer;
import com.alibaba.dashscope.utils.Constants;
import io.reactivex.Flowable;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import reactor.adapter.rxjava.RxJava2Adapter;
import reactor.core.publisher.Flux;
import java.nio.ByteBuffer;
@Slf4j
@Service
public class SpeechSynthesisService {
private static final String MODEL = "cosyvoice-v3-flash";
private static final String VOICE = "longanyang";
public Flux<byte[]> streamSynthesize(String text) {
try {
Constants.baseWebsocketApiUrl = "wss://dashscope.aliyuncs.com/api-ws/v1/inference";
SpeechSynthesisParam param = SpeechSynthesisParam.builder()
.apiKey("sk-f7d2253302b547c7a2fe257673cb854b")
.model(MODEL)
.voice(VOICE)
.build();
SpeechSynthesizer synthesizer = new SpeechSynthesizer(param, null);
Flowable<SpeechSynthesisResult> resultFlowable = synthesizer.callAsFlowable(text);
Flux<byte[]> audioFlux = RxJava2Adapter.flowableToFlux(resultFlowable)
.doOnNext(result -> {
log.info("收到SpeechSynthesisResult, audioFrame: {}, frameSize: {}",
result.getAudioFrame() != null,
result.getAudioFrame() != null ? result.getAudioFrame().remaining() : 0);
})
.filter(result -> {
boolean hasAudio = result.getAudioFrame() != null;
if (!hasAudio) {
log.info("跳过无音频帧的结果");
}
return hasAudio;
})
.map(result -> {
ByteBuffer buffer = result.getAudioFrame();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
log.info("处理音频数据块,大小: {} 字节", bytes.length);
return bytes;
})
.doOnComplete(() -> {
log.info("语音合成完成,requestId: {}, 首包延迟: {}ms",
synthesizer.getLastRequestId(),
synthesizer.getFirstPackageDelay());
try {
synthesizer.getDuplexApi().close(1000, "bye");
} catch (Exception e) {
log.error("关闭连接失败", e);
}
})
.doOnError(error -> {
log.error("语音合成错误", error);
try {
synthesizer.getDuplexApi().close(1000, "bye");
} catch (Exception e) {
log.error("关闭连接失败", e);
}
});
return audioFlux;
} catch (Exception e) {
log.error("语音合成失败", e);
return Flux.error(e);
}
}
}

14
黄永兴学习笔记/智能客服案例/SpringAIAlibaba/src/main/resources/application.yml

@ -0,0 +1,14 @@
server:
port: 8080
spring:
ai:
dashscope:
# 核心:工作流应用的AppId
agent:
app-id: df04e1abf24a416c8702260e88863ac4
# 核心:你的API-Key
api-key: sk-f7d2253302b547c7a2fe257673cb854b
# 业务空间ID
workspace-id: llm-fuczq7vkh8vyz716

263
黄永兴学习笔记/智能客服案例/SpringAIAlibaba/src/main/resources/static/index.html

@ -0,0 +1,263 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>智能客服 - 文本 + 语音</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 1000px;
margin: 30px auto;
padding: 20px;
}
.container {
border: 1px solid #ddd;
border-radius: 8px;
padding: 30px;
}
h1 {
color: #333;
margin-bottom: 30px;
}
.input-group {
margin-bottom: 20px;
}
textarea {
width: 100%;
height: 100px;
padding: 12px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 4px;
resize: vertical;
box-sizing: border-box;
}
button {
padding: 12px 30px;
font-size: 16px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.output-section {
margin-top: 30px;
border-top: 1px solid #eee;
padding-top: 20px;
}
.output-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 15px;
color: #333;
}
.output-text {
padding: 15px;
background-color: #f8f9fa;
border-radius: 4px;
border: 1px solid #e9ecef;
min-height: 100px;
font-size: 16px;
line-height: 1.8;
color: #333;
white-space: pre-wrap;
}
.status {
margin-top: 20px;
padding: 15px;
background-color: #d1ecf1;
border-radius: 4px;
color: #0c5460;
font-size: 16px;
}
audio {
margin-top: 20px;
width: 100%;
}
.example-btn {
margin-left: 10px;
background-color: #28a745;
}
.example-btn:hover {
background-color: #218838;
}
</style>
</head>
<body>
<div class="container">
<h1>🤖 智能客服 - 文本 + 语音</h1>
<div class="input-group">
<label style="display: block; margin-bottom: 10px; font-size: 16px;">请输入您的问题:</label>
<textarea id="questionInput" placeholder="请输入您的问题,例如:什么是半仓?"></textarea>
</div>
<div class="input-group">
<button id="generateBtn" onclick="askQuestion()">提问</button>
<button class="example-btn" onclick="fillExample()">示例问题</button>
</div>
<div class="status" id="status">
状态:等待提问
</div>
<div class="output-section">
<div class="output-title">📝 回答内容</div>
<div class="output-text" id="outputText">
(回答将在这里显示)
</div>
</div>
<audio id="audioPlayer" controls></audio>
</div>
<script>
const exampleQuestion = "什么是半仓?";
function fillExample() {
document.getElementById('questionInput').value = exampleQuestion;
}
async function askQuestion() {
const question = document.getElementById('questionInput').value.trim();
const statusDiv = document.getElementById('status');
const outputText = document.getElementById('outputText');
const audioPlayer = document.getElementById('audioPlayer');
const generateBtn = document.getElementById('generateBtn');
if (!question) {
alert('请输入您的问题!');
return;
}
generateBtn.disabled = true;
outputText.textContent = '';
statusDiv.textContent = '状态:正在获取回答...';
console.log('开始提问:', question);
let fullText = '';
let eventSource = null;
try {
eventSource = new EventSource(`/customer/service2?question=${encodeURIComponent(question)}`);
eventSource.onmessage = (event) => {
console.log('收到原始数据:', event.data);
const dataStr = event.data;
if (!dataStr || dataStr.trim() === '') {
console.log('跳过空数据');
return;
}
try {
const data = JSON.parse(dataStr);
const text = data.text;
if (text) {
fullText += text;
outputText.textContent += text;
outputText.scrollTop = outputText.scrollHeight;
statusDiv.textContent = `状态:正在获取回答... (已收到 ${fullText.length} 字)`;
console.log('解析到文本:', text);
}
} catch (parseError) {
console.error('JSON解析失败:', parseError, '原始数据:', dataStr);
}
};
eventSource.onerror = (error) => {
console.log('EventSource 连接关闭:', error);
eventSource.close();
if (fullText.length > 0) {
statusDiv.textContent = '状态:回答获取完成,正在生成语音...';
console.log('完整回答:', fullText);
generateAndPlayAudio(fullText);
} else {
statusDiv.textContent = '状态:未获取到回答';
generateBtn.disabled = false;
}
};
} catch (error) {
console.error('获取回答错误:', error);
statusDiv.textContent = `状态:获取回答失败 - ${error.message}`;
generateBtn.disabled = false;
}
}
async function generateAndPlayAudio(text) {
const statusDiv = document.getElementById('status');
const audioPlayer = document.getElementById('audioPlayer');
const generateBtn = document.getElementById('generateBtn');
statusDiv.textContent = '状态:正在生成语音...';
console.log('开始语音合成,文本长度:', text.length);
try {
const url = `/customer/synthesize?text=${encodeURIComponent(text)}`;
audioPlayer.src = url;
audioPlayer.onloadstart = () => {
statusDiv.textContent = '状态:开始接收音频流...';
console.log('开始接收音频流');
};
audioPlayer.onloadeddata = () => {
statusDiv.textContent = '状态:数据加载中,准备播放...';
console.log('音频数据加载完成,可以开始播放');
};
audioPlayer.onplaying = () => {
statusDiv.textContent = '状态:正在播放(流式)';
console.log('开始播放音频');
};
audioPlayer.onwaiting = () => {
statusDiv.textContent = '状态:缓冲中...';
};
audioPlayer.oncanplay = () => {
statusDiv.textContent = '状态:正在播放(流畅)';
};
audioPlayer.onended = () => {
statusDiv.textContent = '状态:播放完成';
generateBtn.disabled = false;
console.log('播放完成');
};
audioPlayer.onerror = (e) => {
console.error('音频播放错误:', e);
statusDiv.textContent = `状态:音频播放失败 - ${audioPlayer.error ? audioPlayer.error.message : '未知错误'}`;
generateBtn.disabled = false;
};
audioPlayer.play();
console.log('已调用 audio.play()');
} catch (error) {
console.error('语音合成错误:', error);
statusDiv.textContent = `状态:出错了 - ${error.message}`;
generateBtn.disabled = false;
}
}
window.onload = () => {
console.log('页面加载完成');
};
</script>
</body>
</html>

13
黄永兴学习笔记/智能客服案例/SpringAIAlibaba/src/test/java/com/example/springaialibaba/SpringAiAlibabaApplicationTests.java

@ -0,0 +1,13 @@
package com.example.springaialibaba;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SpringAiAlibabaApplicationTests {
@Test
void contextLoads() {
}
}

14
黄永兴学习笔记/智能客服案例/SpringAIAlibaba/target/classes/application.yml

@ -0,0 +1,14 @@
server:
port: 8080
spring:
ai:
dashscope:
# 核心:工作流应用的AppId
agent:
app-id: df04e1abf24a416c8702260e88863ac4
# 核心:你的API-Key
api-key: sk-f7d2253302b547c7a2fe257673cb854b
# 业务空间ID
workspace-id: llm-fuczq7vkh8vyz716

BIN
黄永兴学习笔记/智能客服案例/SpringAIAlibaba/target/classes/com/example/springaialibaba/SpringAiAlibabaApplication.class

BIN
黄永兴学习笔记/智能客服案例/SpringAIAlibaba/target/classes/com/example/springaialibaba/controller/CustomerServiceController.class

BIN
黄永兴学习笔记/智能客服案例/SpringAIAlibaba/target/classes/com/example/springaialibaba/controller/Main$1.class

BIN
黄永兴学习笔记/智能客服案例/SpringAIAlibaba/target/classes/com/example/springaialibaba/controller/Main.class

BIN
黄永兴学习笔记/智能客服案例/SpringAIAlibaba/target/classes/com/example/springaialibaba/controller/TimeUtils.class

BIN
黄永兴学习笔记/智能客服案例/SpringAIAlibaba/target/classes/com/example/springaialibaba/pojo/StreamResponse.class

BIN
黄永兴学习笔记/智能客服案例/SpringAIAlibaba/target/classes/com/example/springaialibaba/service/SpeechSynthesisService.class

263
黄永兴学习笔记/智能客服案例/SpringAIAlibaba/target/classes/static/index.html

@ -0,0 +1,263 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>智能客服 - 文本 + 语音</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 1000px;
margin: 30px auto;
padding: 20px;
}
.container {
border: 1px solid #ddd;
border-radius: 8px;
padding: 30px;
}
h1 {
color: #333;
margin-bottom: 30px;
}
.input-group {
margin-bottom: 20px;
}
textarea {
width: 100%;
height: 100px;
padding: 12px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 4px;
resize: vertical;
box-sizing: border-box;
}
button {
padding: 12px 30px;
font-size: 16px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.output-section {
margin-top: 30px;
border-top: 1px solid #eee;
padding-top: 20px;
}
.output-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 15px;
color: #333;
}
.output-text {
padding: 15px;
background-color: #f8f9fa;
border-radius: 4px;
border: 1px solid #e9ecef;
min-height: 100px;
font-size: 16px;
line-height: 1.8;
color: #333;
white-space: pre-wrap;
}
.status {
margin-top: 20px;
padding: 15px;
background-color: #d1ecf1;
border-radius: 4px;
color: #0c5460;
font-size: 16px;
}
audio {
margin-top: 20px;
width: 100%;
}
.example-btn {
margin-left: 10px;
background-color: #28a745;
}
.example-btn:hover {
background-color: #218838;
}
</style>
</head>
<body>
<div class="container">
<h1>🤖 智能客服 - 文本 + 语音</h1>
<div class="input-group">
<label style="display: block; margin-bottom: 10px; font-size: 16px;">请输入您的问题:</label>
<textarea id="questionInput" placeholder="请输入您的问题,例如:什么是半仓?"></textarea>
</div>
<div class="input-group">
<button id="generateBtn" onclick="askQuestion()">提问</button>
<button class="example-btn" onclick="fillExample()">示例问题</button>
</div>
<div class="status" id="status">
状态:等待提问
</div>
<div class="output-section">
<div class="output-title">📝 回答内容</div>
<div class="output-text" id="outputText">
(回答将在这里显示)
</div>
</div>
<audio id="audioPlayer" controls></audio>
</div>
<script>
const exampleQuestion = "什么是半仓?";
function fillExample() {
document.getElementById('questionInput').value = exampleQuestion;
}
async function askQuestion() {
const question = document.getElementById('questionInput').value.trim();
const statusDiv = document.getElementById('status');
const outputText = document.getElementById('outputText');
const audioPlayer = document.getElementById('audioPlayer');
const generateBtn = document.getElementById('generateBtn');
if (!question) {
alert('请输入您的问题!');
return;
}
generateBtn.disabled = true;
outputText.textContent = '';
statusDiv.textContent = '状态:正在获取回答...';
console.log('开始提问:', question);
let fullText = '';
let eventSource = null;
try {
eventSource = new EventSource(`/customer/service2?question=${encodeURIComponent(question)}`);
eventSource.onmessage = (event) => {
console.log('收到原始数据:', event.data);
const dataStr = event.data;
if (!dataStr || dataStr.trim() === '') {
console.log('跳过空数据');
return;
}
try {
const data = JSON.parse(dataStr);
const text = data.text;
if (text) {
fullText += text;
outputText.textContent += text;
outputText.scrollTop = outputText.scrollHeight;
statusDiv.textContent = `状态:正在获取回答... (已收到 ${fullText.length} 字)`;
console.log('解析到文本:', text);
}
} catch (parseError) {
console.error('JSON解析失败:', parseError, '原始数据:', dataStr);
}
};
eventSource.onerror = (error) => {
console.log('EventSource 连接关闭:', error);
eventSource.close();
if (fullText.length > 0) {
statusDiv.textContent = '状态:回答获取完成,正在生成语音...';
console.log('完整回答:', fullText);
generateAndPlayAudio(fullText);
} else {
statusDiv.textContent = '状态:未获取到回答';
generateBtn.disabled = false;
}
};
} catch (error) {
console.error('获取回答错误:', error);
statusDiv.textContent = `状态:获取回答失败 - ${error.message}`;
generateBtn.disabled = false;
}
}
async function generateAndPlayAudio(text) {
const statusDiv = document.getElementById('status');
const audioPlayer = document.getElementById('audioPlayer');
const generateBtn = document.getElementById('generateBtn');
statusDiv.textContent = '状态:正在生成语音...';
console.log('开始语音合成,文本长度:', text.length);
try {
const url = `/customer/synthesize?text=${encodeURIComponent(text)}`;
audioPlayer.src = url;
audioPlayer.onloadstart = () => {
statusDiv.textContent = '状态:开始接收音频流...';
console.log('开始接收音频流');
};
audioPlayer.onloadeddata = () => {
statusDiv.textContent = '状态:数据加载中,准备播放...';
console.log('音频数据加载完成,可以开始播放');
};
audioPlayer.onplaying = () => {
statusDiv.textContent = '状态:正在播放(流式)';
console.log('开始播放音频');
};
audioPlayer.onwaiting = () => {
statusDiv.textContent = '状态:缓冲中...';
};
audioPlayer.oncanplay = () => {
statusDiv.textContent = '状态:正在播放(流畅)';
};
audioPlayer.onended = () => {
statusDiv.textContent = '状态:播放完成';
generateBtn.disabled = false;
console.log('播放完成');
};
audioPlayer.onerror = (e) => {
console.error('音频播放错误:', e);
statusDiv.textContent = `状态:音频播放失败 - ${audioPlayer.error ? audioPlayer.error.message : '未知错误'}`;
generateBtn.disabled = false;
};
audioPlayer.play();
console.log('已调用 audio.play()');
} catch (error) {
console.error('语音合成错误:', error);
statusDiv.textContent = `状态:出错了 - ${error.message}`;
generateBtn.disabled = false;
}
}
window.onload = () => {
console.log('页面加载完成');
};
</script>
</body>
</html>

0
黄永兴学习笔记/智能客服案例/SpringAIAlibaba/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst

4
黄永兴学习笔记/智能客服案例/SpringAIAlibaba/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst

@ -0,0 +1,4 @@
C:\Users\Mayn\IdeaProjects\StudySpringAI\StudySpringAI\SpringAIAlibaba\src\main\java\com\example\springaialibaba\controller\CustomerServiceController.java
C:\Users\Mayn\IdeaProjects\StudySpringAI\StudySpringAI\SpringAIAlibaba\src\main\java\com\example\springaialibaba\controller\Main.java
C:\Users\Mayn\IdeaProjects\StudySpringAI\StudySpringAI\SpringAIAlibaba\src\main\java\com\example\springaialibaba\service\SpeechSynthesisService.java
C:\Users\Mayn\IdeaProjects\StudySpringAI\StudySpringAI\SpringAIAlibaba\src\main\java\com\example\springaialibaba\SpringAiAlibabaApplication.java

BIN
黄永兴学习笔记/智能客服案例/SpringAIAlibaba/target/test-classes/com/example/springaialibaba/SpringAiAlibabaApplicationTests.class

Loading…
Cancel
Save