# rag-knowledge-app-vue **Repository Path**: weh_coder/rag-knowledge-app-vue ## Basic Information - **Project Name**: rag-knowledge-app-vue - **Description**: Vue前端:多租户知识库问答(同步 + 流式 SSE) 文档上传 / 更新(发版)/ 软删除 / 列表 / 版本审计 会话历史(增删查、重命名、统计) 基于 JWT + Redis 的登录态鉴权 多租户、按部门 / 全公司的可见性权限(在 Milvus 检索 filter 中生效) - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-15 - **Last Updated**: 2026-08-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # RAG 知识库问答平台开发文档 > 技术栈:Spring Boot 3.2.5 / Java 17 / Spring AI 1.1.2 / Milvus 2.4 / Redis / JWT > 配套契约:《后端API接口文档.md》(v2.0.0,前端 Vue 3 + Element Plus) > 编写日期:2026-08-12|最后更新:2026-08-15(同步最新实现:Milvus 原生 BM25 Function 稀疏检索、阿里云 rerank API、JVM UTF-8 要求、统一日志 AOP、StatusCodeEnum 状态码枚举化) --- ## 1. 项目概述 本服务是为企业知识库问答(RAG)系统提供的后端,前端采用 `baseURL: '/api'` 的 axios 封装。后端所有业务接口统一挂在 `/api` 前缀下,并严格对齐《后端API接口文档》的「统一响应包裹」「字段命名(camelCase)」「SSE 事件格式」等契约。 核心能力: - 多租户知识库问答(同步 + 流式 SSE) - 文档上传 / 更新(发版)/ 软删除 / 列表 / 版本审计 - 会话历史(增删查、重命名、统计) - 基于 JWT + Redis 的登录态鉴权 - 多租户、按部门 / 全公司的可见性权限(在 Milvus 检索 filter 中生效) --- ## 2. 技术栈与依赖 | 领域 | 选型 | |------|------| | Web 框架 | Spring Boot 3.2.5(`spring-boot-starter-web`,Servlet 栈) | | 语言 | Java 17 | | AI / RAG | Spring AI 1.1.2(`spring-ai-starter-model-openai`、`spring-ai-starter-model-transformers`) | | 向量库 | Milvus(`milvus-sdk-java` **2.6.10**,Java SDK v2,原生 `hybridSearch` + `RRFRanker` + BM25 Function) | | 缓存 / 会话 | Redis(`spring-boot-starter-data-redis`) | | 鉴权 | JJWT 0.11.5 + 自定义 `JwtAuthFilter` | | 稀疏检索 | **Milvus 原生 BM25 Function**:`content` 启用 jieba analyzer + text match,服务端自动从 `content` 生成 `sparse_vector`;检索 `sparse_vector` 传查询文本 `EmbeddedText`(**不再**在应用层计算 jieba+IDF `SparseFloatVec`,旧 `SparseVectorUtil` 方案已废弃,代码保留但不在检索主链路调用) | | 重排(rerank) | **阿里云 rerank API**(`RerankerConfig`,cross-encoder 相关性打分;低于阈值丢弃,异常 `fallback` 保序);旧的 `rag.chat.rerank-*-weight` 加权权重为**死配置** | | 统一日志 | `spring-boot-starter-aop`(AOP 切面 `LoggingAspect` + `@Loggable` 注解,无需逐方法手写日志) | | 统一状态码 | `StatusCodeEnum` 枚举(收敛 200/400/401/403/404/413/500/503,禁止裸整数魔法值) | | 文档解析 | PDFBox 3.0.1、POI 5.2.5(docx / doc) | | 接口文档 | springdoc-openapi 2.3.0(`/v3/api-docs`、`/swagger-ui`) | | 工具 | Lombok、Gson、Hutool-free | | 测试 | JUnit 5 + Mockito + Spring MockMvc + `@SpringBootTest` | > ⚠️ 本项目**未**继承 `spring-boot-starter-parent`,因此在 `pom.xml` 中显式锁定了 `maven-compiler-plugin` ≥ 3.11.0,并在 `annotationProcessorPaths` 中声明 Lombok 1.18.32,否则 Lombok 注解(`@Data/@Builder`)不生效。 --- ## 3. 工程结构 ``` rag-search-app/ ├── pom.xml ├── models/ # 本地 transformer 稠密向量模型(onnx + tokenizer.json) ├── storage/ # 上传文档落盘目录(运行时生成) ├── src/main/java/com/weh/rag/ │ ├── RagSearchApplication.java # 启动类 │ ├── config/ # AiConfig / VectorStoreConfig / RedisConfig / SwaggerConfig / WebConfig / RerankerConfig(阿里云 rerank) / AppMilvusProperties │ ├── controller/ # Auth / History / Knowledge / Document / Department / Health / Admin(运维) │ ├── service/ (impl/) # 业务门面:Auth / History / Knowledge / Document / Department / Redis / VectorStore / MilvusSchema │ ├── dao/ (impl/) # 数据访问:Auth / History / Document / Milvus │ ├── entity/ # User / Tenant / Department / HistoryModel │ ├── model/ │ │ ├── dto/ # 入参 / 内部传输对象(ChatDTO / UserInfoDTO / DocumentChunkDTO …) │ │ ├── vo/ # 出参视图对象(ResultVO / QueryKnowledgeVO / DocumentVO / UserInfoVO …) │ │ └── enums/ # StatusCodeEnum(统一状态码) │ ├── filter/ # JwtAuthFilter │ ├── exception/ # ControllerAdviceHandler / MyCustomException │ ├── aspect/ # LoggingAspect(统一方法级日志 AOP 切面) │ ├── annotation/ # Loggable / LogLevel(统一日志注解) │ └── util/ # JwtTokenUtils / HashUtils / MilvusFilterBuilder / SessionUtils / BeanCopyUtil / SparseVectorUtil(废弃) / UserContextUtils └── src/test/java/com/weh/rag/ # 单元测试 + 集成测试(11 用例 / 3 测试类:LoggingAspectTest / RerankTest / ResultVOTest) ``` 分层规范(阿里规范):Controller 仅做参数校验与结果封装,业务逻辑下沉到 Service,数据访问下沉到 Dao。 --- ## 4. 编译与构建 ### 4.1 关键约束(务必遵守) - **必须用 `mvn.cmd` 而非 `mvn`**:在 Git Bash 下直接调用 `mvn` 会因路径转换触发 `classworlds` / `NoClassDefFoundError` 等异常。正确做法: ```bash export PATH="$PATH:/d/Java/apache-maven-3.6.3/bin" mvn.cmd -B clean package -DskipTests ``` - **JDK 17**:`JAVA_HOME` 指向 `D:\Java\jdk-17.0.12`。 - **Maven 本地仓库**:`D:\Java\apache-maven-3.6.3\repository`。 - `maven-surefire-plugin` 已锁定为 `3.2.5`(兼容 JUnit 5)。 ### 4.2 常用命令 ```bash # 仅编译 mvn.cmd -B clean compile # 编译 + 跑全部测试 mvn.cmd -B clean test # 跑单个测试类 / 方法 mvn.cmd -B test -Dtest=ApiContractTest mvn.cmd -B test -Dtest=ApiContractTest#knowledge_stream_returnsSseEvents # 打可执行 jar(模型文件不进 jar,运行时从外部 models/ 加载) mvn.cmd -B clean package -DskipTests # 启动(必须显式指定端口 + UTF-8!BM25 Function 中文稀疏检索依赖 JVM 默认字符集为 UTF-8) java -Dfile.encoding=UTF-8 -Dserver.port=8081 -jar target/rag-search-app-1.0.0.jar ``` --- ## 5. 配置说明 主配置 `application.yml` 通过环境变量 / profile 占位符解耦,实际值见 `application-dev.yml`(`spring.profiles.active=dev`)。 | 配置项 | 说明 | dev 示例 | |--------|------|---------| | `server.port` | 服务端口 | 8081(与前端 vite 代理目标一致) | | `spring.data.redis.*` | Redis 连接 | localhost:6379 / db 1 | | `spring.ai.openai.base-url` | LLM 兼容端点(OpenAI 协议) | 阿里云百炼兼容模式地址 | | `spring.ai.openai.chat.options.model` | 对话模型 | qwen3.7-max-preview | | `spring.ai.embedding.transformer.*` | 本地稠密向量模型(onnx + tokenizer) | `file:models/dense/...` | | `app.milvus.*` | Milvus(Zilliz serverless)uri / token / collection | `rag_search_collection` | | `app.milvus.dense-dimension` | 稠密向量维度 | 768 | | `milvus.sparse-dimension` | 稀疏向量维度(jieba 词哈希分桶数,仅 `content` analyzer 参考) | 65536 | | `rag.chat.*` | 检索 topK / final-top-k / RRF-k / 阈值 / 切片大小(rerank-top-k 生效;`rerank-*-weight` 三项是**死配置**,不再参与排序) | retrieval-top-k=10, rrf-top-k=8, rrf-k=60 | | `aliyun.rerank.*` | 阿里云 rerank API(endpoint / token / model / threshold) | 用于 `RerankerConfig` 相关性精排 | | `app.logging.*` | 统一日志开关:`global`(全局自动记录) / `log-args`(记参数) / `log-result`(记返回值) | global=true, log-args=true, log-result=false | | `jwt.secret` / `jwt.expiration` | JWT 密钥 / 过期(ms) | 1h | > ⚠️ **JVM 字符集**:BM25 Function 的中文稀疏检索依赖 JVM 默认字符集为 **UTF-8**。启动时**必须**带 `-Dfile.encoding=UTF-8`(见 §4.2 / §11),否则 GBK 环境下中文召回会静默失败(`HealthCheckerListener` 启动时会告警)。 > > 注意:`application-dev.yml` 内含真实 LLM API Key 与 Milvus token,**请勿提交到公开仓库**。 --- ## 6. 鉴权机制 - 登录:`POST /api/auth/login` → 返回 `data.token`(JWT)。 - 后续请求在 `Authorization: Bearer ` 携带;`JwtAuthFilter` 校验签名 + 从 Redis(`auth:user:`)取出登录态,写入线程上下文 `UserContextUtils`。 - 白名单(`JwtAuthFilter.WHITE_LIST`,免鉴权):`/api/auth/login`、`/api/health`、`/`、`/index.html`、`/css/**`、`/js/**`、`/assets/**`、`/swagger-ui/**`、`/v3/api-docs/**`、`/doc.html`、`/error`。 - 缺失 / 非法 token → 401(`success:false, code:401, message`)。 - 登录态失效(Redis 无记录)→ 401。 - `AuthService.getCurrentUser()` 从线程上下文取用户,缺失即抛 `MyCustomException(StatusCodeEnum.UNAUTHORIZED, "未登录或登录已失效")`。 - **状态码一律走 `StatusCodeEnum`**(见 §14.2):业务代码不再写裸整数;`MyCustomException(StatusCodeEnum.X, msg)` 与 `ResultVO.error(StatusCodeEnum.X, msg)` 取代老的 `new MyCustomException(int, String)` / `ResultVO.error(int, String)`。 --- ## 7. 统一响应结构 所有接口返回 `ResultVO`(HTTP 200 的成功包裹): ```json { "success": true, "code": 200, "message": "操作成功", "data": { ... } } ``` - `success`:业务是否成功;`code`:业务状态码(成功 200);`message`:提示;`data`:业务主体。 - 错误统一由 `RestControllerAdvice`(`ControllerAdviceHandler`)处理,并按异常类型设置 HTTP 状态码: - `MyCustomException(StatusCodeEnum.X, msg)` → `ResponseEntity.status(enum.getCode())`(业务码即 HTTP 码,如 `FORBIDDEN` / `SYSTEM_ERROR`); - `@Valid` 校验失败(`MethodArgumentNotValidException`)→ 400(`PARAM_ERROR`); - 资源未找到(`NoResourceFoundException` 等)→ 404(`NOT_FOUND`); - 其余 `Exception` → 500(`SYSTEM_ERROR`)。 - 状态码全部收敛到 `StatusCodeEnum`(见 §14.2),业务代码禁止再写裸整数魔法值。 - 登录失败建议返回 `HTTP 200 + {success:false, code:500, message:"用户名密码错误"}`(业务失败),前端登录页内联展示。 --- ## 8. 接口清单(17 个,对齐契约) | 模块 | 方法 | 路径 | 鉴权 | 说明 | |------|------|------|------|------| | 认证 | POST | `/api/auth/login` | 否 | 登录获取 Token | | 认证 | GET | `/api/auth/me` | 是 | 获取当前用户信息 | | 认证 | GET | `/api/health` | 否 | 健康检查(探测 Milvus / Redis) | | 认证 | POST | `/api/auth/session` | 是 | 创建会话,返回 `conversationId` | | 历史 | GET | `/api/history/user` | 是 | 当前用户全部会话(`{histories:[...]}`) | | 历史 | GET | `/api/history/{id}/session` | 是 | 单会话详情(`{history:{...}}`) | | 历史 | DELETE | `/api/history/{id}/delete` | 是 | 删除会话 | | 历史 | DELETE | `/api/history/user/delete` | 是 | 清空全部会话 | | 历史 | PUT | `/api/history/session/{id}/title` | 是 | 重命名会话标题 | | 历史 | GET | `/api/history/stats` | 是 | 使用统计(`{stats:{...}}`) | | 问答 | POST | `/api/knowledge/query` | 是 | 同步问答 | | 问答 | POST | `/api/knowledge/stream` | 是 | 流式问答(SSE) | | 文档 | GET | `/api/documents/list` | 是 | 文档列表(数组) | | 文档 | POST | `/api/documents/upload` | 是(admin) | 上传文档 | | 文档 | PUT | `/api/documents/{id}/update` | 是(admin) | 更新文档版本 | | 文档 | DELETE | `/api/documents/{id}/delete` | 是(admin) | 软删除文档 | | 文档 | GET | `/api/documents/{id}/versions` | 是(admin) | 版本历史 | | 部门 | GET | `/api/departments/list` | 是 | 部门列表(`[{id,name}]`) | --- ## 9. 各模块实现说明 ### 9.1 认证模块(`AuthController` / `AuthServiceImpl` / `AuthDao`) - 登录:委托 `AuthDao.login` 生成 JWT(`JwtTokenUtils.generateToken(userId)`)并写 Redis(`auth:user:`)。 - `/me`:从上下文取 `UserInfoDTO`,经 `BeanCopyUtil` 转为 `UserInfoVO` 返回(字段:id / username / realName / role / tenantId / tenantName / departmentId / departmentName)。 - `/session`:调用 `SessionUtils.getSessionId()` 生成 `conversationId`(格式 `conv__`),直接返回。 ### 9.2 会话历史模块(`HistoryController` / `HistoryServiceImpl` / `HistoryDao`) - 存储:Redis(key 前缀 `chat:history`,默认 24h 过期)。 - `getUserHistories`:返回 `{userId, histories:[...], count}`;`getSessionHistory`:返回 `{userId, conversationId, history:{...}}`。 - 消息 `role` 仅 `user` / `assistant`;`metadata` 携带 `sources` / `pipeline` / `status`。 - `getStats`:返回 `totalConversations` / `totalMessages` / `todayConversations`(可选 `mode` 过滤)。 - 问答成功后会自动 `appendMessage`(user + assistant)。 - **重新生成(覆盖旧回答)**:`ChatDTO.regenerate=true` 时调用 `replaceAnswerForQuestion(user, mode, question, vo)`——找到与当前问题匹配的那条 user 消息,**原地覆盖**其后的 assistant 回答(content + sources/status/pipeline 元数据),**不追加**新的 user 提问,避免历史重复膨胀。前端「重新生成」按钮即走此路径(普通提问 `regenerate` 默认 `false`,向后兼容)。 ### 9.3 知识库问答模块(`KnowledgeController` / `KnowledgeServiceImpl`) - **检索(hybridSearch)**:Milvus 原生 `hybridSearch` + `RRFRanker` 在服务端一次性完成两路 ANN 与 RRF 融合(**不**手动融合): - 稠密(`dense_vector`,AUTOINDEX + COSINE):传 `FloatVec`(应用层 ONNX embedding 生成)。 - 稀疏(`sparse_vector`,`SPARSE_INVERTED_INDEX + BM25`):**Milvus 原生 BM25 Function 自动从 `content` 生成稀疏向量**,检索时传**查询文本 `EmbeddedText(query)`**(**不**传 `SparseFloatVec`);`content` 启用 jieba analyzer + text match。 - 权限过滤表达式挂在**每路 `AnnSearchReq.filter`**(`HybridSearchReq` 本身无 filter 字段),召回前过滤。 - **重排(rerank)**:`VectorStoreServiceImpl.rerank` 调用 **`RerankerConfig` → 阿里云 rerank API**(cross-encoder 相关性打分,低于 `aliyun.rerank.threshold` 丢弃);API 异常/空结果时 **`fallback` 保序**兜底,不阻断问答。旧的 `rag.chat.rerank-*-weight` 加权公式是**死配置**。 - **同步 `/query`**:检索 → 重排 → 构建上下文 + 历史 → `ChatClient.call()` → 组装 `QueryKnowledgeVO`(`status` / `answer` / `sources` / `pipeline`)→ 持久化历史(含重新生成覆盖逻辑,见 §9.2)。 - **流式 `/stream`(SSE)**:返回 `SseEmitter`,事件序列严格遵循契约: `retrieving → sources → chunk* → done`(异常时 `error`)。 - 服务层 `streamKnowledge` 产出**纯 JSON 字符串**事件体;Controller 用 `SseEmitter.event().data(chunk)` 以文本写入,由 Spring 统一包裹为 `data:\n\n`。 - 检索失败 / 生成失败 → 直接推送 `error` 事件,不进入问答流。 - **多租户权限**:检索 filter 由 `MilvusFilterBuilder.buildPermissionFilter(user)` 生成(如 `tenant_id == "qiteng" and is_active == true`),在召回前过滤。 ### 9.4 文档模块(`DocumentController` / `DocumentServiceImpl`) - 上传:校验 → 本地落盘(`./storage/documents///v1_`)→ 读取文本(md/txt/pdf/docx/doc)→ 切片(`splitText`,按 `chunk-size`/`chunk-overlap`)→ 向量化入库(`VectorStoreService.addDocuments`)→ 写 Redis 元数据 + 版本记录。 - 更新(发版):新版本入库后,将上一版本从 Milvus 检索范围移除(`softDeleteDocuments`,保留 Redis 版本历史供审计),版本号 +1。 - 软删除:从 Milvus 移除分片 + Redis 元数据标记 `isActive=false`(不物理删除,版本历史可查)。 - 列表:仅返回 `isActive=true` 文档;非 admin 仅可见 `company` 或本部门 `department`;按 `updatedAt` 倒序。 - 写接口(upload / update / delete / versions)限定 `role == "admin"`(`requireAdmin()`)。 ### 9.5 部门 / 健康模块 - `/departments/list`:返回 `[{id, name}]`(camelCase,对齐前端 `name` 消费)。 - `/health`:探测 Milvus `hasCollection` 与 Redis `ping`,返回 `{status, milvus:{...}, redis:{...}}`(`UP` / `DEGRADED`)。 --- ## 10. 关键设计决策与已修复缺陷 | 编号 | 问题 | 修复 | 影响 | |------|------|------|------| | D1 | 返回 `Flux` 原始 JSON 给 `text/event-stream` 时,Spring 把每个 String 当 JSON 字符串二次加引号,客户端收到 `data:"{...}"` 而非 `data:{...}` | `KnowledgeController` 改为返回 `SseEmitter`,用 `SseEmitter.event().data(chunk)` 写原始文本 | 流式问答契约正确(真实生产 Bug) | | D2 | `@ExceptionHandler` 返回 `ResultVO` 但默认 HTTP 200,导致 `@Valid` 失败返回 200、方法不支持等被吞为 200 | `ControllerAdviceHandler` 为各处理器加 `@ResponseStatus` / 改用 `ResponseEntity` 保留业务码为 HTTP 码 | 错误状态码正确(400/404/500/403) | | D3 | `documents/update` 是 `@PutMapping`,但测试以 POST 发送导致方法不支持 | 测试改用 PUT(`request.setMethod("PUT")`),并配合 D2 修复 | 接口契约对齐 | | D4 | Lombok 编译「全局失效」为假象(首次 `clean` 重建时文件系统保护 shim 干扰),非配置问题 | 确认 Lombok 1.18.32 路径正确,`clean compile` 稳定通过 | 无需代码改动 | | D5 | 稀疏检索链路历经两次调整:① 早期声明 Milvus 原生 **BM25 Function**(`content`→`sparse_vector`,稀疏索引 `BM25`),但应用层仍用 jieba+IDF 生成的 `SparseFloatVec` 去检索 `sparse_vector`,二者冲突报 `please provide varchar/text for BM25 Function based search, got SparseFloatVector`(混合检索致命缺陷);② 中间态修复删 BM25 Function、改纯 VarChar + `IP` 稀疏索引(即本文档 2026-08-12 版描述的形态);③ **当前最终设计(2026-08-15)恢复并正确使用 BM25 Function**——`content` 启用 jieba analyzer + text match,`sparse_vector` 由服务端自动生成,检索 `sparse_vector` 改传**查询文本 `EmbeddedText(query)`**(不传 `SparseFloatVec`),冲突根因消除。 | 当前:保留 BM25 Function(`content` analyzer/match + 检索用 `EmbeddedText`);部署集合残留标签用 `POST /api/admin/collection/reset` 按当前 Schema 重建清除;旧 `SparseVectorUtil`(jieba+IDF) 方案废弃(代码保留但不在检索主链路调用)。 | 混合检索致命缺陷彻底修复,RAG 问答(同步+流式)端到端打通(真实联调验证,见 §13) | | D6 | `/api/admin/collection/reset` 原先依赖「`resetCollection()`(仅 drop)+ 单独 `warmup()`(再建)」两步,存在只 drop 不重建的中间态风险;集合创建逻辑分散在 `ensureCollection` 内 | 抽取 `MilvusSchemaDaoImpl.buildCollection(denseDim)` 为「创建+建索引+加载」唯一入口;`VectorStoreServiceImpl.resetCollection()` 改为原子操作(drop 后立即 `ensureReady()` 重建并加载);`AdminController.resetCollection` 去掉冗余 `warmup()` | reset 一步完成删旧建新并立即可用(`POST /reset`→200「集合已重置并重建」,health 仍 UP 验证通过) | --- ## 11. 运行方式 1. 准备外部依赖:Redis(localhost:6379)、Milvus(Zilliz serverless,uri+token)、可用的 LLM 兼容端点。 2. 将本地稠密向量模型放到 `models/dense/`(`model.onnx` + `tokenizer.json`)。 3. 配置 `application-dev.yml`(或环境变量)中的 redis / milvus / ai / jwt 等。 4. 构建并启动(**必须带 `-Dfile.encoding=UTF-8`**,否则 BM25 中文检索失败;固定 8081 端口): ```bash export PATH="$PATH:/d/Java/apache-maven-3.6.3/bin" mvn.cmd -B clean package -DskipTests java -Dfile.encoding=UTF-8 -Dserver.port=8081 -jar target/rag-search-app-1.0.0.jar ``` 5. 接口文档:启动后访问 `http://127.0.0.1:8081/swagger-ui/index.html`。 > 说明:本仓库的「API 接口测试」以 **MockMvc 契约测试 + 单元测试** 方式覆盖(下游 Milvus/Redis/LLM 全部 Mock),无需真实外部依赖即可验证 17 个接口的路径、响应包裹、字段命名、参数校验与 SSE 格式;真实环境下的端到端联调已在本轮完成(见 §13),由前端联调脚本串联真实 Milvus(Zilliz)+Redis+LLM 验证。 --- ## 12. 数据模型速查 | 模型 | 关键字段 | |------|----------| | User / UserInfoDTO | id, username, email, realName, role, tenantId, tenantName, departmentId, departmentName | | HistoryModel | conversationId, userId, tenantId, tenantName, title, mode("knowledge"), messages[], createdAt, updatedAt, metadata | | Message | role("user"\|"assistant"), content, timestamp, metadata{sources, pipeline, status} | | DocumentVO | documentId, version, title, visibility(company/department), departmentId, departmentName, checksum, sourcePath, chunkCount, updatedAt, isActive, tenantId | | QueryKnowledgeVO | status, answer, conversationId, sources[SourceChunkVO], pipeline[PipelineVO{candidates[]}] | | SourceChunkVO | chunkId, documentId, title, version, chunkIndex, sourcePath, content | | DepartmentVO | id, name | | Stats | totalConversations, totalMessages, todayConversations | --- ## 13. 前端联调测试(真实端到端验证) 以 `web/rag-knowledge-vue`(Vue 3 + Element Plus)为基准,进行**前后端联调测试**,严格遵循前端的 API 请求 / 响应数据结构规范;对后端潜在 bug 进行修复与改进。 ### 13.1 前端契约基准 | 维度 | 前端约定(后端须对齐) | |------|------------------------| | baseURL | `/api`(Vite 代理 `target=http://127.0.0.1:8081`,与后端 `/api` 前缀对齐) | | 响应判定 | axios 拦截器判定 `code===200 \|\| success===true` 为成功,返回 `response.data`(即统一包裹体) | | 401 处理 | 清 `localStorage` 的 `rag_token` / `rag_user`,跳 `/login` | | 统一包裹 | `{success, code, message, data}`,与 `ResultVO` 完全一致 | | SSE 解析 | `data: `;按 `type` 分支(`retrieving/sources/chunk/done/error/warning`);依赖 `data.sources` / `data.pipeline` / `data.answerStatus` / `data.answer` | | multipart 键 | 文档上传/更新:`file` / `title` / `departmentId` / `visibility`,与 `@RequestParam` 一致 | ### 13.2 17 接口契约核对结论 逐一比前端 `src/api/*.js`、`stores`、`components` 与后端 Controller / VO / DTO:**路径、HTTP 方法、请求体字段、multipart 键、响应包裹、VO 字段名(camelCase)、SSE 事件结构 全部一致,无字段命名/结构类 bug。** ### 13.3 联调暴露并修复的缺陷 - **D5(检索链路调整)**:见 §10。根因为 BM25 Function 与「应用层 SparseFloatVec 检索」混用冲突;当前已改为**正确使用** BM25 Function(`content` 启用 analyzer/match + 检索传 `EmbeddedText`),RAG 问答(同步+流式)端到端验证通过。 ### 13.4 端到端验证链路(真实环境:Milvus Zilliz + Redis + LLM) 测试脚本串联调用,等价模拟前端请求序列(种子账号 `zhangsan/123456`,admin,tenant `qiteng`): 1. 登录(admin)→ 取得 JWT(`token.len=143`)。 2. `POST /api/admin/collection/reset` → `200`,集合按新 schema 重建。 3. `GET /api/health` → `UP`(Milvus 集合存在、Redis PONG)。 4. `POST /api/documents/upload`(multipart,年假政策 md,`departmentId=platform`,`visibility=company`)→ `200`,`chunkCount=51`,`documentId=eaf91eed-…`。 5. `POST /api/auth/session` → `conversationId=b40268c89d…`。 6. `POST /api/knowledge/query` → **`200`**,`status=answered`,`answer` 正确给出「正式员工每满一年享有 15 天带薪年假…」,`sources`(4 个 chunk) / `pipeline`(permissionFilter=`tenant_id == "qiteng" and is_active == true`, recalledCount=4) 齐备。 7. `POST /api/knowledge/stream`(SSE)→ **72 行**,事件序列 `retrieving → sources → chunk* → done`,`grep -c '"type":"error"'` = **0**。 **结论**:17 接口契约全部匹配;D5 修复后 RAG 问答(同步 + 流式)端到端打通,可交由前端直接联调。 ### 13.5 联调运维接口(不在前端 17 契约内) `AdminController`(`@RequestMapping("/api/admin")`)仅 admin 可用: - `POST /api/admin/collection/reset`:删旧集合并按当前 schema 重建(用于 Schema 变更后强制刷新)。 - 鉴权:非 admin 返回 `403`(`MyCustomException(StatusCodeEnum.FORBIDDEN, "仅管理员可执行该操作")`)。 --- ## 14. 统一基础设施(日志 / 状态码枚举) ### 14.1 统一方法级日志(AOP,无需逐方法手写) 业务方法**不再需要**手写进入/退出日志。`LoggingAspect`(`aspect/`)以 `@Around` 自动为匹配方法记录「进入(参数,敏感字段脱敏)/ 完成(耗时 ms)/ 异常(类型+消息,原样抛出)」。 - **覆盖范围**:① 标注 `@Loggable` 的方法/类(始终生效,不受全局开关影响);② 全局模式开启时(`app.logging.global=true`,默认开)所有 `controller` / `service` 层公共方法自动记录。 - **注解 `com.weh.rag.annotation.Loggable`**:`value`(动作名) / `enabled`(可单方法关闭) / `logArgs` / `logResult` / `level`(枚举 `LogLevel`)。标注在类上对该类所有公共方法生效。 - **敏感字段自动脱敏**:入参 JSON 序列化后含 `password / passwd / pwd / token / secret / apikey / api_key / authorization / credentials / cookie / accesskey / access_key` 的,值一律替换为 `***`。 - **配置**(`application.yml` 的 `app.logging`):`global`(默认 true) / `log-args`(默认 true) / `log-result`(默认 false,避免大对象/流式结果刷屏)。 - 示例:`▶ 进入 [VectorStoreServiceImpl.warmup]` / `◀ 完成 [MilvusSchemaServiceImpl.ensureCollection], 耗时=347ms`。 ### 14.2 统一状态码枚举 `StatusCodeEnum` 所有 HTTP / 业务状态码收敛到 `model/enums/StatusCodeEnum`,**禁止在业务代码写裸整数魔法值**: | 枚举 | code | 含义 | |------|------|------| | `SUCCESS` | 200 | 成功 | | `PARAM_ERROR` | 400 | 请求参数错误 | | `UNAUTHORIZED` | 401 | 未授权(未登录 / token 失效) | | `FORBIDDEN` | 403 | 禁止访问(无权限) | | `NOT_FOUND` | 404 | 资源不存在 | | `PAYLOAD_TOO_LARGE` | 413 | 请求体过大(文件/内容超上限) | | `SYSTEM_ERROR` | 500 | 系统异常 | | `SERVICE_UNAVAILABLE` | 503 | 服务不可用(检索 / 大模型异常) | - **用法**:抛异常用 `new MyCustomException(StatusCodeEnum.X, msg)`;返回错误用 `ResultVO.error(StatusCodeEnum.X, msg)`。`ResultVO` 另保留 `error(int,...)` 重载仅供 `ControllerAdviceHandler` 透传异常码。 - `MyCustomException(String)` 单参构造默认归 `SYSTEM_ERROR`(500)。 - **注意边界**:`KnowledgeServiceImpl` 里 `QueryKnowledgeVO.status("answered" / "insufficient_evidence")` 是**业务状态字符串,不是 HTTP 码**,切勿混淆改成枚举。 - `ControllerAdviceHandler` 的 `@ResponseStatus(HttpStatus.X)` 是 Spring 注解类型所限、保留不动,其值与本枚举一一对应。