历史文章

Spring AI:对接DeepSeek实战
Spring AI:对接官方 DeepSeek-R1 模型 —— 实现推理效果
Spring AI:ChatClient实现对话效果
Spring AI:使用 Advisor 组件 - 打印请求大模型出入参日志
Spring AI:ChatMemory 实现聊天记忆功能
Spring AI:本地安装 Ollama 并运行 Qwen3 模型
Spring AI:提示词工程
Spring AI:提示词工程 - Prompt 角色分类(系统角色与用户角色)
Spring AI:基于 “助手角色” 消息实现聊天记忆功能
Spring AI:结构化输出 - 大模型响应内容
Spring AI:Docker 安装 Cassandra 5.x(限制内存占用)&& CQL
Spring AI:整合 Cassandra - 实现聊天消息持久化
Spring AI:多模态 AI 大模型
Spring AI:文生图:调用通义万相 AI 大模型
Spring AI:文生音频 - cosyvoice-V2
Spring AI:文生视频 - wanx2.1-i2v-plus
Spring AI:上手体验工具调用(Tool Calling)
Spring AI:整合 MCP Client - 调用高德地图 MCP 服务
Spring AI:搭建自定义 MCP Server:获取 QQ 信息
Spring AI:对接自定义 MCP Server
Spring AI:RAG 增强检索介绍
Spring AI:Docker 安装向量数据库 - Redis Stack
Spring AI:文档向量化存储与检索

上文中,我们在项目初始化时,手动创建了 3 条 Document 文档,然后存储到了 Redis 向量数据库中。

但是实际应用场景中,可能需要读取不同格式的文档,如 txt 、Json 、Markdown 、Html 、Pdf 等等。本文中,我们就来尝试从不同格式文件读取数据,并转换为 Document 文档。

提取 txt 文件

在 /resources 资源目录下,创建一个 /document 文件夹,用于放置本不同格式的数据源文件。

先在里面创建一个名为 manual.txt 的冰箱说明书文件,内容如下:

海尔BCD-520WDPD对开门冰箱使用说明书

1. 安全注意事项
- 请将冰箱放置在通风良好、远离热源的位置
- 确保电源电压与冰箱额定电压(220V~50Hz)相符
- 新冰箱首次使用前应静置2小时以上再通电

2. 部件说明
① 冷藏室(4-8℃)
② 变温室(-3~4℃可调)
③ 冷冻室(-18℃以下)
④ 智能控制面板
⑤ 可调节搁架

3. 基本操作
- 温度设置:
  短按"温区选择"键切换温区 → 按"温度调节"键设置(冷藏:2-8℃,冷冻:-16~-24℃)
- 速冷功能:冷藏室温度快速降至2℃(持续6小时后自动退出)
- 速冻功能:冷冻室快速制冷(持续24小时后自动退出)

4. 日常使用建议
✓ 热食应冷却至室温后再放入
✓ 食物存放请保留1cm以上间隙保证冷气循环
✓ 每月清洁一次密封条(用中性清洁剂+软布)
✓ 长期不用时应断电、除霜、清洁并保持门体微开

5. 故障处理
◆ 异常报警音:
  - 连续3声:门未关严(检查门封)
  - 连续6声:传感器故障(联系售后)
◆ 冷藏室结霜:检查门封密封性
◆ 噪音过大:确认冰箱是否放置平稳

6. 技术参数
额定功率:120W
总容积:520L(冷藏室326L+冷冻室194L)
噪音值:38dB
能效等级:1级
净重:98kg

(本产品符合GB 4706.1和GB 4706.13国家标准)

接着,如下图所示,新建一个 /reader 包,用于放置读取不同格式文件的阅读器类。先在包内创建一个 MyTextReader 阅读器,用于读取 txt 文件:

@Component
public class MyTextReader {

    @Value("classpath:/document/manual.txt")
    private Resource resource;

    /**
     * 读取 Txt 文档
     * @return
     */
    public List<Document> loadText() {
        // 创建 TextReader 对象,用于读取指定资源 (resource) 的文本内容
        TextReader textReader = new TextReader(resource);
        // 添加自定义元数据,如文件名称
        textReader.getCustomMetadata()
                .put("filename", "manual.txt");
        // 读取并转换为 Document 文档集合
        return textReader.read();
    }

    /**
     * 读取 Txt 文档并分块拆分
     * @return
     */
    public List<Document> loadTextAndSplit() {
        // 创建 TextReader 对象,用于读取指定资源 (resource) 的文本内容
        TextReader textReader = new TextReader(resource);

        // 将资源内容解析为 Document 对象集合
        List<Document> documents = textReader.get();

        // 使用 TokenTextSplitter 对文档列表进行分块处理
        List<Document> splitDocuments = new TokenTextSplitter().apply(documents);

        // 返回拆分后的文档分块集合
        return splitDocuments;
    }
}

