SpringBoot+PostgreSQL + 硅基流動大模型從零搭建 Text-to-SQL 智能問答系統(tǒng)
目錄前言一、項目整體介紹1.1 什么是 Text-to-SQL1.2 系統(tǒng)核心功能1.3 技術選型說明二、本地環(huán)境準備2.1 開發(fā)環(huán)境硬性要求2.2 PostgreSQL 數(shù)據(jù)庫初始化2.3 硅基流動 API Key 申請步驟三、項目完整目錄結構四、Maven 依賴與全局配置4.1 pom.xml 完整依賴4.2 application.yml 配置詳解五、數(shù)據(jù)庫表結構與測試數(shù)據(jù)5.1 schema.sql 建表語句5.2 data.sql 初始化測試數(shù)據(jù)六、實體類與數(shù)據(jù)訪問層代碼6.1 Member.java 實體映射類6.2 MemberRepository 數(shù)據(jù)訪問接口七、核心硅基流動大模型 API 對接服務 LlmService開發(fā)重點說明八、核心業(yè)務整合服務 Text2SqlService安全邏輯重點說明九、Controller 頁面路由與 API 接口十、前端頁面代碼實現(xiàn)10.1 首頁 index.html會員數(shù)據(jù)總覽10.2 問答頁面 chat.html核心交互頁面十一、項目總結11.1 項目核心亮點11.2 開發(fā)踩坑 FAQQ1啟動項目時報 PostgreSQL 連接失敗Q2大模型返回 ERROR:API 調用失敗Q3生成的 SQL 查詢不出數(shù)據(jù)前言最近公司內部有個需求業(yè)務同事不會寫 SQL每次查會員數(shù)據(jù)都要找后端開發(fā)幫忙來回溝通效率極低。想著能不能做一套自然語言轉 SQL 的小工具業(yè)務人員輸入中文描述就能自動查數(shù)據(jù)庫不用懂任何數(shù)據(jù)庫語法。調研了一圈方案本地部署大模型硬件成本太高、推理速度慢國內可直接調用的公有大模型 API 里硅基流動性價比不錯對中文 SQL 生成適配很好搭配 SpringBootPostgreSQL 就能快速落地。本篇文章我會把完整搭建流程、踩坑細節(jié)、安全處理邏輯全部整理出來從環(huán)境準備、數(shù)據(jù)庫建表、后端分層開發(fā)、大模型 API 對接再到前端問答頁面一套完整流程直接照著跑就能出效果新手也能跟著實現(xiàn)。一、項目整體介紹1.1 什么是 Text-to-SQL簡單說就是自然語言轉結構化 SQL 語句非技術人員只用大白話描述查詢需求后端對接大模型自動翻譯成標準 SQL執(zhí)行后把表格數(shù)據(jù)返回前端展示。 舉個實際場景業(yè)務輸入 “查詢所有積分超過 1 萬的金卡會員”系統(tǒng)自動生成SELECT * FROM member WHERE level 金卡會員 AND points 10000并展示匹配數(shù)據(jù)完全不用人工寫查詢語句。1.2 系統(tǒng)核心功能這套小系統(tǒng)主要做了 5 個實用能力滿足基礎業(yè)務查詢場景自然語言智能問答中文提問自動生成 SQL 并返回數(shù)據(jù)表結果會員數(shù)據(jù)總覽頁面打開首頁直接查看全量會員數(shù)據(jù)簡單統(tǒng)計總條數(shù)SQL 可視化展示大模型生成的原始 SQL 清理后完整展示支持復制復用SQL 安全攔截機制強制只允許 SELECT 查詢杜絕刪表、改數(shù)據(jù)等危險操作防止注入風險自適應前端頁面原生 JSThymeleaf 實現(xiàn)電腦、平板打開都能正常使用1.3 技術選型說明選型沒有追求花里胡哨的框架選用成熟穩(wěn)定、上手門檻低的技術方便后續(xù)二次改造分層技術棧版本選擇理由后端主框架SpringBoot3.2.0生態(tài)完善Web、JPA、JDBC 一鍵集成企業(yè)主流技術棧數(shù)據(jù)庫PostgreSQL14 及以上開源免費支持復雜查詢、中文注釋、自增序列企業(yè)數(shù)據(jù)分析常用ORM 層Spring Data JPA無指定版本簡化單表 CRUD 開發(fā)不用手寫基礎 SQL原生 SQL 執(zhí)行JdbcTemplate內置大模型生成動態(tài) SQL 后需要原生執(zhí)行工具返回表格數(shù)據(jù)頁面模板Thymeleaf內置服務端渲染頁面不用前后端分離小型工具開發(fā)更輕量化大模型服務硅基流動 API在線調用國內合規(guī)大模型服務中文理解強SQL 生成準確率高不用本地部署前端交互原生 JavaScript無框架無需引入 Vue/React減少打包、跨域等額外問題快速實現(xiàn)問答交互代碼簡化工具Lombok最新穩(wěn)定版省去實體類 get/set/toString 冗余代碼二、本地環(huán)境準備2.1 開發(fā)環(huán)境硬性要求JDK 版本最低 JDK17推薦 JDK21SpringBoot3.x 強制要求高版本 JDK低版本會直接啟動報錯構建工具Maven3.8 或者 Gradle8本文全程使用 Maven數(shù)據(jù)庫PostgreSQL14 及以上本地安裝或者 Docker 啟動均可開發(fā)工具IDEA2023 以上版本最佳VS Code 搭配 Java 插件也能開發(fā)2.2 PostgreSQL 數(shù)據(jù)庫初始化本地裝好 PostgreSQL 后打開數(shù)據(jù)庫客戶端pgAdmin/psql 命令行執(zhí)行下面 SQL新建專屬數(shù)據(jù)庫和用戶避免和本地其他業(yè)務庫沖突-- 創(chuàng)建項目專用數(shù)據(jù)庫 CREATE DATABASE llm_texttosql; -- 創(chuàng)建數(shù)據(jù)庫訪問用戶 CREATE USER postgres WITH PASSWORD postgres; -- 給用戶分配該庫全部操作權限 GRANT ALL PRIVILEGES ON DATABASE llm_texttosql TO postgres;踩坑提醒很多新手直接用 postgres 默認庫存業(yè)務表后期多項目開發(fā)容易表名沖突單獨建庫是好習慣。2.3 硅基流動 API Key 申請步驟想要調用大模型生成 SQL必須先拿到接口密鑰步驟很簡單瀏覽器打開硅基流動官網(wǎng)完成手機號注冊登錄進入控制臺 - API 密鑰管理復制生成專屬 Key注意妥善保存只展示一次模型選擇新手推薦tencent/Hunyuan-MT-7B或Qwen/Qwen2.5-7B-Instruct對中文 SQL 適配度最高計費說明新用戶一般贈送免費調用額度測試完全夠用正式使用按需充值三、項目完整目錄結構標準 SpringBoot 分層架構嚴格按照 Controller-Service-Repository 分層資源文件分類存放后續(xù)擴展多表、多接口不會混亂llm-text-to-sql/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/example/text2sql/ │ │ │ ├── Text2SqlApplication.java # 項目啟動類 │ │ │ ├── config/ │ │ │ │ └── WebConfig.java # Web擴展配置本文基礎版暫未拓展預留擴展 │ │ │ ├── controller/ │ │ │ │ └── Text2SqlController.java # 頁面路由前后端API接口 │ │ │ ├── entity/ │ │ │ │ └── Member.java # 會員數(shù)據(jù)庫實體映射類 │ │ │ ├── repository/ │ │ │ │ └── MemberRepository.java # JPA數(shù)據(jù)訪問層 │ │ │ └── service/ │ │ │ ├── LlmService.java # 硅基流動大模型API對接核心類 │ │ │ └── Text2SqlService.java # 業(yè)務核心整合服務LLMSQL執(zhí)行安全校驗 │ │ └── resources/ │ │ ├── application.yml # 全局配置文件數(shù)據(jù)庫、大模型參數(shù)全部寫在這里 │ │ ├── schema.sql # 項目啟動自動執(zhí)行建表語句 │ │ ├── data.sql # 測試會員初始化數(shù)據(jù) │ │ └── templates/ │ │ ├── index.html # 首頁會員數(shù)據(jù)總覽頁面 │ │ └── chat.html # 智能問答交互頁面 └── pom.xml # Maven依賴管理文件四、Maven 依賴與全局配置4.1 pom.xml 完整依賴所有用到的依賴全部貼出直接復制替換項目 pom.xml 即可無多余冗余包?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.0/version /parent groupIdcom.example/groupId artifactIdtext2sql/artifactId version1.0.0/version nametext2sql/name descriptionText-to-SQL會員智能問答系統(tǒng)/description properties java.version17/java.version /properties dependencies !-- SpringBoot Web容器提供接口訪問能力 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- Thymeleaf頁面模板引擎渲染前端html -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependency !-- Spring Data JPA簡化單表CRUD開發(fā) -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId /dependency !-- JdbcTemplate執(zhí)行大模型生成的動態(tài)SQL -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-jdbc/artifactId /dependency !-- PostgreSQL數(shù)據(jù)庫驅動runtime運行時生效 -- dependency groupIdorg.postgresql/groupId artifactIdpostgresql/artifactId scoperuntime/scope /dependency !-- Lombok簡化實體類代碼不用手動寫get/set -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId configuration excludes exclude groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /exclude /excludes /configuration /plugin /plugins /build /project4.2 application.yml 配置詳解所有配置做了詳細注釋新手能看懂每一項作用注意替換自己的硅基流動 API Key# 服務端口配置訪問地址localhost:8080 server: port: 8080 spring: application: name: text2sql-llm-demo # PostgreSQL數(shù)據(jù)庫連接配置和前面初始化庫對應 datasource: url: jdbc:postgresql://localhost:5432/llm_texttosql username: postgres password: postgres driver-class-name: org.postgresql.Driver # JPA持久化配置 jpa: hibernate: ddl-auto: none # 關閉自動建表統(tǒng)一使用schema.sql腳本管理表結構線上更安全 show-sql: true # 控制臺打印執(zhí)行的SQL調試排錯很方便 properties: hibernate: format_sql: true # 格式化打印SQL不會擠成一行 dialect: org.hibernate.dialect.PostgreSQLDialect # 項目啟動自動執(zhí)行初始化SQL腳本 sql: init: mode: always # 每次重啟項目都執(zhí)行腳本測試環(huán)境使用生產建議改成embedded schema-locations: classpath:schema.sql >五、數(shù)據(jù)庫表結構與測試數(shù)據(jù)5.1 schema.sql 建表語句設計一張會員業(yè)務表覆蓋姓名、等級、積分、入會時間等常用查詢維度增加字段注釋、索引提升查詢效率-- 創(chuàng)建會員信息業(yè)務表 CREATE TABLE IF NOT EXISTS member ( id SERIAL PRIMARY KEY, -- 自增主鍵會員唯一ID name VARCHAR(50) NOT NULL, -- 會員姓名非空 gender VARCHAR(10), -- 性別男/女 age INTEGER, -- 年齡 phone VARCHAR(20) UNIQUE, -- 手機號唯一約束防止重復錄入 email VARCHAR(100), -- 聯(lián)系郵箱 level VARCHAR(20) DEFAULT 普通會員, -- 會員等級普通/銀卡/金卡/鉆石 points INTEGER DEFAULT 0, -- 賬戶積分 join_date DATE, -- 入會日期 status VARCHAR(20) DEFAULT 活躍, -- 賬號狀態(tài)活躍/凍結/注銷 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- PostgreSQL專屬字段中文注釋數(shù)據(jù)庫客戶端可直接查看業(yè)務含義 COMMENT ON TABLE member IS 門店會員信息主表; COMMENT ON COLUMN member.id IS 會員主鍵ID; COMMENT ON COLUMN member.name IS 會員真實姓名; COMMENT ON COLUMN member.gender IS 會員性別; COMMENT ON COLUMN member.age IS 會員年齡; COMMENT ON COLUMN member.phone IS 綁定手機號; COMMENT ON COLUMN member.email IS 預留郵箱; COMMENT ON COLUMN member.level IS 會員會員等級; COMMENT ON COLUMN member.points IS 累計消費積分; COMMENT ON COLUMN member.join_date IS 首次入會時間; COMMENT ON COLUMN member.status IS 賬號使用狀態(tài); COMMENT ON COLUMN member.created_at IS 數(shù)據(jù)創(chuàng)建時間; -- 建立常用查詢字段索引大數(shù)據(jù)量下提升查詢速度 CREATE INDEX IF NOT EXISTS idx_member_level ON member(level); CREATE INDEX IF NOT EXISTS idx_member_points ON member(points);5.2 data.sql 初始化測試數(shù)據(jù)批量插入多條測試會員數(shù)據(jù)加入ON CONFLICT沖突判斷避免項目重復啟動報唯一鍵沖突-- 初始化會員數(shù)據(jù)使用ON CONFLICT避免重復插入 INSERT INTO member (name, gender, age, phone, email, level, points, join_date, status) VALUES (張三, 男, 28, 13800138001, zhangsanexample.com, 金卡會員, 15800, 2023-01-15, 活躍), (李四, 女, 35, 13800138002, lisiexample.com, 鉆石會員, 32000, 2022-03-20, 活躍), (王五, 男, 22, 13800138003, wangwuexample.com, 普通會員, 500, 2024-01-10, 活躍), (趙六, 女, 41, 13800138004, zhaoliuexample.com, 銀卡會員, 8200, 2023-06-01, 活躍), (錢七, 男, 30, 13800138005, qianqiexample.com, 金卡會員, 12500, 2023-08-15, 活躍), (孫八, 女, 27, 13800138006, sunbaexample.com, 普通會員, 1200, 2024-02-20, 活躍), (周九, 男, 45, 13800138007, zhoujiuexample.com, 鉆石會員, 45000, 2021-11-05, 活躍), (吳十, 女, 19, 13800138008, wushiexample.com, 普通會員, 200, 2024-05-01, 活躍), (鄭十一, 男, 33, 13800138009, zheng11example.com, 銀卡會員, 6800, 2023-04-10, 凍結), (馮十二, 女, 38, 13800138010, feng12example.com, 金卡會員, 18900, 2022-12-25, 活躍), (陳十三, 男, 25, 13800138011, chen13example.com, 普通會員, 800, 2024-03-18, 活躍), (褚十四, 女, 42, 13800138012, chu14example.com, 鉆石會員, 52000, 2021-06-30, 活躍), (衛(wèi)十五, 男, 29, 13800138013, wei15example.com, 金卡會員, 14200, 2023-09-12, 活躍), (蔣十六, 女, 36, 13800138014, jiang16example.com, 銀卡會員, 9500, 2023-05-08, 活躍), (沈十七, 男, 23, 13800138015, shen17example.com, 普通會員, 350, 2024-04-22, 注銷), (韓十八, 女, 48, 13800138016, han18example.com, 鉆石會員, 61000, 2020-08-14, 活躍), (楊十九, 男, 31, 13800138017, yang19example.com, 金卡會員, 16500, 2023-07-19, 活躍), (朱二十, 女, 26, 13800138018, zhu20example.com, 普通會員, 950, 2024-01-05, 活躍), (秦廿一, 男, 39, 13800138019, qin21example.com, 銀卡會員, 7800, 2023-02-28, 活躍), (尤廿二, 女, 34, 13800138020, you22example.com, 金卡會員, 13800, 2023-10-11, 活躍) ON CONFLICT (phone) DO NOTHING;六、實體類與數(shù)據(jù)訪問層代碼6.1 Member.java 實體映射類使用 Lombok 簡化代碼字段和數(shù)據(jù)庫一一對應區(qū)分日期、時間類型package com.example.text2sql.entity; import jakarta.persistence.*; import lombok.Data; import java.time.LocalDate; import java.time.LocalDateTime; /** * 會員信息實體類 * * 對應數(shù)據(jù)庫表member * * 字段說明 * - id: 會員ID主鍵自增 * - name: 會員姓名 * - gender: 性別男/女 * - age: 年齡 * - phone: 手機號唯一 * - email: 郵箱 * - level: 會員等級普通會員/銀卡會員/金卡會員/鉆石會員 * - points: 積分 * - joinDate: 入會日期 * - status: 狀態(tài)活躍/凍結/注銷 * - createdAt: 創(chuàng)建時間 */ Entity Table(name member) Data // Lombok注解自動生成getter/setter/toString等方法 public class Member { /** * 會員ID主鍵自增 */ Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; /** * 會員姓名非空最大長度50 */ Column(name name, nullable false, length 50) private String name; /** * 性別可選值男/女最大長度10 */ Column(name gender, length 10) private String gender; /** * 年齡 */ Column(name age) private Integer age; /** * 手機號唯一約束最大長度20 */ Column(name phone, length 20) private String phone; /** * 郵箱最大長度100 */ Column(name email, length 100) private String email; /** * 會員等級 * 可選值普通會員、銀卡會員、金卡會員、鉆石會員 * 默認值普通會員 */ Column(name level, length 20) private String level; /** * 積分默認值0 */ Column(name points) private Integer points; /** * 入會日期 */ Column(name join_date) private LocalDate joinDate; /** * 會員狀態(tài) * 可選值活躍、凍結、注銷 * 默認值活躍 */ Column(name status, length 20) private String status; /** * 記錄創(chuàng)建時間默認當前時間 */ Column(name created_at) private LocalDateTime createdAt; }6.2 MemberRepository 數(shù)據(jù)訪問接口繼承 JpaRepository 自帶基礎 CRUD額外擴展幾個常用條件查詢方法方便首頁數(shù)據(jù)統(tǒng)計package com.example.text2sql.repository; import com.example.text2sql.entity.Member; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.List; /** * 會員數(shù)據(jù)訪問接口 * * 功能說明 * - 基于Spring Data JPA提供Member實體的CRUD操作 * - 繼承JpaRepository自動獲得以下方法 * - findById(id): 根據(jù)ID查找會員 * - findAll(): 查找所有會員 * - save(member): 保存會員 * - deleteById(id): 根據(jù)ID刪除會員 * - count(): 統(tǒng)計會員數(shù)量 * * 自定義查詢方法Spring Data JPA自動生成SQL * - findByLevel(level): 根據(jù)會員等級查詢 * - findByStatus(status): 根據(jù)狀態(tài)查詢 * - findByGender(gender): 根據(jù)性別查詢 * - findByAgeGreaterThan(age): 查詢年齡大于指定值的會員 * - findByPointsGreaterThan(points): 查詢積分大于指定值的會員 */ Repository public interface MemberRepository extends JpaRepositoryMember, Long { /** * 根據(jù)會員等級查詢會員列表 * * param level 會員等級普通會員/銀卡會員/金卡會員/鉆石會員 * return 該等級的會員列表 */ ListMember findByLevel(String level); /** * 根據(jù)會員狀態(tài)查詢會員列表 * * param status 會員狀態(tài)活躍/凍結/注銷 * return 該狀態(tài)的會員列表 */ ListMember findByStatus(String status); /** * 根據(jù)性別查詢會員列表 * * param gender 性別男/女 * return 該性別的會員列表 */ ListMember findByGender(String gender); /** * 查詢年齡大于指定值的會員 * * param age 年齡閾值 * return 年齡大于指定值的會員列表 */ ListMember findByAgeGreaterThan(Integer age); /** * 查詢積分大于指定值的會員 * * param points 積分閾值 * return 積分大于指定值的會員列表 */ ListMember findByPointsGreaterThan(Integer points); }七、核心硅基流動大模型 API 對接服務 LlmService這是整個項目最核心的類負責組裝提示詞、發(fā)起 HTTP 請求調用大模型、解析返回的 SQL 語句每一步都加了異常捕獲接口調用失敗會返回明確錯誤標識方便前端提示用戶。package com.example.text2sql.service; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 大模型對接服務封裝硅基流動API所有交互邏輯 */ Service public class LlmService { // 從yml配置文件讀取大模型接口參數(shù) Value(${siliconflow.api.url}) private String apiUrl; Value(${siliconflow.api.key}) private String apiKey; Value(${siliconflow.api.model}) private String model; private final ObjectMapper objectMapper; // 構造注入JSON序列化工具 public LlmService(ObjectMapper objectMapper) { this.objectMapper objectMapper; } /** * 接收用戶自然語言調用大模型生成PostgreSQL標準SQL * param question 用戶輸入中文查詢問題 * return 生成SQL異常統(tǒng)一返回ERROR:開頭錯誤信息 */ public String textToSql(String question) { // 系統(tǒng)提示詞告訴大模型數(shù)據(jù)庫表結構、輸出規(guī)范是SQL生成準確率關鍵 String systemPrompt 你是專業(yè)PostgreSQL SQL生成助手嚴格根據(jù)下方member表結構把用戶中文問題轉換成可直接執(zhí)行的SQL語句。 數(shù)據(jù)庫表member完整結構 - id: 會員ID (SERIAL PRIMARY KEY) - name: 會員姓名 (VARCHAR(50)) - gender: 性別 (VARCHAR(10)) 可選值僅男、女 - age: 年齡 (INTEGER) - phone: 手機號 (VARCHAR(20)) - email: 郵箱 (VARCHAR(100)) - level: 會員等級 (VARCHAR(20)) 可選值普通會員、銀卡會員、金卡會員、鉆石會員 - points: 累計積分 (INTEGER) - join_date: 入會日期 (DATE) - status: 賬號狀態(tài) (VARCHAR(20)) 可選值活躍、凍結、注銷 - created_at: 創(chuàng)建時間 (TIMESTAMP) 強制輸出要求 1. 只輸出純SQL語句不要任何解釋、說明文字 2. 嚴格遵循PostgreSQL語法中文字符串用單引號包裹 3. 用戶問題無法生成有效查詢時直接返回固定文本ERROR:無法理解的問題 4. 禁止生成DELETE、UPDATE、DROP、ALTER等修改、刪除類SQL只輸出SELECT相關語句 ; try { // 組裝大模型請求體 MapString, Object requestBody new HashMap(); requestBody.put(model, model); // 系統(tǒng)角色消息表結構規(guī)則約束 MapString, String systemMsg Map.of(role, system, content, systemPrompt); // 用戶提問消息 MapString, String userMsg Map.of(role, user, content, question); requestBody.put(messages, List.of(systemMsg, userMsg)); requestBody.put(max_tokens, 512); // temperature越低輸出結果越固定、不會隨意發(fā)揮SQL場景建議0.1 requestBody.put(temperature, 0.1); // JSON序列化請求參數(shù) String jsonReq objectMapper.writeValueAsString(requestBody); // 創(chuàng)建HTTP客戶端發(fā)起接口調用 HttpClient httpClient HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(30)) .build(); HttpRequest request HttpRequest.newBuilder() .uri(URI.create(apiUrl)) .header(Authorization, Bearer apiKey) .header(Content-Type, application/json) .timeout(Duration.ofSeconds(60)) .POST(HttpRequest.BodyPublishers.ofString(jsonReq)) .build(); // 接收接口返回結果 HttpResponseString response httpClient.send(request, HttpResponse.BodyHandlers.ofString()); // 接口正常返回200狀態(tài)碼解析生成的SQL if (response.statusCode() 200) { JsonNode root objectMapper.readTree(response.body()); JsonNode choices root.get(choices); if (choices ! null choices.isArray() choices.size() 0) { return choices.get(0).get(message).get(content).asText().trim(); } return ERROR:大模型返回數(shù)據(jù)格式異常; } else { // 接口調用失敗返回狀態(tài)碼方便排查 return ERROR:API調用失敗HTTP狀態(tài)碼: response.statusCode(); } } catch (Exception e) { // 捕獲所有網(wǎng)絡、序列化異常統(tǒng)一封裝錯誤信息 return ERROR: e.getMessage(); } } }開發(fā)重點說明提示詞設計完整把表字段、枚舉值、語法規(guī)則全部傳給大模型是避免生成錯誤 SQL 的核心如果后期 SQL 經(jīng)常出錯優(yōu)先優(yōu)化提示詞而非調整代碼。temperature 參數(shù)文本創(chuàng)作場景會調高到 0.7-1但是 SQL 生成需要精準設置 0.1 限制模型隨機發(fā)揮。統(tǒng)一錯誤前綴所有異常都用ERROR:開頭上層業(yè)務服務直接判斷前綴就能區(qū)分正常 SQL 和錯誤信息邏輯更簡潔。八、核心業(yè)務整合服務 Text2SqlService串聯(lián)大模型調用、SQL 清洗、安全校驗、數(shù)據(jù)庫執(zhí)行整套流程增加安全攔截邏輯防止用戶誘導大模型生成刪改數(shù)據(jù) SQL是保障系統(tǒng)安全的關鍵層。package com.example.text2sql.service; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import java.util.*; /** * Text-to-SQL整合業(yè)務服務串聯(lián)大模型、SQL安全校驗、數(shù)據(jù)庫執(zhí)行 */ Service public class Text2SqlService { private final LlmService llmService; private final JdbcTemplate jdbcTemplate; private final ObjectMapper objectMapper; // 構造注入依賴 public Text2SqlService(LlmService llmService, JdbcTemplate jdbcTemplate, ObjectMapper objectMapper) { this.llmService llmService; this.jdbcTemplate jdbcTemplate; this.objectMapper objectMapper; } /** * 完整問答處理主流程 * 1.調用大模型生成SQL → 2.清理SQL多余標記 → 3.安全校驗只允許SELECT → 4.執(zhí)行查詢返回數(shù)據(jù) * param question 用戶輸入中文問題 * return 包含原始問題、生成SQL、查詢結果、成功/失敗標識的Map */ public MapString, Object query(String question) { MapString, Object resultMap new HashMap(); resultMap.put(question, question); // 第一步調用大模型獲取SQL String rawSql llmService.textToSql(question); resultMap.put(generatedSql, rawSql); // 判斷大模型是否返回錯誤 if (rawSql.startsWith(ERROR)) { resultMap.put(success, false); resultMap.put(error, rawSql); return resultMap; } // 第二步清洗SQL去除AI附帶的markdown代碼塊標記 String cleanSql cleanSqlText(rawSql); resultMap.put(cleanedSql, cleanSql); try { // 第三步安全校驗攔截非查詢類SQL if (!checkSqlSafe(cleanSql)) { resultMap.put(success, false); resultMap.put(error, 安全攔截系統(tǒng)僅支持SELECT查詢語句禁止修改/刪除數(shù)據(jù)操作); return resultMap; } // 第四步執(zhí)行動態(tài)SQL返回表格數(shù)據(jù) ListMapString, Object dataList jdbcTemplate.queryForList(cleanSql); resultMap.put(success, true); resultMap.put(data, dataList); resultMap.put(count, dataList.size()); } catch (Exception e) { // SQL語法錯誤、表不存在等數(shù)據(jù)庫異常捕獲 resultMap.put(success, false); resultMap.put(error, SQL執(zhí)行失敗 e.getMessage()); } return resultMap; } /** * 清理大模型返回SQL附帶的多余符號比如sql、、SQL:前綴 */ private String cleanSqlText(String sql) { if (sql null || sql.isEmpty()) return ; sql sql.replaceAll(sql, ).replaceAll(, ).trim(); if (sql.toLowerCase().startsWith(sql:)) { sql sql.substring(4).trim(); } return sql; } /** * SQL安全校驗僅允許SELECT開頭支持WITH子句CTE查詢 * 攔截UPDATE/DELETE/DROP/ALTER等危險操作規(guī)避數(shù)據(jù)安全風險 */ private boolean checkSqlSafe(String sql) { String upperSql sql.toUpperCase().trim(); return upperSql.startsWith(SELECT) || upperSql.startsWith(WITH); } /** * 查詢全部會員數(shù)據(jù)首頁總覽頁面使用 */ public ListMapString, Object getAllMemberData() { return jdbcTemplate.queryForList(SELECT * FROM member ORDER BY id ASC); } }安全邏輯重點說明很多新手做 Text-to-SQL 項目會忽略安全問題直接執(zhí)行大模型返回的 SQL一旦有人刻意誘導大模型生成DROP TABLE member整張業(yè)務表會直接刪除。本文做了兩層防護提示詞約束大模型禁止生成修改類 SQL代碼層二次校驗 SQL 開頭關鍵字雙重攔截避免數(shù)據(jù)事故。九、Controller 頁面路由與 API 接口區(qū)分頁面跳轉接口和 JSON 數(shù)據(jù)接口使用 Controller 返回頁面ResponseBody 返回 JSON接口注釋清晰方便后續(xù)對接前端或者第三方系統(tǒng)。package com.example.text2sql.controller; import com.example.text2sql.service.Text2SqlService; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Map; /** * 系統(tǒng)控制器頁面跳轉、前端問答API統(tǒng)一處理 */ Controller public class Text2SqlController { private final Text2SqlService text2SqlService; public Text2SqlController(Text2SqlService text2SqlService) { this.text2SqlService text2SqlService; } /** * 首頁會員數(shù)據(jù)總覽頁面 */ GetMapping(/) public String indexPage(Model model) { ListMapString, Object allMember text2SqlService.getAllMemberData(); model.addAttribute(members, allMember); model.addAttribute(totalCount, allMember.size()); return index; } /** * 智能問答聊天頁面 */ GetMapping(/chat) public String chatPage() { return chat; } /** * 問答核心API接口前端AJAX異步調用 * 請求體{question:查詢鉆石會員} */ PostMapping(/api/query) ResponseBody public MapString, Object queryData(RequestBody MapString, String request) { String question request.get(question); if (question null || question.trim().length() 0) { return Map.of(success, false, error, 輸入內容不能為空請描述你的查詢需求); } return text2SqlService.query(question); } /** * 獲取全量會員數(shù)據(jù)接口可供第三方調用 */ GetMapping(/api/members) ResponseBody public ListMapString, Object getMemberList() { return text2SqlService.getAllMemberData(); } }十、前端頁面代碼實現(xiàn)10.1 首頁 index.html會員數(shù)據(jù)總覽使用 Thymeleaf 循環(huán)渲染會員表格簡單統(tǒng)計會員總數(shù)頁面樣式簡潔適配辦公場景。body div classheader h1 全部會員數(shù)據(jù)總覽/h1 div classnav a href/chat進入AI智能問答/a /div /div div classcount-card h3當前會員總條數(shù)span th:text${totalCount}0/span/h3 /div table thead tr thID/th th姓名/th th性別/th th年齡/th th手機號/th th會員等級/th th累計積分/th th賬號狀態(tài)/th th入會日期/th /tr /thead tbody tr th:eachitem : ${members} td th:text${item.id}/td td th:text${item.name}/td td th:text${item.gender}/td td th:text${item.age}/td td th:text${item.phone}/td td th:text${item.level}/td td th:text${item.points}/td td th:text${item.status}/td td th:text${item.join_date}/td /tr /tbody /table /body展示效果如下10.2 問答頁面 chat.html核心交互頁面原生 JS 實現(xiàn)表單提交、異步請求、結果渲染自帶示例快捷提問按鈕增加 HTML 轉義函數(shù)防止 XSS 攻擊展示生成 SQL 和查詢表格。script // 填充示例問題到輸入框 function fillExample(text) { document.getElementById(questionInput).value text; } // 表單提交監(jiān)聽 const form document.getElementById(queryForm); form.addEventListener(submit, async function(e) { e.preventDefault(); const inputVal document.getElementById(questionInput).value.trim(); if (!inputVal) { alert(請輸入查詢問題); return; } try { // 調用后端查詢接口 const res await fetch(/api/query, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({question: inputVal}) }); const result await res.json(); renderResult(inputVal, result); } catch (err) { alert(網(wǎng)絡請求失敗 err.message); } }); // 渲染查詢結果到頁面 function renderResult(question, res) { let htmlStr div classresult-item div classquestion? 你的問題${htmlEscape(question)}/div; if (res.success) { htmlStr div生成SQL語句/div div classsql-block${htmlEscape(res.cleanedSql)}/div div匹配到${res.count}條數(shù)據(jù)/div table thead tr ${Object.keys(res.data[0] || {}).map(k th${htmlEscape(k)}/th).join()} /tr /thead tbody ${res.data.map(row tr ${Object.values(row).map(v td${htmlEscape(v || )}/td).join()} /tr ).join()} /tbody /table ; } else { htmlStr div classerror-text? 查詢失敗${htmlEscape(res.error)}/div; } htmlStr /div; // 最新結果插入最上方 document.getElementById(resultContainer).insertAdjacentHTML(afterbegin, htmlStr); } // HTML轉義防止XSS注入 function htmlEscape(text) { const div document.createElement(div); div.textContent text; return div.innerHTML; } /script十一、項目總結11.1 項目核心亮點完整可落地從數(shù)據(jù)庫建表、后端分層、大模型對接、前端頁面全套代碼復制即可運行無缺失模塊雙層 SQL 安全防護提示詞約束 代碼關鍵字攔截杜絕刪改表等高危操作企業(yè)內部使用更放心輕量化無復雜依賴不引入 Vue、Redis、消息隊列等重型組件小型工具快速開發(fā)部署用戶友好前端自帶示例快捷提問自動展示生成 SQL業(yè)務人員可以復制 SQL 復用完善異常捕獲大模型接口報錯、SQL 語法錯誤、空輸入全部做友好提示便于排查問題。11.2 開發(fā)踩坑 FAQQ1啟動項目時報 PostgreSQL 連接失敗A檢查 yml 數(shù)據(jù)庫 url、賬號密碼確認本地 PostgreSQL 服務正常啟動5432 端口沒有被占用同時確認 llm_texttosql 數(shù)據(jù)庫已提前創(chuàng)建。Q2大模型返回 ERROR:API 調用失敗A核對硅基流動 API Key 是否復制正確Key 前后不要帶空格檢查本地網(wǎng)絡是否能訪問硅基流動外網(wǎng)接口新用戶查看是否還有免費調用額度。Q3生成的 SQL 查詢不出數(shù)據(jù)A優(yōu)先檢查提示詞里的字段枚舉值是否和數(shù)據(jù)庫一致比如 “金卡會員” 不能寫成 “金卡”其次優(yōu)化提示詞增加 1-2 條查詢示例給大模型參考提升匹配準確率。這套 Text-to-SQL 系統(tǒng)完全適配中小企業(yè)內部數(shù)據(jù)查詢場景不用業(yè)務人員學習 SQL 語法開發(fā)維護成本很低。文章里所有代碼都是實際運行調試后的完整版本大家可以直接復制搭建有任何搭建報錯、功能拓展的問題。行文倉促定有不足之處歡迎各位朋友在評論區(qū)批評指正不勝感激。

