Java对接DeepSeek

Java对接DeepSeek

一,注册DeepSeek API Key

1,访问DeepSeek官网并进入【API开发平台】

在浏览器地址https://www.deepseek.com/官网地址,

点击使用API开发平台

2,注册/登录DeepSeek开发平台

3,进入API keys管理界面并创建key

4,充值账户余额

二,创建项目

pom.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.sin</groupId>
    <artifactId>DemoModel</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>

        <!-- ========== 1. Java 工具库 ========== -->
        <!-- 说明:
             作用:Hutool 是一个 Java 工具库,提供了大量静态方法,简化了文件、IO、网络、JSON、
                  加密、日期、集合等常见操作。使用 "hutool-all" 可获得全部模块。
             为什么用这个版本:5.7.20 是 2021 年的稳定版本,与项目其他依赖(如 POI 4.1.2、
                  Elasticsearch 7.14.0)发布时间相近,兼容性好。若需新特性可升级至 5.8.x。
             典型使用场景:
               - JSON 处理(JSONObject / JSONArray)------ 如您前面的代码所示。
               - 字符串工具(StrUtil)、集合工具(CollUtil)。
               - HTTP 客户端(HttpUtil)可替代原生 HttpURLConnection。
               - 文件读写(FileUtil)、加密(SecureUtil)等。
             注意事项:引入所有模块可能增加包体积,但开发便利性更高;若只需 JSON 模块,可替换为
               hutool-json 以减小依赖。
        -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.20</version>
        </dependency>


        <!-- ========== 2. Apache POI - Office 文档读写 ========== -->
        <!-- 说明:
             作用:POI 是 Java 操作 Microsoft Office 格式文件的开源库。poi-ooxml 模块专门处理
                    Office 2007+ 的 XML 格式(.xlsx, .docx, .pptx)。
            为什么用这个版本:4.1.2 是 2020 年发布的稳定版本,支持 Office Open XML 格式完整操作,
                    与 Elasticsearch 7.14.0 和 Hutool 5.7.20 无已知冲突。更新版本(5.x)已发布,
                    但可能引入模块拆分变化,需评估兼容性。
            典型使用场景:
                - 从 Excel (.xlsx) 读取数据批量导入系统。
                - 生成 Excel 报表(支持样式、公式、图表)。
                - 读取 Word 模板并填充内容生成文档。
                - 与 AI 模型结合:例如将大模型生成的内容写入 Excel 或 Word 文档。
                注意:poi-ooxml 会自动传递依赖 poi 和 poi-ooxml-schemas,无需单独引入。
                建议:处理大文件时注意内存使用,可配合 SAX 模式(XSSFReader)或升级到 5.x 使用
                streaming 模块。
        -->
            <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi-ooxml</artifactId>
                <version>4.1.2</version>
            </dependency>


        <!-- ========== 3. Java 核心客户端库 ========== -->
        <!-- 说明:
           作用:Elasticsearch 的 Java 核心客户端库,用于与 Elasticsearch 集群通信,执行
                索引、搜索、聚合等操作。注意:这是 Transport Client 的依赖(7.x 中尚可用),
                 但 Elasticsearch 官方从 7.x 开始推荐使用 Java High Level REST Client 或
                 新的 Java API Client (8.x+)。此依赖包本身包含低级和高级客户端的部分代码。
           为什么用这个版本:7.14.0 是 2021 年发布的稳定版本,与您的 POI 和 Hutool 版本年代
                接近,兼容性风险低。同时,7.x 是广泛使用的版本,很多生产环境仍在使用。
           典型使用场景:
             - 将大模型生成的文本内容存储到 Elasticsearch 中,构建知识库或搜索索引。
             - 从 ES 检索相关文档作为上下文,输入给大模型(检索增强生成,RAG)。
             - 对用户对话历史进行向量化存储与相似度检索。
           注意事项:
             - 若您的 ES 服务器版本是 7.14.0,此依赖匹配;若版本不同,建议客户端与服务端
               主要版本一致(如服务端 7.17.x 也可用 7.14.0 客户端)。
             - 从 7.15 开始,官方逐步废弃 TransportClient,建议迁移到
               `elasticsearch-rest-high-level-client`。
             - 使用此依赖时,通常还需要引入 `org.elasticsearch.client:elasticsearch-rest-high-level-client`
               以获得更友好的 REST API 封装。
           替代方案:对于新项目,推荐使用 `co.elastic.clients:elasticsearch-java` (8.x+)
                配合 Jackson 或 JSON-B。
      -->
        <dependency>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
            <version>7.14.0</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>


        <!-- 旧版 .ppt 用(HSLFSlideShow) -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-scratchpad</artifactId>
            <version>4.1.2</version>
        </dependency>

        <!-- Apache Commons IO,POI 5.2.x 解析 .ppt 时必须 -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.12.0</version>
        </dependency>

        <!-- Apache POI 核心(必须显式引入,ThreadLocalUtil 在这里) -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>4.1.2</version>
        </dependency>

    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.7.9</version>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <!--配置阿里云仓库下载-->
    <repositories>
        <repository>
            <id>nexus-aliyun</id>
            <name>nexus-aliyun</name>
            <url>https://maven.aliyun.com/nexus/content/groups/public/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>public</id>
            <name>nexus-aliyun</name>
            <url>https://maven.aliyun.com/nexus/content/groups/public/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>
</project>

对接DeepSeek API进行对话

java 复制代码
package com.sin; // 声明当前类所在的包,包名通常与目录结构对应

// 导入 Hutool 工具库中的 JSON 相关类,用于方便地构建和解析 JSON

import cn.hutool.json.JSONArray;   // 表示 JSON 数组
import cn.hutool.json.JSONObject;  // 表示 JSON 对象
import cn.hutool.json.JSONUtil;    // 提供 JSON 字符串与对象之间的转换工具

// 导入 Java 原生 IO 和网络相关类
import java.io.BufferedReader;     // 带缓冲的字符输入流,用于高效读取响应内容
import java.io.InputStream;        // 字节输入流,用于获取响应体
import java.io.InputStreamReader;  // 将字节流转换为字符流,并指定编码
import java.io.OutputStream;       // 字节输出流,用于发送请求体
import java.net.HttpURLConnection; // Java 原生的 HTTP 连接类
import java.net.URL;               // 表示统一资源定位符
import java.nio.charset.StandardCharsets; // 提供标准字符集常量,如 UTF-8

/**
 * @createTime 2026/9/14 10:44
 * @createAuthor SIN
 * @use DeepSeek 大模型 API 调用示例
 * 功能:发送一条信息,完整接收 AI 返回的 JSON 数据并打印
 */
public class DemoModel1 {


    // ==================== 常量定义 ====================

    // 1. API Key(DeepSeek 的 key 以 sk- 开头)
    // 注意:生产环境请勿硬编码在代码中!建议使用环境变量、配置中心或密钥管理服务。
    private static final String API_KEY = "sk-360a513df*************af38c53";

    // 2. DeepSeek API 地址
    // DeepSeek 的聊天补全接口与 OpenAI 格式兼容,完整端点为:
    // https://api.deepseek.com/chat/completions
    private static final String API_URL = "https://api.deepseek.com/chat/completions";

