我开发了一个ai智能问答系统,功能是可创建知识库,文档存入知识库,用户提问,ai检索知识库的相关内容回答。所用到的东西是百炼平台,postgreSql,腾讯云cos,系统有点庞大,想要源码和有问题的可以联系我,qq邮箱:1328113017@qq.com
一、数据库与存储层
1. PostgreSQL vector类型不存在
-
现象 :执行
ALTER TABLE ... TYPE vector(1536)或CAST(... AS vector)时报type "vector" does not exist。 -
根因 :pgvector 扩展未安装,或安装后当前数据库会话的
search_path未包含扩展所在的 schema(通常是public)。 -
解决:
-
安装 pgvector 扩展(宝塔环境需手动编译安装,或确认软件商店已安装)。
-
用超级用户执行
CREATE EXTENSION IF NOT EXISTS vector SCHEMA public; -
设置数据库默认搜索路径:
ALTER DATABASE intellikb SET search_path = intellikb_schema, public; -
或在 JDBC URL 中通过
options=-c%20search_path=intellikb_schema,public显式传递。
-
2. MyBatis-Plus 插入返回 rows affected = null
-
现象 :
documentChunkMapper.insert(chunk)返回null,且数据未入库。 -
根因 :MyBatis-Plus 实体类
@TableName未指定schema,导致插入到错误的 schema(如public);或 XML 映射文件与注解方法冲突。 -
解决:
-
实体类添加
@TableName(value = "document_chunk", schema = "intellikb_schema")。 -
删除冲突的 XML 文件,统一使用注解。
-
改用
JdbcTemplate执行原生 SQL,彻底绕开 MyBatis-Plus 的映射问题。
-
3. CAST(? AS vector)参数类型无法推断
-
现象 :执行
CAST(#{queryVec} AS vector)时报could not determine data type of parameter。 -
根因 :
#{kbId} IS NULL中?参数无上下文类型;或queryVec为null/空字符串。 -
解决:
-
使用
CAST(#{kbId} AS bigint) IS NULL显式指定类型。 -
在 Java 层确保
queryVec不为空(Arrays.toString(float[])至少产生[0.0])。 -
SQL 中加入
AND #{queryVec} IS NOT NULL AND #{queryVec} != ''过滤空值。
-
二、后端业务逻辑
4. 文档上传后 document_chunk无数据
-
现象 :上传成功但切片表为空,日志显示
Saving chunk index=...但最终无记录。 -
根因 :
@Transactional事务回滚------向量生成阶段(generateEmbeddingsForDocument)抛出异常,导致整个事务回滚,之前插入的切片被撤销。 -
解决:
-
将向量生成改为异步执行(
@Async+ 独立事务REQUIRES_NEW),或捕获异常不往外抛。 -
先注释掉向量生成步骤,单独测试切片插入,确认问题是否在此。
-
5. 百炼接口返回 url error/ InvalidParameter
-
现象 :调用百炼 Chat 或 Embedding 接口时返回
{"code":"InvalidParameter","message":"url error, please check url!"}。 -
根因:
-
baseUrl配置错误(如带了/compatible-mode/v1但与原生端点不匹配)。 -
model参数使用了社区名(如qwen3.7-plus)而非百炼模型 ID(如qwen-plus)。
-
-
解决:
-
统一使用百炼原生根域名
https://dashscope.aliyuncs.com。 -
Chat 模型填
qwen-plus,Embedding 模型填text-embedding-v2。 -
在 WebClient 中添加
onStatus错误日志,便于调试。
-
6. SSE 流式响应解析失败
-
现象 :百炼返回了完整 JSON(非流式格式),但后端只解析
data:前缀的行,导致 Flux 空流,前端进入error。 -
根因 :百炼
stream: true参数未生效(可能是请求体格式问题),返回了非流式响应。 -
解决:
-
确认请求体
parameters.stream为true。 -
在
flatMap中同时支持流式(data: {...})和非流式({...})两种格式。 -
使用
ServerSentEvent包装响应,前端监听end事件。
-
三、前端与交互
7. uni-nav-bar组件不可用
-
现象 :使用
uni-nav-bar报错或样式异常。 -
根因:项目使用的是 NutUI 组件库,而非 uni-ui。
-
解决 :替换为
nut-navbar,注意属性差异(如left-show、@on-click-back)。
8. 图片上传组件进度显示
-
现象:上传封面或文档时无进度反馈。
-
根因 :
uni.uploadFile默认不显示进度。 -
解决 :监听
uploadTask.onProgressUpdate,在组件内展示进度条(progress值驱动宽度动画)。
9. SSE 前端连接进入 error但后端无日志
-
现象 :传
kbId时前端 EventSource 触发error,后端控制台无异常。 -
根因 :后端返回了非 200 状态码(如 500)或响应头缺少
Content-Type: text/event-stream,浏览器拒绝解析。 -
解决:
-
Controller 方法明确指定
produces = MediaType.TEXT_EVENT_STREAM_VALUE。 -
全局异常处理器对 SSE 请求返回合适的错误格式(或直接抛出,让 Spring 默认处理)。
-
前端增加
onerror日志输出,便于定位。
-
10. Vue3 响应式数据不更新
-
现象 :
currentKbName在onKbChange中赋值后,模板未刷新。 -
根因 :变量未使用
ref包裹,或赋值时忘了.value。 -
解决 :统一使用
ref定义响应式变量,赋值时加.value;在异步回调中特别注意。
四、部署与环境
11. 宝塔 PostgreSQL 连接问题
-
现象 :
psql命令找不到、socket 文件不存在、Navicat 连不上。 -
根因:宝塔的 PostgreSQL 路径和默认 socket 位置与原生 Linux 不同。
-
解决:
-
使用宝塔面板内的"终端"功能,或指定
-h 127.0.0.1 -p 端口通过 TCP 连接。 -
在宝塔软件商店确认 pgvector 是否已安装,未安装则手动编译。
-
12. 文件上传大小限制
-
现象 :
Maximum upload size exceeded。 -
根因 :Spring Boot 默认
max-file-size=1MB。 -
解决 :在
application.yml中配置spring.servlet.multipart.max-file-size=10MB和max-request-size=20MB。
13.隔一段时间访问百炼就报Connection reset
问题描述
后端服务通过 WebClient(Reactor Netty)调用百炼(DashScope)API 时,在请求间隔较长(如超过 1 分钟)后 ,下一次请求会抛出 java.io.IOException: Connection reset异常,导致对话或向量生成失败。
根因分析
-
连接池复用机制 :
WebClient底层使用 Reactor Netty 的连接池,复用 HTTP 长连接。 -
网关空闲断开 :阿里云 SLB / 百炼网关默认空闲超时约为 60 秒,超过该时间会主动 RST 连接。
-
连接池未及时感知 :Netty 连接池默认的空闲连接驱逐时间较长(或未配置),导致池中保留的"半死"连接在下次请求时被复用,触发
Connection reset。
解决方案(三件套)
1️⃣ 自定义连接池,提前驱逐空闲连接
配置 ConnectionProvider,将 maxIdleTime设为 45 秒(小于网关 60 秒),并开启后台定期扫描:
ConnectionProvider.builder("dashscope")
.maxIdleTime(Duration.ofSeconds(45))
.evictInBackground(Duration.ofSeconds(30))
.build();
2️⃣ 为流式调用(Flux)添加重试机制
对 Connection reset异常进行 1 次指数退避重试,自动重建连接:
.retryWhen(Retry.backoff(1, Duration.ofMillis(500))
.filter(t -> t instanceof IOException
|| t.getMessage().contains("Connection reset")))
3️⃣ 为同步调用(block)也添加重试
Embedding 等同步请求同样可能触发 RST,使用相同 retryWhen保护:
.bodyToMono(String.class)
.retryWhen(Retry.backoff(1, Duration.ofMillis(500))
.filter(t -> ...))
.block();
效果
-
空闲连接在 45 秒内被主动清理,永远不会等到网关超时。
-
即使偶发 RST,自动重试一次即可恢复,用户无感。
-
系统稳定性显著提升,长时间无人提问后再次使用不会报错。
经验教训
-
调用外部 API 时,连接池的空闲超时配置必须小于网关的超时时间 ,否则必然出现
Connection reset。 -
对于网络抖动或短暂故障,合理使用重试机制(限制次数、指数退避)是提高鲁棒性的有效手段。
-
生产环境中,建议为每个外部服务独立配置连接池和超时参数,避免相互影响。
五、架构与设计决策
| 决策点 | 选择 | 理由 |
|---|---|---|
| 向量数据库 | PostgreSQL + pgvector | 与业务库同库,免运维,支持 HNSW 索引 |
| 切片策略 | 固定字符数(500)+ 重叠(50) | 简单可控,后期可替换为语义分割 |
| 流式输出 | SSE(Server-Sent Events) | 实现简单,浏览器原生支持 |
| 前端 UI 库 | NutUI | 与项目已有组件库统一 |
| 文件存储 | 腾讯云 COS(服务端上传) | 稳定可靠,后期可升级为前端直传 + STS |
六、经验教训
-
数据库扩展先行 :使用 PostgreSQL 的自定义类型(如
vector)前,务必先确认扩展安装和search_path配置,否则后续所有操作都会受阻。 -
统一 MyBatis 配置 :避免混合使用 XML 和注解,容易冲突;对于复杂类型转换,直接使用
JdbcTemplate更可控。 -
事务粒度控制:耗时操作(如调用外部 API 生成向量)不应与核心业务(如切片保存)放在同一事务中,应异步处理或使用独立事务。
-
SSE 调试技巧 :后端增加
doOnNext/doOnError日志,前端检查 Network 面板的响应体和状态码,快速定位是连接问题还是数据问题。 -
响应式数据检查 :Vue3 中遇到模板不更新,优先检查变量是否用
ref/reactive定义,以及赋值时是否加了.value。
七、当前系统能力矩阵
| 功能 | 状态 |
|---|---|
| 知识库 CRUD | ✅ |
| 文档上传(txt) + 切片 | ✅ |
| 向量化存储(pgvector) | ✅ |
| 向量检索(余弦距离) | ✅ |
| 全局 / 指定知识库问答 | ✅ |
| SSE 流式输出 | ✅ |
| 封面上传(进度条) | ✅ |
| 文档列表展示 | ✅ |
这份总结涵盖了从数据库、后端、前端到部署的全链路问题。如果你需要针对某个具体问题展开详细的技术文档(如 pgvector 安装步骤、SSE 最佳实践),我可以帮你整理。