相關新聞

Unity強化學習套件ml-agents-release_23_tag安裝

Unity強化學習套件ml-agents-release_23_tag安裝

強化學習仿真通過 unity 可以獲取不錯的可視化效果,依托游戲引擎也可以更真實的反映物理邏輯。套件官方描述如下: Unity 機器學習代理工具包(ML-Agents)是一個開源項目,可讓游戲和模擬環(huán)境成為訓練智能代理的平臺。我們提供了基于 PyTorch 的前沿算法實現(xiàn),使游戲開發(fā)者和…

2026/8/4 13:43:12 閱讀更多
openKylin與Ubuntu跨系統(tǒng)文件傳輸實戰(zhàn)指南

openKylin與Ubuntu跨系統(tǒng)文件傳輸實戰(zhàn)指南

1. 跨系統(tǒng)文件傳輸?shù)耐袋c與解決方案在國產操作系統(tǒng)openkylin和主流Linux發(fā)行版Ubuntu之間傳輸文件,是不少開發(fā)者日常工作中的剛需場景。openkylin作為基于Linux的國產操作系統(tǒng),與Ubuntu雖然同屬Linux家族,但在文件系統(tǒng)結構、默認工具鏈和網(wǎng)絡…

2026/8/4 13:43:12 閱讀更多
Java Hash機制深度解析與性能優(yōu)化實踐