解释一下上述代码:

  • 通过 @Value(“classpath:/document/manual.txt”) 注解,读取 /resources 目录下的 manual.txt , 并注入到 Resource 资源类中;

  • 定义 loadText() 方法 : 读取 txt 文件,转换为 Document 文档集合,在不做任何中间处理的情况下,集合大小为 1, 是将整个文本作为一条文档返回;

  • 定义 loadTextAndSplit() : 对于体积非常大的 txt 文件,你可能需要将文本拆分为更小的块,例如使用像 TokenTextSplitter 这样的文本分割器;

为了测试一下效果,我们在 /controller 包下,新建一个 ReaderController 控制器,并声明 /read/txt 和 /read/txt2 两个接口,分别测试一下上述阅读器中定义的两个方法:

@RestController
@RequestMapping("/read")
public class ReaderController {

    @Resource
    private MyTextReader textReader;

    @GetMapping(value = "/txt")
    public List<Document> readText() {
        return textReader.loadText();
    }

    @GetMapping(value = "/txt2")
    public List<Document> readText2() {
        return textReader.loadTextAndSplit();
    }

}

重启后端项目,先请求 /read/txt 接口,观察返回的 Document 文档数据,效果如下:

在这里插入图片描述
然后是请求 /read/txt2 接口,测试一下文档分块效果,如下:

在这里插入图片描述

读取 Json 文件

在 /document 文件下,再新建一个 tv.json 文件:

[
  {
    "id": 1,
    "title": "狂飙",
    "description": "2023年现象级扫黑刑侦剧,讲述刑警与黑恶势力长达二十年的生死较量,张译张颂文双雄对决引爆全网。"
  },
  {
    "id": 2,
    "title": "繁花",
    "description": "王家卫执导的上海商战年代剧,胡歌演绎90年代股市浮沉,沪语对白与胶片质感掀起怀旧热潮。"
  },
  {
    "id": 3,
    "title": "长相思",
    "description": "古装神话爆款,杨紫张晚意演绎上古爱恨纠葛,唯美虐恋单日播放量破亿,登顶年度仙侠TOP。"
  },
  {
    "id": 4,
    "title": "庆余年第二季",
    "description": "权谋神剧续作,张若昀陈道明朝堂博弈,开播即打破平台热度纪录,反转剧情引爆社交媒体。"
  }
]

然后,在 /reader 包下,新建一个 MyJsonReader 阅读器,代码如下:

@Component
public class MyJsonReader {

    @Value("classpath:/document/tv.json")
    private Resource resource;

    /**
     * 读取 Json 文件
     * @return
     */
    public List<Document> loadJson() {
        // 创建 JsonReader 阅读器实例,配置需要读取的字段
        JsonReader jsonReader = new JsonReader(resource, "description", "content", "title");
        // 执行读取操作,并转换为 Document 对象集合
        return jsonReader.get();
    }

}

接着,编辑 ReaderController 控制器,声明一个 /read/json 接口,新增代码如下:

@RestController
@RequestMapping("/read")
public class ReaderController {

	// 省略...
	
    @Resource
    private MyJsonReader jsonReader;

	// 省略...

    @GetMapping(value = "/json")
    public List<Document> readJson() {
        return jsonReader.loadJson();
    }

}

重启后端项目,浏览器请求上述接口,效果如下:

在这里插入图片描述

读取 Markdown

再来试试读取 Markdown 格式的文档。编辑 pom.xml , 添加如下依赖:

<!-- 读取 Markdown -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-markdown-document-reader</artifactId>
</dependency>

添加完成后,记得刷新一下 Maven 依赖,将包下载到本地仓库中。

接着,在 /document 文件夹下,新建一个 code.md 文件,文档内容如下:

以下是一个自定义安全配置类的 Java 实现:

package com.example.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/public/**").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .permitAll()
            );
        
        return http.build();
    }
}


在安全配置中,`HttpSecurity` 对象允许我们定义细粒度的访问控制规则,这种声明式配置方式比传统的 XML 配置更简洁。

要测试安全配置,可使用以下 curl 命令模拟登录请求:


curl -X POST http://localhost:8080/login \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "username=admin&password=secret"


> **注意**:实际部署时应使用加密密码存储,示例中为简化展示使用明文

继续在 /reader 包下,新建一个 MyMarkdownReader 阅读器,代码如下:

@Component
public class MyMarkdownReader {

    @Value("classpath:/document/code.md")
    private Resource resource;

    public List<Document> loadMarkdown() {
        // MarkdownDocumentReader 阅读器配置类
        MarkdownDocumentReaderConfig config = MarkdownDocumentReaderConfig.builder()
                .withHorizontalRuleCreateDocument(true) // 遇到水平线 ---,则创建新文档
                .withIncludeCodeBlock(false) // 排除代码块(代码块生成单独文档)
                .withIncludeBlockquote(false) // 排除块引用(块引用生成单独文档)
                .withAdditionalMetadata("filename", "code.md") // 添加自定义元数据,如文件名称
                .build();

        // 新建 MarkdownDocumentReader 阅读器
        MarkdownDocumentReader reader = new MarkdownDocumentReader(resource, config);
        
        // 读取并转换为 Document 文档集合
        return reader.get();
    }
}

编辑 ReaderController 控制器,声明一个 /read/md 接口,新增代码如下:

@RestController
@RequestMapping("/read")
public class ReaderController {

    // 省略...
    
    @Resource
    private MyMarkdownReader markdownReader;

    // 省略...

    @GetMapping(value = "/md")
    public List<Document> readMarkdown() {
        return markdownReader.loadMarkdown();
    }

}

重启后端项目,浏览器请求上面的接口。

读取 Html

再来试试读取 HTML 格式文档。编辑 pom.xml, 添加如下依赖:

<!-- 读取 HTML -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-jsoup-document-reader</artifactId>
</dependency>

添加完成后,记得刷新一下 Maven 依赖。

紧接着,在 /document 文件夹中,新增一个名为 my-page.html 的文件,内容如下:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>我的网页</title>
    <meta name="description" content="Spring AI示例网页">
    <meta name="keywords" content="spring, ai, html, 示例">
    <meta name="author" content="犬小哈">
    <meta name="date" content="2025-01-15">
    <link rel="stylesheet" href="style.css">
</head>
<body>
<header>
    <h1>欢迎来到我的网页</h1>
</header>
<nav>
    <ul>
        <li><a href="/">首页</a></li>
        <li><a href="/about">关于</a></li>
    </ul>
</nav>
<article>
    <h2>主要内容</h2>
    <p>这是我的网页的主要内容。</p>
    <p>它包含多个段落。</p>
    <a href="https://www.example.com">外部链接</a>
</article>
<footer>
    <p>© 2025 小马</p>
</footer>
</body>
</html>

在 /reader 包下,新建一个 MyHtmlReader 阅读器,代码如下:

@Component
public class MyHtmlReader {

    @Value("classpath:/document/my-page.html")
    private Resource resource;

    public List<Document> loadHtml() {
        // JsoupDocumentReader 阅读器配置类
        JsoupDocumentReaderConfig config = JsoupDocumentReaderConfig.builder()
                .selector("article p") // 提取 <article> 标签内的 p 段落
                .charset("UTF-8") // 使用 UTF-8 编码
                .includeLinkUrls(true) // 在元数据中包含链接 URL(绝对链接)
                .metadataTags(List.of("author", "date")) // 提取 author 和 date 元标签
                .additionalMetadata("source", "my-page.html") // 添加自定义元数据
                .build();

        // 新建 JsoupDocumentReader 阅读器
        JsoupDocumentReader reader = new JsoupDocumentReader(resource, config);

        // 读取并转换为 Document 文档集合
        return reader.get();
    }
}

编辑 ReaderController 控制器,声明 /read/html 接口,新增代码如下:

@RestController
@RequestMapping("/read")
public class ReaderController {

    // 省略...
    
    @Resource
    private MyHtmlReader htmlReader;

    // 省略...

    @GetMapping(value = "/html")
    public List<Document> readHtml() {
        return htmlReader.loadHtml();
    }

}

重启后端项目,浏览器请求接口。

读取 Pdf

最后再来试试读取 Pdf 格式文件。编辑 pom.xml , 添加如下依赖:

<!-- 读取 PDF -->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-pdf-document-reader</artifactId>
</dependency>

准备一个pdf文件

下载完成后,将其复制到 /document 文件夹下,并重命名为 profile.pdf 。

在 /reader 包下,新建一个 MyPdfReader 阅读器,代码如下:

@Component
public class MyPdfReader {

    public List<Document> getDocsFromPdf() {
        // 新建 PagePdfDocumentReader 阅读器
        PagePdfDocumentReader pdfReader = new PagePdfDocumentReader("classpath:/document/profile.pdf", // PDF 文件路径
                PdfDocumentReaderConfig.builder()
                        .withPageTopMargin(0) // 设置页面顶边距为0
                        .withPageExtractedTextFormatter(ExtractedTextFormatter.builder()
                                .withNumberOfTopTextLinesToDelete(0) // 设置删除顶部文本行数为0
                                .build())
                        .withPagesPerDocument(1) // 设置每个文档包含1页
                        .build());

        // 读取并转换为 Document 文档集合
        return pdfReader.read();
    }
}

编辑 ReaderController 控制器,声明一个 /read/pdf 接口,新增代码如下:

@RestController
@RequestMapping("/read")
public class ReaderController {

    // 省略...
    
    @Resource
    private MyPdfReader pdfReader;

    // 省略...

    @GetMapping(value = "/pdf")
    public List<Document> readPdf() {
        return pdfReader.getDocsFromPdf();
    }

}

重启后端项目,浏览器请求上述接口,观察返回的文档集合。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