    /**
     * 程序入口方法
     *
     * @param args 命令行参数(本示例未使用)
     */
    public static void main(String[] args) {
        try {
            // ==================== 第一步:构建请求体 JSON ====================

            // 创建最外层的 JSON 对象,用于承载整个请求体
            JSONObject requestBody = new JSONObject();

            // 指定要调用的模型名称
            // deepseek-chat 是 DeepSeek 的通用对话模型
            // 如果需要推理模型,可以改为 deepseek-reasoner
            requestBody.put("model", "deepseek-chat");

            // 是否使用流式输出
            // false 表示一次性返回完整结果;true 表示以 SSE 流的方式逐块返回
            // 这里为了简化,设为 false,方便一次性读取完整 JSON
            requestBody.put("stream", false);

            // 构建 messages 数组,用于存放对话历史
            // DeepSeek 要求 messages 是一个数组,每个元素包含 role 和 content
            JSONArray messages = new JSONArray();

            // 创建一条用户消息对象
            JSONObject userMessage = new JSONObject();
            // 指定消息角色为 "user",表示这是用户发送的内容
            userMessage.put("role", "user");

            // 定义用户提问内容
            String question = "你好";
            System.out.println("用户向大模型提出的问题是:" + question);
            // 将问题文本放入 content 字段
            userMessage.put("content", question);

            // 将这条用户消息添加到 messages 数组中
            messages.put(userMessage);
            // 将 messages 数组放入最外层的请求体对象中
            requestBody.put("messages", messages);

            // 此时 requestBody 的结构大致为:
            // {
            //   "model": "deepseek-chat",
            //   "stream": false,
            //   "messages": [
            //     { "role": "user", "content": "你好" }
            //   ]
            // }

            // ==================== 第二步:建立 HTTP 连接 ====================

            // 根据 API 地址创建 URL 对象
            URL url = new URL(API_URL);
            // 打开连接,并强制转换为 HttpURLConnection,以便设置请求方法和请求头
            HttpURLConnection con = (HttpURLConnection) url.openConnection();

            // 设置请求方法为 POST(DeepSeek 聊天接口要求 POST)
            con.setRequestMethod("POST");

            // 设置请求头 Content-Type,告诉服务器请求体的格式是 JSON,并使用 UTF-8 编码
            // 注意:原代码中曾拼写成 appliaction/json,这里是正确写法
            con.setRequestProperty("Content-Type", "application/json; charset=UTF-8");

            // 设置认证请求头
            // DeepSeek 使用 Bearer Token 认证,格式为:Authorization: Bearer <API_KEY>
            // 注意 Bearer 与 Key 之间必须有一个空格
            con.setRequestProperty("Authorization", "Bearer " + API_KEY);

            // 允许向连接中写入数据(发送请求体)
            con.setDoOutput(true);
            // 允许从连接中读取数据(接收响应体)
            con.setDoInput(true);

            // ==================== 第三步:发送请求 ====================

            // 获取输出流,并使用 try-with-resources 确保流自动关闭
            try (OutputStream os = con.getOutputStream()) {
                // 将 JSON 对象转换为字符串,再按 UTF-8 编码为字节数组,写入输出流
                os.write(requestBody.toString().getBytes(StandardCharsets.UTF_8));
                // 刷新缓冲区,确保所有数据立即发送到服务器
                os.flush();
            }

            // ==================== 第四步:获取并读取响应 ====================

            // 获取 HTTP 响应状态码
            // 常见状态码:200:成功。401:认证失败。402:余额不足。429:请求过多。500:服务器错误
            int responseCode = con.getResponseCode();
            System.out.println("HTTP响应码为:" + responseCode);

            // 根据响应码选择输入流:
            // 如果状态码是 2xx,表示成功,使用 getInputStream() 读取正常响应体;
            // 否则使用 getErrorStream() 读取错误信息,避免抛出 IOException。
            InputStream inputStream = (responseCode >= 200 && responseCode < 300) ? con.getInputStream() : con.getErrorStream();

            // 使用 BufferedReader 按行读取响应内容,并拼接成完整的字符串
            StringBuilder responseSb = new StringBuilder();

            // try-with-resources 语法:括号内声明的资源会在 try 代码块结束后自动关闭,
            try (BufferedReader reader = new BufferedReader(
                    // InputStreamReader 的作用是将字节流转换为字符流。
                    // 第一个参数 inputStream 是原始的字节输入流,
                    // 第二个参数 StandardCharsets.UTF_8 指定使用 UTF-8 字符集解码字节,
                    // 这是为了正确读取中文等多字节字符,避免使用系统默认编码导致乱码。
                    new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {

                // 声明一个字符串 line,用于临时存放每次读取到的一行文本
                String line;

                // 循环读取每一行,直到流末尾。

                while ((line = reader.readLine()) != null) {

                    // 将当前读取到的这一行内容追加到 StringBuilder 中。
                    // 注意:readLine() 已经去掉了行尾的换行符,所以拼接后的字符串不会保留换行。
                    // 对于 JSON 响应来说,这通常没有问题,因为 JSON 解析器不依赖换行符,
                    // 而且 JSON 字符串内部的换行会以转义形式 \n 存在,不会被 readLine() 切掉。
                    responseSb.append(line);
                }
            }


            // 得到完整的响应 JSON 字符串
            String responseBody = responseSb.toString();
            System.out.println("完整响应 JSON:");
            System.out.println(responseBody);

            // ==================== 第五步:解析 AI 回答 ====================

            // 仅当 HTTP 状态码为 200 时,才认为请求成功,进行 JSON 解析
            if (responseCode == 200) {
                // 使用 Hutool 的 JSONUtil 将响应字符串解析为 JSONObject
                JSONObject jsonResponse = JSONUtil.parseObj(responseBody);

                // 获取 choices 数组,DeepSeek 的响应结构与 OpenAI 一致
                // choices 是一个数组,通常只有一个元素,代表模型生成的一个回复
                JSONArray choices = jsonResponse.getJSONArray("choices");

                if (choices != null && !choices.isEmpty()) {
                    // 取出第一个 choice 对象
                    JSONObject firstChoice = choices.getJSONObject(0);
                    // 从 choice 中获取 message 对象
                    JSONObject message = firstChoice.getJSONObject("message");
                    // 从 message 中获取 content 字段,即 AI 生成的文本
                    String content = message.getStr("content");
                    System.out.println("\n=== AI 回答 ===");
                    System.out.println(content);
                } else {
                    // 如果 choices 为空,说明响应结构异常
                    System.out.println("未找到 choices 字段");
                }
            } else {
                // 非 200 响应,提示用户检查 API Key 或余额
                System.out.println("请求失败,请检查 API Key 或余额。");
            }

        } catch (Exception e) {
            // 捕获所有异常并打印,便于调试
            System.out.println("异常信息:" + e);
            e.printStackTrace();
        }
    }
}

对接DeepSeek API生成Excel数据表

java 复制代码
package com.sin;

import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;

/**
 * @createTime 2026/9/17 8:37
 * @createAuthor SIN
 * @use 对接DeepSeek API 生成Excel表
 */
public class DemoModel2 {


    // ==================== 常量定义 ==================== //

    // 1. API Key(DeepSeek 的 key 以 sk- 开头)
    private static final String API_KEY = "sk-360a513df*************af38c53";

    // 2. DeepSeek API 地址(聊天补全接口,与 OpenAI 格式兼容)
    private static final String API_URL = "https://api.deepseek.com/chat/completions";

    // 3. 生成的 Excel 文件路径(相对项目根目录)
    private static final String EXCEL_PATH = "E:\\***\\***\\****\\AI生成的表格.xlsx";
    /**
     * 程序入口方法
     *
     * @param args 命令行参数(本示例未使用)
     */
    public static void main(String[] args) {
        try {

            // ==================== 第一步:构建请求体 JSON ====================

            // 创建最外层的 JSON 对象,用于承载整个请求体
            JSONObject requestBody = new JSONObject();

            // 指定要调用的模型名称
            // deepseek-chat 是 DeepSeek 的通用对话模型
            // 如果需要推理模型,可以改为 deepseek-reasoner
            requestBody.put("model", "deepseek-chat");

            // 是否使用流式输出
            // false 表示一次性返回完整结果;true 表示以 SSE 流的方式逐块返回
            // 这里为了简化,设为 false,方便一次性读取完整 JSON
            requestBody.put("stream", false);

            // 低温度可以让模型输出更稳定、格式更可控,适合"要 JSON 不要废话"的场景
            requestBody.put("temperature", 0.2);

            // 构建 messages 数组,用于存放对话历史
            JSONArray messages = new JSONArray();

            // ---------- 系统消息:约束模型的角色与输出格式 ----------
            JSONObject systemMessage = new JSONObject();
            systemMessage.put("role", "system");
            systemMessage.put("content", // 开始设置 content 字段(一段规则文本)
                    "你是一个数据生成助手。你只能输出一个合法的 JSON 对象," // 规则第一段:限定角色和输出类型
                            + "禁止输出任何解释性文字、禁止使用 markdown 代码块(不要出现 ```)。"); // 规则第二段:禁止多余文字和 markdown 代码块
            messages.put(systemMessage);

            // ---------- 用户消息:明确要求生成表格数据 ----------
            JSONObject userMessage = new JSONObject();
            // 指定消息角色为 "user",表示这是用户发送的内容
            userMessage.put("role", "user");

            // 定义用户提问内容:要求返回固定结构的 JSON,方便程序直接解析成 Excel
            String question =
                    "请生成一份【2026年公司员工信息表】的数据,要求:\n"
                            + "1. 列包含:姓名、年龄、部门、职位、入职日期、月薪;\n"
                            + "2. 提供 20 条示例数据,内容合理、不重复;\n"
                            + "3. 严格按下面的 JSON 结构返回,不要添加任何其他字段和文字:\n"
                            + "{\n"
                            + "  \"sheetName\": \"员工信息表\",\n"
                            + "  \"headers\": [\"姓名\",\"年龄\",\"部门\",\"职位\",\"入职日期\",\"月薪\"],\n"
                            + "  \"rows\": [\n"
                            + "    [\"张三\", 28, \"研发部\", \"后端工程师\", \"2023-05-10\", 18000],\n"
                            + "    [\"李四\", 32, \"市场部\", \"市场经理\", \"2021-08-01\", 22000]\n"
                            + "  ]\n"
                            + "}";

            System.out.println("用户向大模型提出的问题是:\n" + question); // 打印用户提问,方便观察程序运行过程

            // 将问题文本放入 content 字段
            userMessage.put("content", question);

            // 将这条用户消息添加到 messages 数组中
            messages.put(userMessage);
            // 将 messages 数组放入最外层的请求体对象中
            requestBody.put("messages", messages);

            // 此时 requestBody 的结构大致为:
            // {
            //   "model": "deepseek-chat",       // 模型字段
            //   "stream": false,                // 非流式
            //   "temperature": 0.2,             // 温度
            //   "messages": [                   // 消息数组
            //     { "role": "system", "content": "..." }, // 系统消息
            //     { "role": "user",   "content": "..." }  // 用户消息
            //   ]                                // 消息数组结束
            // }                                  // 结构右花括号

            // ==================== 第二步:建立 HTTP 连接 ====================

            // 根据 API 地址创建 URL 对象
            URL url = new URL(API_URL);
            // 打开连接,并强制转换为 HttpURLConnection,以便设置请求方法和请求头
            HttpURLConnection con = (HttpURLConnection) url.openConnection();

            // 设置请求方法为 POST(DeepSeek 聊天接口要求 POST)
            con.setRequestMethod("POST");

            // 设置请求头 Content-Type,告诉服务器请求体的格式是 JSON,并使用 UTF-8 编码
            con.setRequestProperty("Content-Type", "application/json; charset=UTF-8");

            // 设置认证请求头
            // DeepSeek 使用 Bearer Token 认证,格式为:Authorization: Bearer <API_KEY>
            // 注意 Bearer 与 Key 之间必须有一个空格
            con.setRequestProperty("Authorization", "Bearer " + API_KEY);

            // 允许向连接中写入数据(发送请求体)
            con.setDoOutput(true);
            // 允许从连接中读取数据(接收响应体)
            con.setDoInput(true);

            // ==================== 第三步:发送请求 ====================

            // 获取输出流,并使用 try-with-resources 确保流自动关闭
            try (OutputStream os = con.getOutputStream()) {
                // 将 JSON 对象转换为字符串,再按 UTF-8 编码为字节数组,写入输出流
                os.write(requestBody.toString().getBytes(StandardCharsets.UTF_8));
                // 刷新缓冲区,确保所有数据立即发送到服务器
                os.flush();
            }

            // ==================== 第四步:获取并读取响应 ====================

            // 获取 HTTP 响应状态码 作用
            // 常见状态码:200:成功。401:认证失败。402:余额不足。429:请求过多。500:服务器错误
            int responseCode = con.getResponseCode();
            System.out.println("HTTP响应码为:" + responseCode);

            // 根据响应码选择输入流:
            // 如果状态码是 2xx,表示成功,使用 getInputStream() 读取正常响应体;
            // 否则使用 getErrorStream() 读取错误信息,避免抛出 IOException。
            InputStream inputStream = (responseCode >= 200 && responseCode < 300)
                    ? con.getInputStream()
                    : con.getErrorStream();

            // 使用 BufferedReader 按行读取响应内容,并拼接成完整的字符串
            StringBuilder responseSb = new StringBuilder();

            // try-with-resources 语法:括号内声明的资源会在 try 代码块结束后自动关闭
            try (BufferedReader reader = new BufferedReader(
                    // InputStreamReader 的作用是将字节流转换为字符流。
                    // 第二个参数 StandardCharsets.UTF_8 指定使用 UTF-8 字符集解码字节,
                    // 这是为了正确读取中文等多字节字符,避免使用系统默认编码导致乱码。
                    new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {

                // 声明一个字符串 line,用于临时存放每次读取到的一行文本
                String line;

                // 循环读取每一行,直到流末尾
                while ((line = reader.readLine()) != null) {
                    // 将当前读取到的这一行内容追加到 StringBuilder 中
                    responseSb.append(line);
                }
            }

            // 得到完整的响应 JSON 字符串
            String responseBody = responseSb.toString();
            System.out.println("完整响应 JSON:");
            System.out.println(responseBody);
            // ==================== 第五步:解析 AI 回答 ====================

            // 仅当 HTTP 状态码为 200 时,才认为请求成功,进行 JSON 解析
            if (responseCode != 200) { // 如果响应码不是 200
                System.out.println("请求失败,请检查 API Key 或余额。");
                return;
            }

            // 使用 Hutool 的 JSONUtil 将响应字符串解析为 JSONObject
            JSONObject jsonResponse = JSONUtil.parseObj(responseBody);

            // 获取 choices 数组,DeepSeek 的响应结构与 OpenAI 一致
            JSONArray choices = jsonResponse.getJSONArray("choices");
            if (choices == null || choices.isEmpty()) {
                System.out.println("未找到 choices 字段");
                return;
            }

            // 取出第一个 choice → message → content,即 AI 生成的文本
            JSONObject firstChoice = choices.getJSONObject(0); // 取 choices 的第一个元素(下标 0)
            JSONObject message = firstChoice.getJSONObject("message"); // 从 choice 中取出 message 对象
            String content = message.getStr("content"); // 从 message 中取出 content 字符串,即 AI 生成的文本

            System.out.println("\n=== AI 返回的内容 ===");
            System.out.println(content); 

            // ==================== 第六步:把 AI 返回的 JSON 转成 Excel ====================

            // 模型偶尔会画蛇添足地包一层 ```json ... ```,这里先做一次清洗
            String tableJsonText = clearJson(content); // 调用 cleanJson 方法,得到纯 JSON 字符串

            // 解析成表格结构对象 
            JSONObject tableData = JSONUtil.parseObj(tableJsonText); // 把纯 JSON 字符串解析成 JSONObject

            // 工作表名称(做一次合法性兜底) 
            String sheetName = safeSheetName(tableData.getStr("sheetName", "Sheet1")); // 取 sheetName,缺省为 Sheet1,再走一遍合法性处理

            // 表头数组,例如 ["姓名","年龄","部门","职位","入职日期","月薪"] 
            JSONArray headers = tableData.getJSONArray("headers"); // 从 tableData 中取出 headers 数组

            // 数据行数组,例如 [["张三",28,...], ["李四",32,...]] 
            JSONArray rows = tableData.getJSONArray("rows"); // 从 tableData 中取出 rows 数组

            if (headers == null || headers.isEmpty()) { // 如果 headers 为 null 或空
                System.out.println("AI 返回的数据中缺少 headers 字段,无法生成 Excel。"); // 打印提示
                return; // 结束 main 方法
            } // if 结束

            // 写出 .xlsx 文件 
            writeExcel(EXCEL_PATH, sheetName, headers, rows); // 调用 writeExcel 方法把数据写成 Excel 文件

            System.out.println("\n=== Excel 生成完成 ==="); 
            System.out.println("文件位置:" + new java.io.File(EXCEL_PATH).getAbsolutePath()); // 打印文件的绝对路径

        } catch (Exception e) { // 捕获 try 块中可能抛出的所有异常
            // 捕获所有异常并打印,便于调试 
            System.out.println("异常信息:" + e); // 打印异常对象信息
            e.printStackTrace(); // 打印异常调用栈,便于定位问题
        } // catch 结束
    } // main 方法结束

    /**
     * 把 AI 返回的文本清洗成纯 JSON 字符串。
     * 处理两种常见"污染":
     * 1. 被 markdown 代码块包裹:```json ... ```
     * 2. 前后带有多余的解释性文字
     *
     * @param content AI 返回的原始文本
     * @return 纯 JSON 字符串
     */
    private static String clearJson(String content) {
        // 如果原始文本数据为空则返回空字符串,避免后续空指针
        if (content == null) {
            return "";
        }
        // 去掉首尾空白字符(空格,换行,制表符等),
        String text = content.trim();

        // 去掉开头的```json```
        if (text.startsWith("```")) {
            // 找到第一个换行符的位置
            int firstLineEnd = text.indexOf('\n');
            // 如果有换行符
            if (firstLineEnd > 0) {
                // firstLineEnd(换行符在哪个索引位置) + 1  (后面索引位置的数据)
                // substring (截取字符串(后一个元素)赋值给当前text从而弥补空缺)
                text = text.substring(firstLineEnd + 1);
            }
        }
        // 去掉结尾的```
        if (text.endsWith("```")) {
            // 去掉结尾的三个```然后重新赋值给text
            text = text.substring(0, text.length() - 3);
        }
        // 兜底操作,截取第一个{和最后}之间的内容
        // 找到第一个{的索引位置
        int start = text.indexOf('{');
        // 找到最后一个}的索引位置
        int end = text.lastIndexOf('}');
        // 如果两者之间顺序正确
        if (start >= 0 && end > start){
            // 截取两者之间的内容
            text = text.substring(start,end + 1);
        }

        // 再次去除掉首位的空白并返回
        return text.trim();
    }


    /**
     * 保证Excel表名称合法性: 不能超过31个字符,且不含Excel的禁止字符:\/?* []
     *
     * @param raw 原始的名称
     * @return 合法的Excel表名称
     */
    private static String safeSheetName(String raw) {
        // 判断参数是否为空
        if (raw == null || raw.trim().isEmpty()) {
            // 返回默认的名称
            return "Sheet1";
        }
        // 去除首尾空格,并把Excel禁止字符替换成下划线
        String name = raw.trim().replaceAll("[:\\\\/?*\\[\\]]", "_");
        // 如果文件名称长超过31个进行截取,否则返回原名称
        return name.length() > 31 ? name.substring(0, 31) : name;
    }

    /**
     * 根据表头与数据行生成 .xlsx 文件 
     *
     * @param filePath  输出文件路径 
     * @param sheetName 工作表名称 
     * @param headers   表头数组(JSONArray,元素为字符串) 
     * @param rows      数据行数组(JSONArray,元素为 JSONArray) 
     * @throws Exception 文件写入异常 
     */
    private static void writeExcel(String filePath, String sheetName, // 定义私有静态方法 writeExcel,接收路径与表名
                                   JSONArray headers, JSONArray rows) throws Exception { // 继续接收表头与数据行,并声明可能抛异常

        // Workbook 代表一个 Excel 文件;XSSFWorkbook 对应 .xlsx 格式 
        // 与 FileOutputStream 一起放在 try-with-resources 中,确保自动关闭、不占用文件句柄 
        try (Workbook workbook = new XSSFWorkbook(); // 创建 XSSF 工作簿对象(内存中的 Excel)
             FileOutputStream fos = new FileOutputStream(filePath)) { // 创建文件输出流,指向目标 .xlsx 路径

            // 创建一个工作表 
            Sheet sheet = workbook.createSheet(sheetName); // 在工作簿中按名称创建一张工作表

            // ---------- 表头样式:加粗 + 居中 ---------- 
            CellStyle headerStyle = workbook.createCellStyle(); // 创建单元格样式对象
            Font headerFont = workbook.createFont(); // 创建字体对象
            headerFont.setBold(true);          // 设置字体为加粗
            headerStyle.setFont(headerFont); // 把字体应用到样式上
            headerStyle.setAlignment(HorizontalAlignment.CENTER); // 设置水平对齐方式为居中

            // ---------- 写表头(第 0 行) ---------- 
            int rowIndex = 0; // 声明行号计数器,从 0 开始
            Row headerRow = sheet.createRow(rowIndex++); // 在工作表中创建第 0 行,并把 rowIndex 自增到 1
            for (int i = 0; i < headers.size(); i++) { // 遍历表头数组的每个下标
                Cell cell = headerRow.createCell(i); // 在表头行中第 i 列创建单元格
                cell.setCellValue(headers.getStr(i)); // 把表头第 i 个字符串写入单元格
                cell.setCellStyle(headerStyle); // 为表头单元格应用加粗居中样式
            } // for 循环结束

            // ---------- 写数据行 ----------
            if (rows != null) { // 如果 rows 不为 null
                for (Object rowObj : rows) { // 遍历 rows 中的每个元素
                    // Hutool 迭代 JSONArray 时元素是 Object,需要转回 JSONArray 
                    JSONArray rowArray = (JSONArray) rowObj; // 把当前元素强转为 JSONArray
                    Row row = sheet.createRow(rowIndex++); // 创建新的一行,并把行号自增

                    for (int i = 0; i < rowArray.size(); i++) { // 遍历当前行的每个单元格数据
                        Object value = rowArray.get(i); // 取出第 i 个数据(可能是字符串、数字等)
                        Cell cell = row.createCell(i); // 在当前行第 i 列创建单元格

                        // 数值类型写成真正的数字,这样 Excel 里可以直接求和/排序; 
                        // 其它类型(字符串、日期、null)统一按文本写入 
                        if (value instanceof Number) { // 如果值是 Number 及其子类
                            cell.setCellValue(((Number) value).doubleValue()); // 用 double 形式写入,保证在 Excel 中是数值
                        } else {
                            cell.setCellValue(value == null ? "" : value.toString()); // null 写空串,其余写字符串
                        }
                    }
                }
            }

            // ---------- 自动列宽 ----------
            for (int i = 0; i < headers.size(); i++) {
                sheet.autoSizeColumn(i); // 根据内容自动调整第 i 列宽度
                // autoSizeColumn 对中文支持一般,这里额外留出一点宽度,避免文字被截断 
                sheet.setColumnWidth(i, sheet.getColumnWidth(i) + 1024); // 在原宽度基础上再加 1024 单位
            } // for 结束

            // 把内存中的工作簿写入文件流 
            workbook.write(fos); // 把工作簿内容写出到文件输出流
        } 
    } 
} 

对接DeepSeek API创建数据库,数据表,数据

java 复制代码
package com.sin;

import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.sql.*;

/**
 * @createTime 2026/9/17 8:37
 * @createAuthor SIN
 * @use 对接 DeepSeek API,把数据库连接信息告诉 AI,让 AI 生成 SQL,程序连数据库执行并展示结果
 */
public class DemoModel3 {

    // ==================== 常量定义 ====================

    // DeepSeek 的 API Key(生产环境请用环境变量,不要硬编码)
    private static final String API_KEY = "sk-360a513df*************af38c53";

    // DeepSeek 聊天补全接口地址 // 说明该常量的用途
    private static final String API_URL = "https://api.deepseek.com/chat/completions";

    // ---------- 数据库基础信息(改这里,提示词和连接串会同步变) ---------- // 分节说明下面这组常量
    private static final String DB_HOST = "localhost"; // 数据库主机地址,本机用 localhost
    private static final String DB_PORT = "3306";      // 数据库端口,MySQL 默认 3306
    private static final String DB_NAME = "employee_db3"; // 要操作的数据库名
    private static final String DB_USER = "root";      // 数据库用户名
    private static final String DB_PWD = "123456";    // 数据库密码

    // 程序连接用 JDBC URL:由上面的基础信息拼出来
    private static final String JDBC_URL = // 私有静态常量:完整 JDBC 连接串
            "jdbc:mysql://" + DB_HOST + ":" + DB_PORT + "/" + DB_NAME // 协议 + 主机 + 端口 + 库名
                    + "?useSSL=false&serverTimezone=Asia/Shanghai"    // 关 SSL,指定东八区时区,避免时区报错
                    + "&allowPublicKeyRetrieval=true&characterEncoding=UTF-8" // MySQL8 允许公钥检索,字符集 UTF-8
                    + "&user=" + DB_USER + "&password=" + DB_PWD;    // 拼上用户名与密码(URL 参数形式)

    public static void main(String[] args) { // 程序入口方法:public 外部可访问,static 无需实例化,void 无返回值
        try { // 开始 try 块,捕获可能出现的各种异常

            // ==================== 第一步:构建请求体 ====================

            JSONObject requestBody = new JSONObject(); // 最外层请求体对象,承载整个请求数据
            requestBody.put("model", "deepseek-chat");  // 指定调用的模型为 deepseek-chat(通用对话模型)
            requestBody.put("stream", false);           // 关闭流式输出,一次拿完整 JSON
            requestBody.put("temperature", 0.2);        // 低温度,让模型输出格式更稳定、更可控

            JSONArray messages = new JSONArray(); // 消息数组,用于承载对话历史(system + user)

            // 系统消息:约束模型只输出 SQL // 分节注释
            JSONObject systemMessage = new JSONObject(); // 创建一个 JSONObject 表示系统消息
            systemMessage.put("role", "system"); // 该消息角色设为 system,用于设定模型行为规则
            systemMessage.put("content", // 开始设置 content 字段(一段规则文本)
                    "你是一个数据库脚本生成助手。只输出可执行的 MySQL 5 的 SQL 语句," // 规则第一段:限定角色和输出类型
                            + "每条语句以分号结尾,禁止输出任何解释文字,禁止使用 markdown 代码块。"); // 规则第二段:禁止多余文字
            messages.put(systemMessage); // 把系统消息加入 messages 数组

            // 用户消息:把数据库连接信息写进 content,让 AI 知道要操作哪个库
            JSONObject userMessage = new JSONObject(); // 创建一个 JSONObject 表示用户消息
            userMessage.put("role", "user"); // 该消息角色设为 user,表示这是用户发送的内容

            String question = // 声明并开始拼接字符串 question(要发给 AI 的提示词)
                    "数据库连接信息如下:\n" // 第一段:开头标题,\n 是换行
                            + "- 主机:" + DB_HOST + "\n"   // 拼接主机信息
                            + "- 端口:" + DB_PORT + "\n"   // 拼接端口信息
                            + "- 数据库名:" + DB_NAME + "\n" // 拼接库名信息
                            + "- 用户名:" + DB_USER + "\n" // 拼接用户名信息
                            + "- 密码:" + DB_PWD + "\n"    // 拼接密码信息
                            + "\n"                          // 空行,用于排版
                            + "请基于以上连接信息,生成一份员工管理系统的初始化 SQL,要求:\n" // 任务描述
                            + "1. 在 " + DB_NAME + " 库中创建员工表 employee4,字段:" // 要求 1:建表(注意表名是 employee4)
                            + "id(INT 主键 自增)、name、age、department、position、hire_date、salary;\n" // 字段列表
                            + "2. 插入 20 条示例数据;\n" // 要求 2:插入数据
                            + "3. 最后查询所有员工并按 id 升序。\n" // 要求 3:查询
                            + "只输出 SQL 语句,不要解释。"; // 收尾:再次强调只输出 SQL

            System.out.println("提问:\n" + question); // 打印用户提问,方便观察程序运行过程

            userMessage.put("content", question); // 把拼接好的 question 写入用户消息的 content 字段
            messages.put(userMessage);            // 把 userMessage 加入 messages 数组
            requestBody.put("messages", messages); // 把 messages 数组挂到最外层请求体的 messages 字段

            // ==================== 第二步:发 HTTP 请求 ====================

            URL url = new URL(API_URL); // 用接口地址字符串构造 URL 对象
            HttpURLConnection con = (HttpURLConnection) url.openConnection(); // 打开连接并强转为 HttpURLConnection

            con.setRequestMethod("POST"); // 设置请求方法为 POST
            con.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); // 声明请求体是 JSON,UTF-8
            con.setRequestProperty("Authorization", "Bearer " + API_KEY); // 设置认证头:Bearer + 空格 + Key
            con.setDoOutput(true); // 允许向连接写入数据(发送请求体)
            con.setDoInput(true);  // 允许从连接读取数据(接收响应体)

            // 写出请求体
            try (OutputStream os = con.getOutputStream()) { // 获取输出流,try-with-resources 自动 close
                os.write(requestBody.toString().getBytes(StandardCharsets.UTF_8)); // 请求体 JSON 字符串按 UTF-8 编码后写出
                os.flush(); // 刷新缓冲区,立即发送
            }

            // ==================== 第三步:读响应 ====================

            int responseCode = con.getResponseCode(); // 读取 HTTP 响应状态码
            System.out.println("HTTP响应码:" + responseCode); // 打印状态码

            InputStream inputStream = (responseCode >= 200 && responseCode < 300) // 三元表达式判断是否 2xx
                    ? con.getInputStream()  // 是 2xx:用正常输入流
                    : con.getErrorStream(); // 否则:用错误输入流,避免抛 IOException

            StringBuilder responseSb = new StringBuilder(); // 创建 StringBuilder,用于高效拼接字符串
            try (BufferedReader reader = new BufferedReader( // 用 BufferedReader 包装字符流,逐行读取
                    new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { // 用 UTF-8 把字节流解码成字符流
                String line; // 声明局部变量 line,暂存每行文本
                while ((line = reader.readLine()) != null) { // 读取一行,返回 null 表示流末尾
                    responseSb.append(line); // 把当前行追加到 StringBuilder
                }
            }
            String responseBody = responseSb.toString(); // 把 StringBuilder 转成普通字符串

            if (responseCode != 200) { // 如果不是 200
                System.out.println("请求失败:" + responseBody); // 打印失败响应体
                return;
            }

            // ==================== 第四步:取出 AI 返回的 SQL 文本 ====================

            JSONObject jsonResponse = JSONUtil.parseObj(responseBody); // 把响应字符串解析成 JSON 对象
            JSONArray choices = jsonResponse.getJSONArray("choices"); // 从响应中取出 choices 数组
            if (choices == null || choices.isEmpty()) { // 如果 choices 为空或 null
                System.out.println("未找到 choices 字段"); // 打印提示
                return;
            }

            // choices[0] → message → content // 说明下面这组链式调用
            String content = choices.getJSONObject(0) // 取 choices 第一个元素
                    .getJSONObject("message")         // 从 choice 中取 message
                    .getStr("content");               // 从 message 中取 content 字符串(AI 生成的 SQL)

            System.out.println("\n=== AI 返回的 SQL ==="); // 打印分隔标题
            System.out.println(content); // 打印 AI 返回的原始 SQL 文本

            // 清洗掉可能出现的 markdown 代码块 ```sql ... ```
            String sqlText = clearMarkdown(content); // 调用 clearMarkdown 方法,得到纯 SQL 文本

            // ==================== 第五步:执行 SQL ====================

            executeSql(sqlText); // 调用 executeSql 方法执行 SQL

            System.out.println("\n=== 全部完成 ==="); // 打印完成标题

        } catch (Exception e) { // 捕获 try 块中可能抛出的所有异常
            System.out.println("异常信息:" + e); // 打印异常对象信息
            e.printStackTrace(); // 打印异常调用栈,便于定位问题
        }
    }

    /**
     * 去掉 AI 可能带上的 markdown 代码块包裹。
     *
     * @param content AI 返回的原始文本
     * @return 纯 SQL 文本
     */
    private static String clearMarkdown(String content) { // 定义私有静态方法 clearMarkdown
        if (content == null) return ""; // 如果入参为 null,返回空串,避免空指针
        String text = content.trim(); // 去掉首尾空白字符,赋给 text

        // 去掉开头的 ```或 ```sql
        if (text.startsWith("```")) { // 如果 text 以三个反引号开头
            int firstLineEnd = text.indexOf('\n'); // 找到第一个换行符的位置
            if (firstLineEnd > 0) { // 如果存在换行符
                text = text.substring(firstLineEnd + 1); // 截掉第一行(即 ```或 ```sql),保留后续内容
            } // 内层 if 结束
        } // 外层 if 结束
        // 去掉结尾的 ```
        if (text.endsWith("```")) { // 如果 text 以三个反引号结尾
            text = text.substring(0, text.length() - 3); // 去掉末尾三个反引号
        }
        return text.trim(); // 再次去除首尾空白并返回
    }

    /**
     * 连接到数据库,把 AI 生成的 SQL 按分号拆开,逐条执行。
     * 查询语句会把结果打印出来。 // 补充说明
     *
     * @param sqlText AI 返回的纯 SQL 文本
     * @throws Exception JDBC 异常
     */
    private static void executeSql(String sqlText) throws Exception {

        // 把 JDBC_URL 里的 "/employee_db3" 换掉,得到不带库名的服务器 URL
        String serverUrl = JDBC_URL.replace("/" + DB_NAME, "/");

        try (Connection conn = DriverManager.getConnection(serverUrl);
             Statement stmt = conn.createStatement()) {

            String createDbSql = "CREATE DATABASE IF NOT EXISTS `" + DB_NAME
                    + "` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci";
            System.out.println("\n[建库] " + createDbSql);
            stmt.executeUpdate(createDbSql);
            System.out.println("数据库已就绪:" + DB_NAME);
        }




        // ---------- 第二步:连到刚建的库,逐条执行 AI 生成的 SQL ----------
        try (Connection conn = DriverManager.getConnection(JDBC_URL);
             Statement stmt = conn.createStatement()) {

            String[] statements = sqlText.split(";");

            for (String raw : statements) {
                String sql = raw.trim();
                if (sql.isEmpty()) continue;

                String upper = sql.toUpperCase();

                // 跳过 USE / CREATE DATABASE(程序已处理)
                if (upper.startsWith("USE ") || upper.startsWith("CREATE DATABASE")) {
                    System.out.println("[跳过] " + sql);
                    continue;
                }

                System.out.println("\n[执行] " + sql);

                if (upper.startsWith("SELECT")) {
                    try (ResultSet rs = stmt.executeQuery(sql)) {
                        printResultSet(rs);
                    }
                } else {
                    int affected = stmt.executeUpdate(sql);
                    System.out.println("影响行数:" + affected);
                }
            }
        }
    }

    /**
     * 把 ResultSet 打印成「表头 + 数据行」的简单表格。
     *
     * @param rs JDBC 查询结果集
     * @throws SQLException 读取元数据或数据时可能抛出
     */
    private static void printResultSet(ResultSet rs) throws SQLException { // 定义私有静态方法 printResultSet
        ResultSetMetaData meta = rs.getMetaData(); // 获取结果集的元数据(列名、列数、类型等)
        int colCount = meta.getColumnCount(); // 取得列数

        // 打印表头
        StringBuilder header = new StringBuilder(); // 创建 StringBuilder 用于拼表头
        for (int i = 1; i <= colCount; i++) { // JDBC 列下标从 1 开始,遍历每一列
            header.append(meta.getColumnName(i)); // 追加第 i 列的列名
            if (i < colCount) header.append("\t"); // 如果不是最后一列,追加制表符
        } // for 结束
        System.out.println(header); // 打印表头行
        System.out.println("----------------------------------------");

        // 打印数据行
        int rowNo = 0; // 记录已打印行数
        while (rs.next()) { // 游标下移,返回 true 表示还有下一行
            StringBuilder row = new StringBuilder(); // 创建 StringBuilder 用于拼当前行
            for (int i = 1; i <= colCount; i++) { // 遍历每一列
                String v = rs.getString(i); // 以字符串形式读出第 i 列的值(null 会返回 null)
                row.append(v == null ? "NULL" : v); // null 显示为 NULL,其余直接追加
                if (i < colCount) row.append("\t"); // 不是最后一列则加制表符
            } // 内层 for 结束
            System.out.println(row); // 打印当前行
            rowNo++; // 行数自增
        }
        System.out.println("----------------------------------------");
        System.out.println("共 " + rowNo + " 行。"); // 打印总行数
    }
}

处理上传的文档

  • .sql → 解析后直接执行 SQL
  • .docx → 提取文本 → 让 AI 总结/改写/提取要点
  • .pptx → 提取文本 → 让 AI 总结/生成大纲
  • .xlsx → 提取数据 → 让 AI 分析
  • .txt → 直接送给 AI
java 复制代码
// 声明当前类所在的包,包名 com.sin 与目录结构 com/sin 对应,Java 要求目录结构与包名一致
package com.sin;

// 导入 Hutool 的 JSONArray 类,用于表示 JSON 数组(如 choices、messages)
import cn.hutool.json.JSONArray;

// 导入 Hutool 的 JSONObject 类,用于表示 JSON 对象(键值对结构)
import cn.hutool.json.JSONObject;

// 导入 Hutool 的 JSONUtil 工具类,提供 JSON 字符串与对象之间的相互转换
import cn.hutool.json.JSONUtil;

// 导入 POI 的 Cell 接口,表示 Excel 中的一个单元格
import org.apache.poi.ss.usermodel.Cell;

// 导入 POI 的 DateUtil 工具类,用于判断某个单元格是否被格式化为日期
import org.apache.poi.ss.usermodel.DateUtil;

// 导入 POI 的 Row 接口,表示 Excel 中的一行
import org.apache.poi.ss.usermodel.Row;

// 导入 POI 的 XMLSlideShow 类,用于读取 .pptx(Office 2007+)格式的 PPT 文件
import org.apache.poi.xslf.usermodel.XMLSlideShow;

// 导入 POI 的 XSLFShape 接口,表示 .pptx 幻灯片上的一个形状(文本、图片、图形等)
import org.apache.poi.xslf.usermodel.XSLFShape;

// 导入 POI 的 XSLFSlide 类,表示 .pptx 中的一页幻灯片
import org.apache.poi.xslf.usermodel.XSLFSlide;

// 导入 POI 的 XSLFTextShape 接口,表示 .pptx 中含有文本的形状
import org.apache.poi.xslf.usermodel.XSLFTextShape;

// 导入 POI 的 XSSFSheet 类,表示 .xlsx 工作簿中的一张工作表
import org.apache.poi.xssf.usermodel.XSSFSheet;

// 导入 POI 的 XSSFWorkbook 类,表示一个 .xlsx 工作簿文件
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

// 导入 POI 的 XWPFDocument 类,表示一个 .docx Word 文档
import org.apache.poi.xwpf.usermodel.XWPFDocument;

// 导入 POI 的 XWPFParagraph 类,表示 .docx 文档中的一个段落
import org.apache.poi.xwpf.usermodel.XWPFParagraph;

// 导入 POI 的 XWPFTable 类,表示 .docx 文档中的一张表格
import org.apache.poi.xwpf.usermodel.XWPFTable;

// 导入 POI 的 XWPFTableCell 类,表示 .docx 表格中的一个单元格
import org.apache.poi.xwpf.usermodel.XWPFTableCell;

// 导入 POI 的 XWPFTableRow 类,表示 .docx 表格中的一行
import org.apache.poi.xwpf.usermodel.XWPFTableRow;

// 导入 POI 的 HSLFSlideShow 类,用于读取旧版 .ppt(Office 2003 及更早)格式的 PPT 文件
import org.apache.poi.hslf.usermodel.HSLFSlideShow;

// 导入 POI 的 HSLFSlide 类,表示旧版 .ppt 中的一页幻灯片
import org.apache.poi.hslf.usermodel.HSLFSlide;

// 导入 POI 的 HSLFShape 接口,表示旧版 .ppt 幻灯片上的一个形状
import org.apache.poi.hslf.usermodel.HSLFShape;

// 导入 POI 的 HSLFTextShape 接口,表示旧版 .ppt 中含有文本的形状
import org.apache.poi.hslf.usermodel.HSLFTextShape;

// 导入 java.io 包下的所有类,包括文件流、缓冲流、异常类等
import java.io.*;

// 导入 HttpURLConnection 类,用于发送 HTTP 请求并接收响应
import java.net.HttpURLConnection;

// 导入 URL 类,用于表示统一资源定位符,即 API 接口地址
import java.net.URL;

// 导入 StandardCharsets 类,提供 UTF-8 等标准字符集常量
import java.nio.charset.StandardCharsets;

// 导入 java.sql 包下的所有类,包括 Connection、Statement、ResultSet 等 JDBC 对象
import java.sql.*;

// 导入 Matcher 类,用于对字符串执行正则匹配并获取结果
import java.util.regex.Matcher;

// 导入 Pattern 类,用于编译正则表达式
import java.util.regex.Pattern;

// 定义一个公开类,类名必须与文件名 DemoModel4.java 一致
public class DemoModel4 {

    // 定义私有静态常量,表示 DeepSeek 平台的 API 密钥,用于身份认证
    private static final String API_KEY = "sk-360a513df21c49d1aef38cbfaaf38c53";

    // 定义私有静态常量,表示 DeepSeek 聊天补全接口的完整 URL 地址
    private static final String API_URL = "https://api.deepseek.com/chat/completions";

    // 定义私有静态常量,表示 MySQL 数据库的主机地址
    private static final String DB_HOST = "localhost";

    // 定义私有静态常量,表示 MySQL 数据库的端口号
    private static final String DB_PORT = "3306";

    // 定义私有静态常量,表示 MySQL 数据库的用户名
    private static final String DB_USER = "root";

    // 定义私有静态常量,表示 MySQL 数据库的密码
    private static final String DB_PWD  = "123456";

    // 定义私有静态常量,表示发送给 AI 的内容最大长度,防止 token 超出模型限制
    private static final int MAX_CONTENT_LEN = 10000;

    // 定义程序入口方法,JVM 启动时会调用此方法
    public static void main(String[] args) {

        // 使用 try-catch 包裹主流程,捕获可能出现的所有异常
        try {

            // 调用 pickFile 方法获取待处理文件的路径,args 是命令行参数
            String filePath = pickFile(args);

            // 如果返回的路径为 null,说明没有可用文件,直接结束程序
            if (filePath == null) return;

            // 根据文件路径创建 File 对象,用于后续操作
            File file = new File(filePath);

            // 在控制台打印该文件的绝对路径
            System.out.println("📂 文件:" + file.getAbsolutePath());

            // 在控制台打印该文件的字节大小
            System.out.println("📏 大小:" + file.length() + " 字节");

            // 获取文件扩展名并转为小写,便于后续判断类型
            String ext = getExtension(file.getName()).toLowerCase();

            // 在控制台打印文件扩展名
            System.out.println("🔖 类型:." + ext);

            // 在控制台打印一条分隔线,便于阅读
            System.out.println("----------------------------------------");

            // 使用 switch 语句根据扩展名分发到不同的处理方法
            switch (ext) {

                // 如果是 sql 文件,调用 handleSqlFile 方法执行 SQL 脚本
                case "sql":  handleSqlFile(file);  break;

                // 如果是 docx 文件,调用 handleDocxFile 方法提取并 AI 总结
                case "docx": handleDocxFile(file); break;

                // 如果是 pptx 文件,穿透到下面的 ppt 分支
                case "pptx":

                    // 如果是 ppt 文件,调用 handlePptFile 方法提取并 AI 总结
                case "ppt":  handlePptFile(file);  break;

                // 如果是 xlsx 文件,调用 handleXlsxFile 方法提取并 AI 分析
                case "xlsx": handleXlsxFile(file); break;

                // 如果是 txt 文件,调用 handleTxtFile 方法交给 AI 总结
                case "txt":  handleTxtFile(file);  break;

                // 默认分支,处理不支持的文件类型
                default:

                    // 在控制台打印不支持提示
                    System.out.println("❌ 暂不支持的文件类型:." + ext);
            }

            // 全部处理完毕后,在控制台打印完成信息
            System.out.println("\n=== 全部完成 ===");

            // 捕获 try 块中抛出的任何异常
        } catch (Exception e) {

            // 打印异常对象的简要信息
            System.out.println("❌ 异常信息:" + e);

            // 打印异常的完整堆栈信息,便于定位问题
            e.printStackTrace();
        }
    }

    // 定义私有静态方法 pickFile,用于确定要处理的文件路径,返回字符串
    private static String pickFile(String[] args) {

        // 声明局部变量 path,用三元表达式决定取值
        String path = (args.length > 0)

                // 如果命令行有参数,取第一个参数作为文件路径
                ? args[0]

                // 否则使用下面这个固定的绝对路径作为默认值
                : "E:\\志奇科技\\重庆青年职业技术学院\\人工智能技术应用\\smart_travel.sql";

        // 根据路径字符串创建 File 对象
        File f = new File(path);

        // 判断该文件是否存在
        if (!f.exists()) {

            // 如果不存在,打印错误信息并附上绝对路径
            System.out.println("❌ 文件不存在:" + f.getAbsolutePath());

            // 返回 null 让上层终止流程
            return null;
        }

        // 判断该路径是否指向一个普通文件
        if (!f.isFile()) {

            // 如果不是文件(例如是目录),打印错误信息
            System.out.println("❌ 路径不是文件:" + f.getAbsolutePath());

            // 返回 null 让上层终止流程
            return null;
        }

        // 打印确认使用的文件路径
        System.out.println("📥 使用文件:" + f.getAbsolutePath()

                // 拼接文件大小信息,一并打印
                + "(" + f.length() + " 字节)");

        // 返回最终确定的文件路径字符串
        return path;
    }

    // 定义私有静态方法 handleSqlFile,处理 SQL 脚本文件
    private static void handleSqlFile(File file) throws Exception {

        // 读取文件内容,先清除 markdown 包裹,再得到纯 SQL 文本
        String sqlText = clearMarkdown(readTextFile(file));

        // 在控制台打印 SQL 脚本内容
        System.out.println("📜 SQL 脚本内容:\n" + sqlText);

        // 在控制台打印即将开始执行的提示
        System.out.println("\n▶ 开始执行 SQL ...");

        // 调用 executeSql 方法,真正连接数据库执行 SQL
        executeSql(sqlText);
    }

    // 定义私有静态方法 handleDocxFile,处理 Word 文档
    private static void handleDocxFile(File file) throws Exception {

        // 调用 parseDocx 方法从 Word 中提取全部文本
        String content = parseDocx(file);

        // 在控制台打印提取到的文本长度
        System.out.println("📄 Word 提取文本长度:" + content.length());

        // 调用 askAI 方法,把文本发给 AI 总结
        String summary = askAI(

                // 系统提示:限定 AI 的角色与输出结构
                "你是一个文档处理助手。请阅读用户提供的 Word 内容,输出:\n"

                        // 要求 1:输出总体概括
                        + "1) 一段 100 字以内的总体概括;\n"

                        // 要求 2:输出核心要点
                        + "2) 3-5 条核心要点(以列表形式)。",

                // 用户内容:截断后传入,防止过长
                truncate(content));

        // 在控制台打印标题
        System.out.println("\n=== 🤖 AI 对 Word 的总结 ===");

        // 在控制台打印 AI 总结结果
        System.out.println(summary);
    }

    // 定义私有静态方法 handlePptFile,处理 PPT 文件
    private static void handlePptFile(File file) throws Exception {

        // 调用 parsePpt 方法,内部根据扩展名选择 HSLF 或 XSLF
        String content = parsePpt(file);

        // 在控制台打印提取到的文本长度
        System.out.println("📊 PPT 提取文本长度:" + content.length());

        // 调用 askAI 方法,把 PPT 文本发给 AI 总结
        String summary = askAI(

                // 系统提示:限定 AI 的角色与输出结构
                "你是一个 PPT 处理助手。请阅读用户提供的 PPT 内容(含页码标记),输出:\n"

                        // 要求 1:主题与核心观点
                        + "1) 整个 PPT 的主题与核心观点;\n"

                        // 要求 2:按页要点
                        + "2) 按页给出简要要点。",

                // 用户内容:截断后传入
                truncate(content));

        // 在控制台打印标题
        System.out.println("\n=== 🤖 AI 对 PPT 的总结 ===");

        // 在控制台打印 AI 总结结果
        System.out.println(summary);
    }

    // 定义私有静态方法 handleXlsxFile,处理 Excel 文件
    private static void handleXlsxFile(File file) throws Exception {

        // 调用 parseXlsx 方法提取所有工作表的单元格内容
        String content = parseXlsx(file);

        // 在控制台打印提取到的内容长度
        System.out.println("📈 Excel 提取内容长度:" + content.length());

        // 调用 askAI 方法,把 Excel 内容发给 AI 分析
        String summary = askAI(

                // 系统提示:限定 AI 的角色与输出结构
                "你是一个数据分析助手。请阅读用户提供的 Excel 数据,输出:\n"

                        // 要求 1:数据整体说明
                        + "1) 数据整体说明;\n"

                        // 要求 2:发现或建议
                        + "2) 3-5 条发现或建议。",

                // 用户内容:截断后传入
                truncate(content));

        // 在控制台打印标题
        System.out.println("\n=== 🤖 AI 对 Excel 的分析 ===");

        // 在控制台打印 AI 分析结果
        System.out.println(summary);
    }

    // 定义私有静态方法 handleTxtFile,处理纯文本文件
    private static void handleTxtFile(File file) throws Exception {

        // 调用 readTextFile 方法读取整个文本内容
        String content = readTextFile(file);

        // 在控制台打印内容长度
        System.out.println("📃 TXT 内容长度:" + content.length());

        // 调用 askAI 方法,把文本发给 AI 总结
        String summary = askAI(

                // 系统提示:限定 AI 的输出结构
                "你是一个文本处理助手。请阅读用户提供的文本,输出一段总结和 3-5 条要点。",

                // 用户内容:截断后传入
                truncate(content));

        // 在控制台打印标题
        System.out.println("\n=== 🤖 AI 总结 ===");

        // 在控制台打印 AI 总结结果
        System.out.println(summary);
    }

    // 定义私有静态方法 parseDocx,从 Word 文档中提取段落和表格文本
    private static String parseDocx(File file) throws Exception {

        // 创建 StringBuilder 用于拼接提取到的所有文本
        StringBuilder sb = new StringBuilder();

        // 用 try-with-resources 打开 Word 文档,结束时自动关闭
        try (XWPFDocument doc = new XWPFDocument(new FileInputStream(file))) {

            // 遍历文档中的所有段落
            for (XWPFParagraph p : doc.getParagraphs()) {

                // 获取当前段落的纯文本内容
                String text = p.getText();

                // 如果文本不为 null 且去掉空白后不是空字符串
                if (text != null && !text.trim().isEmpty()) {

                    // 把该段文本追加到 StringBuilder,并在末尾换行
                    sb.append(text).append("\n");
                }
            }

            // 遍历文档中的所有表格
            for (XWPFTable table : doc.getTables()) {

                // 在表格开始处添加一个标记
                sb.append("[表格]\n");

                // 遍历表格中的每一行
                for (XWPFTableRow row : table.getRows()) {

                    // 遍历该行中的每一个单元格
                    for (XWPFTableCell cell : row.getTableCells()) {

                        // 把单元格文本追加到 StringBuilder,并用制表符分隔
                        sb.append(cell.getText()).append("\t");
                    }

                    // 每行结束后追加换行
                    sb.append("\n");
                }
            }
        }

        // 返回拼接完成的字符串
        return sb.toString();
    }

    // 定义私有静态方法 parsePpt,根据扩展名分发到 HSLF 或 XSLF 解析
    private static String parsePpt(File file) throws Exception {

        // 获取文件名并转为小写,便于判断后缀
        String name = file.getName().toLowerCase();

        // 用三元表达式:以 .pptx 结尾走 parsePptx,否则走 parsePptOld
        return name.endsWith(".pptx") ? parsePptx(file) : parsePptOld(file);
    }

    // 定义私有静态方法 parsePptx,解析新版 .pptx 文件
    private static String parsePptx(File file) throws Exception {

        // 创建 StringBuilder 用于拼接提取到的所有文本
        StringBuilder sb = new StringBuilder();

        // 用 try-with-resources 打开 .pptx 文件,结束时自动关闭
        try (XMLSlideShow ppt = new XMLSlideShow(new FileInputStream(file))) {

            // 定义页码计数器,从 1 开始
            int i = 1;

            // 遍历所有幻灯片
            for (XSLFSlide slide : ppt.getSlides()) {

                // 添加页码标记,并把计数器自增
                sb.append("【第 ").append(i++).append(" 页】\n");

                // 遍历该页上的所有形状
                for (XSLFShape shape : slide.getShapes()) {

                    // 判断形状是否为文本形状
                    if (shape instanceof XSLFTextShape) {

                        // 把形状强转为文本形状并获取文本
                        String text = ((XSLFTextShape) shape).getText();

                        // 如果文本非空
                        if (text != null && !text.trim().isEmpty()) {

                            // 追加文本并换行
                            sb.append(text).append("\n");
                        }
                    }
                }

                // 每页结束后追加一个空行,便于阅读
                sb.append("\n");
            }
        }

        // 返回拼接完成的字符串
        return sb.toString();
    }

    // 定义私有静态方法 parsePptOld,解析旧版 .ppt 文件
    private static String parsePptOld(File file) throws Exception {

        // 创建 StringBuilder 用于拼接提取到的所有文本
        StringBuilder sb = new StringBuilder();

        // 用 try-with-resources 打开 .ppt 文件,结束时自动关闭
        try (HSLFSlideShow ppt = new HSLFSlideShow(new FileInputStream(file))) {

            // 定义页码计数器,从 1 开始
            int i = 1;

            // 遍历所有幻灯片
            for (HSLFSlide slide : ppt.getSlides()) {

                // 添加页码标记,并把计数器自增
                sb.append("【第 ").append(i++).append(" 页】\n");

                // 遍历该页上的所有形状
                for (HSLFShape shape : slide.getShapes()) {

                    // 判断形状是否为文本形状
                    if (shape instanceof HSLFTextShape) {

                        // 把形状强转为文本形状并获取文本
                        String text = ((HSLFTextShape) shape).getText();

                        // 如果文本非空
                        if (text != null && !text.trim().isEmpty()) {

                            // 追加文本并换行
                            sb.append(text).append("\n");
                        }
                    }
                }

                // 每页结束后追加一个空行
                sb.append("\n");
            }
        }

        // 返回拼接完成的字符串
        return sb.toString();
    }

    // 定义私有静态方法 parseXlsx,解析 Excel 文件
    private static String parseXlsx(File file) throws Exception {

        // 创建 StringBuilder 用于拼接提取到的所有文本
        StringBuilder sb = new StringBuilder();

        // 用 try-with-resources 打开工作簿,结束时自动关闭
        try (XSSFWorkbook wb = new XSSFWorkbook(new FileInputStream(file))) {

            // 遍历工作簿中的每一张工作表
            for (int s = 0; s < wb.getNumberOfSheets(); s++) {

                // 根据索引获取工作表对象
                XSSFSheet sheet = wb.getSheetAt(s);

                // 添加工作表名标记
                sb.append("【Sheet: ").append(sheet.getSheetName()).append("】\n");

                // 遍历工作表中的每一行
                for (Row row : sheet) {

                    // 遍历该行中的每一个单元格
                    for (Cell cell : row) {

                        // 调用 getCellValue 获取单元格文本值,用制表符分隔
                        sb.append(getCellValue(cell)).append("\t");
                    }

                    // 每行结束后换行
                    sb.append("\n");
                }

                // 每张表结束后追加一个空行
                sb.append("\n");
            }
        }

        // 返回拼接完成的字符串
        return sb.toString();
    }

    // 定义私有静态方法 readTextFile,以 UTF-8 编码读取纯文本文件
    private static String readTextFile(File file) throws Exception {

        // 创建 StringBuilder 用于拼接读取到的所有行
        StringBuilder sb = new StringBuilder();

        // 用 try-with-resources 打开文件,使用 BufferedReader 按行读取
        try (BufferedReader reader = new BufferedReader(

                // 用 InputStreamReader 把字节流按 UTF-8 解码为字符流
                new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) {

            // 声明局部变量 line,用于暂存每行文本
            String line;

            // 循环读取每一行,直到返回 null 表示读到文件末尾
            while ((line = reader.readLine()) != null) {

                // 把当前行追加到 StringBuilder,并补充换行符
                sb.append(line).append("\n");
            }
        }

        // 返回拼接完成的字符串
        return sb.toString();
    }

    // 定义私有静态方法 getCellValue,把 Excel 单元格转换为字符串
    private static String getCellValue(Cell cell) {

        // 如果单元格为 null,返回空字符串,避免空指针
        if (cell == null) return "";

        // 根据单元格的类型进行分支处理
        switch (cell.getCellType()) {

            // 字符串类型
            case STRING:

                // 返回单元格的字符串值
                return cell.getStringCellValue();

            // 数字类型
            case NUMERIC:

                // 先判断该单元格是否被格式化为日期
                if (DateUtil.isCellDateFormatted(cell)) {

                    // 按日期格式返回其本地日期的字符串形式
                    return cell.getLocalDateTimeCellValue().toLocalDate().toString();
                }

                // 不是日期则按数值返回
                return String.valueOf(cell.getNumericCellValue());

            // 布尔类型
            case BOOLEAN:

                // 返回布尔值的字符串形式
                return String.valueOf(cell.getBooleanCellValue());

            // 公式类型
            case FORMULA:

                // 返回单元格中的公式文本
                return cell.getCellFormula();

            // 其他类型(如空白、错误)
            default:

                // 返回空字符串
                return "";
        }
    }

    // 定义私有静态方法 getExtension,用于获取文件扩展名
    private static String getExtension(String fileName) {

        // 找到文件名中最后一个点的索引
        int dot = fileName.lastIndexOf('.');

        // 如果没有点则返回空串,否则返回点后面的部分
        return dot < 0 ? "" : fileName.substring(dot + 1);
    }

    // 定义私有静态方法 truncate,用于截断过长内容
    private static String truncate(String content) {

        // 如果内容为 null,返回空字符串
        if (content == null) return "";

        // 用三元表达式判断是否超长
        return content.length() <= MAX_CONTENT_LEN

                // 未超长则原样返回
                ? content

                // 超长则截取前 MAX_CONTENT_LEN 个字符,并附加提示
                : content.substring(0, MAX_CONTENT_LEN) + "\n...(内容过长,已截断)";
    }

    // 定义私有静态方法 clearMarkdown,去除 AI 返回文本中可能存在的 markdown 代码块包裹
    private static String clearMarkdown(String content) {

        // 如果内容为 null,返回空字符串
        if (content == null) return "";

        // 去除首尾空白,并赋给局部变量 text
        String text = content.trim();

        // 如果以三个反引号开头
        if (text.startsWith("```")) {

            // 找到第一个换行符的位置
            int firstLineEnd = text.indexOf('\n');

            // 如果找到换行符,去掉第一行
            if (firstLineEnd > 0) text = text.substring(firstLineEnd + 1);
        }

        // 如果以三个反引号结尾
        if (text.endsWith("```")) {

            // 去掉末尾三个反引号
            text = text.substring(0, text.length() - 3);
        }

        // 再次去除首尾空白并返回
        return text.trim();
    }

    // 定义私有静态方法 askAI,用于向 DeepSeek 发送对话请求并返回回答文本
    private static String askAI(String systemPrompt, String userPrompt) throws Exception {

        // 创建最外层请求体 JSON 对象
        JSONObject requestBody = new JSONObject();

        // 指定调用的模型名称
        requestBody.put("model", "deepseek-chat");

        // 关闭流式输出,一次性返回完整结果
        requestBody.put("stream", false);

        // 设置温度为 0.3,让结果相对稳定
        requestBody.put("temperature", 0.3);

        // 创建 messages 数组,用于承载对话历史
        JSONArray messages = new JSONArray();

        // 创建系统消息对象
        JSONObject sys = new JSONObject();

        // 设置系统消息角色为 system
        sys.put("role", "system");

        // 设置系统消息内容为传入的 systemPrompt
        sys.put("content", systemPrompt);

        // 把系统消息加入 messages 数组
        messages.put(sys);

        // 创建用户消息对象
        JSONObject user = new JSONObject();

        // 设置用户消息角色为 user
        user.put("role", "user");

        // 设置用户消息内容为传入的 userPrompt
        user.put("content", userPrompt);

        // 把用户消息加入 messages 数组
        messages.put(user);

        // 把 messages 数组放入请求体的 messages 字段
        requestBody.put("messages", messages);

        // 用接口地址字符串构造 URL 对象
        URL url = new URL(API_URL);

        // 打开连接并强转为 HttpURLConnection
        HttpURLConnection con = (HttpURLConnection) url.openConnection();

        // 设置请求方法为 POST
        con.setRequestMethod("POST");

        // 声明请求体为 JSON,编码 UTF-8
        con.setRequestProperty("Content-Type", "application/json; charset=UTF-8");

        // 设置认证头,Bearer 与 Key 之间必须有空格
        con.setRequestProperty("Authorization", "Bearer " + API_KEY);

        // 允许向连接写出数据
        con.setDoOutput(true);

        // 允许从连接读取数据
        con.setDoInput(true);

        // 用 try-with-resources 获取输出流,结束时自动关闭
        try (OutputStream os = con.getOutputStream()) {

            // 把请求体字符串按 UTF-8 编码后写出
            os.write(requestBody.toString().getBytes(StandardCharsets.UTF_8));

            // 刷新缓冲区,确保数据立即发送
            os.flush();
        }

        // 获取 HTTP 响应状态码
        int code = con.getResponseCode();

        // 根据响应码选择正常输入流或错误输入流
        InputStream is = (code >= 200 && code < 300)

                // 2xx 使用正常输入流
                ? con.getInputStream()

                // 否则使用错误输入流
                : con.getErrorStream();

        // 创建 StringBuilder 用于拼接响应内容
        StringBuilder sb = new StringBuilder();

        // 用 try-with-resources 逐行读取响应
        try (BufferedReader reader = new BufferedReader(

                // 使用 UTF-8 解码字节流
                new InputStreamReader(is, StandardCharsets.UTF_8))) {

            // 声明局部变量 line
            String line;

            // 循环读取每一行直到流末尾
            while ((line = reader.readLine()) != null) sb.append(line);
        }

        // 如果响应码不是 200,抛出异常
        if (code != 200) {

            // 抛出包含状态码与响应体的 RuntimeException
            throw new RuntimeException("AI 请求失败 HTTP " + code + ":" + sb);
        }

        // 把响应字符串解析为 JSON 对象
        JSONObject resp = JSONUtil.parseObj(sb.toString());

        // 依次从 choices[0].message.content 中取出回答文本
        return resp.getJSONArray("choices")

                // 取第一个 choice
                .getJSONObject(0)

                // 取其 message 对象
                .getJSONObject("message")

                // 取其 content 字符串
                .getStr("content");
    }

    // 定义私有静态方法 executeSql,用于执行 SQL 脚本
    private static void executeSql(String sqlText) throws Exception {

        // 调用 extractDbName 从脚本中提取数据库名
        String dbName = extractDbName(sqlText);

        // 如果未识别出数据库名
        if (dbName == null) {

            // 打印错误提示
            System.out.println("❌ 未从 SQL 脚本中识别出数据库名,"

                    // 拼接提示内容
                    + "请确保脚本里包含 CREATE DATABASE xxx 或 USE xxx;");

            // 终止方法
            return;
        }

        // 打印识别出的数据库名
        System.out.println("\n🎯 从脚本中识别出数据库名:" + dbName);

        // 拼接连接 MySQL 服务器(不指定库名)的 URL
        String serverUrl = "jdbc:mysql://" + DB_HOST + ":" + DB_PORT + "/"

                // 附加参数:关闭 SSL、指定东八区时区
                + "?useSSL=false&serverTimezone=Asia/Shanghai"

                // 附加参数:允许公钥检索、使用 UTF-8
                + "&allowPublicKeyRetrieval=true&characterEncoding=UTF-8"

                // 附加参数:用户名与密码
                + "&user=" + DB_USER + "&password=" + DB_PWD;

        // 用 try-with-resources 获取数据库连接与 Statement
        try (Connection conn = DriverManager.getConnection(serverUrl);

             // 创建 Statement 对象
             Statement stmt = conn.createStatement()) {

            // 拼接建库 SQL,带 IF NOT EXISTS 与 utf8mb4 字符集
            String createDbSql = "CREATE DATABASE IF NOT EXISTS `" + dbName

                    // 附加字符集与排序规则
                    + "` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci";

            // 打印将要执行的建库语句
            System.out.println("[建库] " + createDbSql);

            // 执行建库语句
            stmt.executeUpdate(createDbSql);

            // 打印库已就绪
            System.out.println("数据库已就绪:" + dbName);
        }

        // 拼接连接目标库的 JDBC URL
        String dbUrl = "jdbc:mysql://" + DB_HOST + ":" + DB_PORT + "/" + dbName

                // 附加参数:关闭 SSL、指定东八区时区
                + "?useSSL=false&serverTimezone=Asia/Shanghai"

                // 附加参数:允许公钥检索、使用 UTF-8
                + "&allowPublicKeyRetrieval=true&characterEncoding=UTF-8"

                // 附加参数:用户名与密码
                + "&user=" + DB_USER + "&password=" + DB_PWD;

        // 用 try-with-resources 获取连接与 Statement
        try (Connection conn = DriverManager.getConnection(dbUrl);

             // 创建 Statement 对象
             Statement stmt = conn.createStatement()) {

            // 以分号为分隔符,把整段 SQL 拆成数组
            String[] statements = sqlText.split(";");

            // 遍历每一条语句
            for (String raw : statements) {

                // 去掉首尾空白
                String sql = raw.trim();

                // 如果为空语句则跳过
                if (sql.isEmpty()) continue;

                // 转成大写,便于关键字前缀判断
                String upper = sql.toUpperCase();

                // 若以 USE 或 CREATE DATABASE 开头则跳过,因为前面已经处理
                if (upper.startsWith("USE ") || upper.startsWith("CREATE DATABASE")) {

                    // 打印跳过提示
                    System.out.println("[跳过] " + sql);

                    // 跳过当前循环
                    continue;
                }

                // 打印即将执行的 SQL
                System.out.println("\n[执行] " + sql);

                // 如果以 SELECT 开头
                if (upper.startsWith("SELECT")) {

                    // 用 try-with-resources 执行查询,自动关闭结果集
                    try (ResultSet rs = stmt.executeQuery(sql)) {

                        // 调用 printResultSet 打印查询结果
                        printResultSet(rs);
                    }

                    // 否则为更新类语句
                } else {

                    // 执行更新,返回受影响行数
                    int affected = stmt.executeUpdate(sql);

                    // 打印受影响行数
                    System.out.println("影响行数:" + affected);
                }
            }
        }
    }

    // 定义私有静态方法 extractDbName,从 SQL 文本中提取数据库名
    private static String extractDbName(String sqlText) {

        // 入参为 null 时直接返回 null
        if (sqlText == null) return null;

        // 编译正则,匹配 CREATE DATABASE [IF NOT EXISTS] xxx
        Pattern p1 = Pattern.compile(

                // 表达式:允许反引号/引号包裹,捕获组为库名
                "CREATE\\s+DATABASE\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?[`\"']?([a-zA-Z0-9_]+)[`\"']?",

                // 忽略大小写
                Pattern.CASE_INSENSITIVE);

        // 在 SQL 文本上创建匹配器
        Matcher m1 = p1.matcher(sqlText);

        // 若匹配到
        if (m1.find()) {

            // 返回捕获组中的数据库名
            return m1.group(1);
        }

        // 编译备用正则,匹配 USE xxx
        Pattern p2 = Pattern.compile(

                // 表达式:允许反引号/引号包裹,捕获组为库名
                "USE\\s+[`\"']?([a-zA-Z0-9_]+)[`\"']?",

                // 忽略大小写
                Pattern.CASE_INSENSITIVE);

        // 在 SQL 文本上创建匹配器
        Matcher m2 = p2.matcher(sqlText);

        // 若匹配到
        if (m2.find()) {

            // 返回捕获组中的数据库名
            return m2.group(1);
        }

        // 都没匹配到则返回 null
        return null;
    }

    // 定义私有静态方法 printResultSet,将查询结果打印到控制台
    private static void printResultSet(ResultSet rs) throws SQLException {

        // 获取结果集的元数据
        ResultSetMetaData meta = rs.getMetaData();

        // 取得列数
        int colCount = meta.getColumnCount();

        // 创建 StringBuilder 用于拼接表头
        StringBuilder header = new StringBuilder();

        // JDBC 列下标从 1 开始遍历
        for (int i = 1; i <= colCount; i++) {

            // 追加列名
            header.append(meta.getColumnName(i));

            // 若不是最后一列,追加制表符
            if (i < colCount) header.append("\t");
        }

        // 打印表头
        System.out.println(header);

        // 打印分隔线
        System.out.println("----------------------------------------");

        // 行数计数器
        int rowNo = 0;

        // 遍历结果集的每一行
        while (rs.next()) {

            // 创建 StringBuilder 用于拼接当前行
            StringBuilder row = new StringBuilder();

            // 遍历每一列
            for (int i = 1; i <= colCount; i++) {

                // 以字符串形式读取该列的值
                String v = rs.getString(i);

                // 若为 null 则显示 NULL,否则显示值
                row.append(v == null ? "NULL" : v);

                // 若不是最后一列,追加制表符
                if (i < colCount) row.append("\t");
            }

            // 打印当前行
            System.out.println(row);

            // 行数计数器加一
            rowNo++;
        }

        // 打印分隔线
        System.out.println("----------------------------------------");

        // 打印总行数
        System.out.println("共 " + rowNo + " 行。");
    }
}
相关推荐
庄园特聘拆椅狂魔1 小时前
Java 后端转全栈的第一课:从前端项目搭建到技术选
java·开发语言·前端
烽火聊员1 小时前
Android屏幕卡顿traces.txt拉取查看
android·java·android studio
Leo.yuan1 小时前
企业可信Data Agent怎么建:可信分析智能体(Traceable Analytic Agent)的分析链路与验证机制
大数据·人工智能·机器学习
u1301302 小时前
AI 日报(2026年9月23日)
人工智能
想带你从多云到转晴2 小时前
LLM/AI应用八股
人工智能·chatgpt
QY_Research_2 小时前
2026-2032年个人安全警报装置市场分析 CAGR4.7% 产业全景报告
大数据·人工智能·安全
lzhdim2 小时前
C#不为人知的10个魔法特性:资深开发者也会震惊的底层奥秘
java·前端·javascript·算法·c#
AI行业说2 小时前
针织 T 恤、运动套装电脑模板机选型与落地指南
大数据·人工智能·机器人·自动化·智能模板机
weixin_307779132 小时前
C++代码实现MATLAB中的pade函数功能
开发语言·c++·算法·matlab