Java Hash機制深度解析與性能優(yōu)化實踐

1. Java中的Hash機制深度解析 在Java開發(fā)中,Hash是貫穿整個技術體系的核心概念。從HashMap的鍵值存儲到HashSet的元素去重,從Object的hashCode()方法到安全領域的消息摘要,Hash技術無處不在。但很多開發(fā)者對它的理解僅停留在"用來快速查…

2026/8/4 13:43:12 閱讀更多
3分鐘搞定!QQ空間歷史說說完整備份終極指南

3分鐘搞定!QQ空間歷史說說完整備份終極指南

3分鐘搞定!QQ空間歷史說說完整備份終極指南 【免費下載鏈接】GetQzonehistory 獲取QQ空間發(fā)布的歷史說說 項目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 你是否曾想過,那些年發(fā)過的QQ空間說說,那些記錄青春的文字…

2026/8/4 13:10:06 閱讀更多
AMAT 0100-02186 I/O 分配 PCB

AMAT 0100-02186 I/O 分配 PCB

AMAT 0100-02186 I/O分配PCB板是應用材料(Applied Materials)公司生產的一款用于半導體設備的I/O信號分配電路板。該型號(0100-02186)的核心特點如下:專用于Endura等半導體工藝腔室。集成信號路由與分配功能。連接控制…

2026/8/3 19:34:52 閱讀更多
Nissei Corp FFMN-32L-10-T0 40AX 三相異步電動機

Nissei Corp FFMN-32L-10-T0 40AX 三相異步電動機

Nissei Corp FFMN-32L-10-T0 40AX 三相異步電動機是日本日清(Nissei)品牌的一款工業(yè)用三相異步電機,適用于自動化設備及通用機械驅動。該型號(FFMN-32L-10-T0 40AX)的核心特點如下:三相交流異步電動機。額定…

2026/8/3 19:34:54 閱讀更多