Files
prop-ai-hr/docs/superpowers/plans/2026-07-31-mobile-citation-source-viewer.md

703 lines
30 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 手机端问答引用详情与原始媒体定位 Implementation Plan
> 制定日期:2026-07-31
>
> 当前状态(2026-08-02):阶段 0 基线冻结已完成;阶段 1—5 已实现部分能力,本机 H5 已验证文本、图片、视频引用详情及视频 Range 实播,但完整越权矩阵、App-Plus/Android 真机和性能门禁未闭合;阶段 6 未开始
>
> 适用范围:`mobile-uni` 员工端“问 · 数字师傅”、`/api/aihr/agent/**`、知识检索与受控原始资料接口
**Goal:** 让员工在手机端点击回答引用后,按当前身份重新鉴权并打开可信来源详情:文本进入全文页并高亮命中位置,图片打开原图,视频进入受控播放器并定位到引用时间或已保存关键帧;来源撤回、过期或失权时失败关闭。
**Architecture:** 保留 `aihr_knowledge_fragment` 为检索正文事实源,新增一对一的片段来源定位表保存附件、媒体类型和页码/段落/时间戳等结构化位置。回答只返回不透明 `detailRef` 和最小展示信息;点击后由服务端重新计算“租户 + 调用应用绑定 + 当前主体授权”,再返回受控文本、图片或视频播放信息。原始 OSS 地址、登录令牌和完整提取正文不进入回答响应。
**Tech Stack:** Java 17、Spring Boot、Sa-Token、JdbcTemplate、MySQL 8、MinIO/sys_oss、ffmpeg/ffprobe、Tika/现有文档解析器、uni-app Vue 3 + TypeScript、Node test、JUnit 5/Mockito、Android App-Plus。
---
## 0. 完成定义与证据纪律
### 0.1 阶段状态
每个阶段只能使用以下状态:
- `未开始`:尚未写代码或迁移。
- `实现中`:允许局部测试通过,但尚未形成该阶段的完整证据。
- `技术通过`:本地自动化和真实运行链路均通过,但尚未部署生产。
- `生产通过`:所需发布单元已部署,正式账号或真机完成该阶段规定的验证。
- `阻断`:失败项、复现步骤和责任边界已记录;不得用后续阶段结果覆盖。
“源码存在”“单元测试通过”“H5 能加载”“视频能播放”都不能单独视为阶段完成。
### 0.2 每阶段必须保存的实际结果
每个阶段结束时必须填写本计划中的“实际结果记录”,至少包含:
| 字段 | 要求 |
|---|---|
| 构建源 | 完整 Git commit,不使用模糊分支名 |
| 环境 | 本地/测试/生产、后端与 schema 版本、APK 版本 |
| 命令或操作 | 可重复执行的命令或真机点击路径 |
| 预期 | 明确数值、HTTP 状态、页面状态或时间误差 |
| 实际 | 真实通过数、失败数、耗时或错误码,不写“基本正常” |
| 证据 | 脱敏日志、JUnit/Node 输出、HTTP 头、截图或真机录像路径 |
| 遗留项 | 未覆盖的文件类型、机型、角色、数据或性能边界 |
执行证据统一写入:
```text
output/citation-source-viewer/<run-id>/
```
仓库文档只保存脱敏摘要和证据相对路径,不保存手机号、验证码、令牌、短期下载票据、原始 OSS 地址或受保护正文。
### 0.3 固定验收样本
本地工程样本使用可公开的合成内容,至少包含:
1. 一份 TXT/Markdown,带唯一标记和已知行号;
2. 一份三页 PDF,目标引用位于第 2 页;
3. 一份 DOCX,目标引用位于指定段落;
4. 一份 PPTX,目标引用位于指定页;
5. 一张带唯一视觉文字的图片;
6. 一段不超过两分钟的视频:
- 已知语句位于固定时间范围;
- 已知画面位于固定时间点;
- 含音轨和画面两种可命中依据。
生产验收只选已批准、当前员工有权访问的正式资料;测试问题和答案正文不进入仓库。
---
## 1. 范围与非目标
### 1.1 本计划交付
- 回答中的 `DOCUMENT` 引用卡片可点击;
- 文本全文页展示同一受控附件的解析正文,并定位、高亮命中片段;
- PDF/PPT/DOCX 在解析能力允许时展示页码、幻灯片号或段落位置;
- 图片引用打开当前员工仍有权访问的原图;
- 视频引用打开独立播放器,定位到语句时间范围或关键帧时间;
- 大视频使用支持 HTTP Range 的受控播放链路;
- 点击时重新鉴权,撤权、跨租户、来源失效和过期票据均失败关闭;
- 新资料在解析时产生定位元数据,历史资料有可审计的回填与降级策略;
- H5、App-Plus、Android 真机分别形成实际验证结果。
### 1.2 本计划不交付
- 不把完整原文直接塞进 Agent 回答或会话审计;
- 不暴露 MinIO/OSS 原始地址或永久公开链接;
- 不允许客户端提交 `attachmentId`、租户、岗位或空间代码来扩大权限;
- 不把 AI 推断的位置当作原文件页码或视频真实时间点;
- 不承诺所有旧资料自动获得精确页码/帧;无法可靠回填时降级为“打开全文/原文件”;
- 不在本计划重做 RAG、向量模型或 Agent 意图规划;
- 不把 DCloud 测试签名 APK 验证写成企业签名或应用商店验收。
---
## 2. 实施总览
| 阶段 | 交付物 | 完成门禁 | 当前状态 |
|---|---|---|---|
| 0. 基线冻结 | 当前引用、资源、视频解析和权限链路事实 | 源码位置与缺口逐项复核 | 已完成 |
| 1. 定位数据契约 | schema、结构化解析结果、写入和替换一致性 | 迁移幂等;六类样本定位记录正确 | 实现中 |
| 2. 受控详情与媒体接口 | `detailRef`、详情解析、全文、图片和 Range 视频 | 正授权成功;跨租户/撤权/伪造全部拒绝 | 实现中 |
| 3. 手机端引用详情 | 可点击引用、全文高亮、图片预览、视频定位 | 390×844 主路径实际点击;H5 与 App 测试通过 | 实现中 |
| 4. 历史资料回填 | 安全回填、失败降级、进度报告 | 不改正式来源审批;成功/失败/降级数量可核对 | 实现中 |
| 5. 集成、安全与性能 | 全链路矩阵、并发/Range/资源释放 | 零越权;定位误差和性能达到冻结阈值 | 实现中 |
| 6. 发布与真机收口 | schema、后端、H5/Android 发布与生产验证 | 独立授权发布;正式账号和签名 APK 真机证据 | 未开始 |
依赖顺序:
```text
阶段 0
→ 阶段 1 数据契约
→ 阶段 2 受控接口
→ 阶段 3 手机端交互
→ 阶段 4 历史回填
→ 阶段 5 全链路门禁
→ 阶段 6 发布与真机
```
阶段 3 可以在阶段 2 的固定测试响应上并行开发,但不能在阶段 2 的权限和 Range 接口完成前标记技术通过。
---
## 3. 阶段 0:当前基线冻结
**目标:** 冻结真实缺口,防止实施时把“引用已展示”“资源卡能打开”误认为引用跳转已经存在。
### 已核实事实
| 核验项 | 实际结果 | 结论 |
|---|---|---|
| 工作区 | `main...origin/main`,核验时无未提交文件 | 可建立独立实施计划 |
| 回答引用 | `snippets` 可展开,显示标题、摘要、空间和 `fragmentId`;引用卡没有点击事件 | 需要新增点击详情 |
| 资源卡 | `resources` 中 FILE 可下载打开、VIDEO 可加载内联播放器 | 可复用受控读取能力,但未与每条引用绑定 |
| 引用契约 | `Citation` 无 `attachmentId/mediaType/locator/detailRef` | 后端需要新增最小详情引用 |
| 视频定位 | ASR 每 5 分钟片段写入一个 `[mm:ss]`;关键帧间隔至少 30 秒且最多抽取 40 帧,并写入 `[画面 mm:ss]` | 只能得到粗定位文本 |
| 关键帧文件 | OCR 完成后临时 JPG 被删除 | 无法直接展示历史关键帧图 |
| 片段 schema | 只有 `idx/doc_id/content/embedding`,没有来源定位字段 | 需新增旁路定位表 |
| 原文件权限 | `/api/knowledge/resources/{attachmentId}/content` 每次重新鉴权 | 继续复用,不改成永久 URL |
### 阶段 0 实际验证结果
- 状态:`通过`
- 日期:`2026-07-31`
- 方法:源码只读审计、`rg` 检索、工作区状态核验
- 结果:8 项事实全部定位到当前源码或 SQL;没有执行数据库写入、部署或生产操作
- 未覆盖:当前生产附件的真实 MIME 分布、OSS Range 行为、历史视频定位可回填比例
---
## 4. 阶段 1:片段来源定位数据契约
**目标:** 让每个可点击引用都能确定性关联到受控附件及其真实位置,不依赖前端解析片段文字。
### Files
- Create: `backend/script/sql/update/aihr_20260731_knowledge_fragment_locator_mysql8.sql`
- Modify: `backend/script/sql/aihr_knowledge_mysql8.sql`
- Create: `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/knowledge/domain/AihrKnowledgeSourceLocator.java`
- Modify: `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrSopSeedService.java`
- Modify: `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/service/AihrVideoService.java`
- Modify: relevant document parser implementations
- Test: `backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/knowledge/AihrKnowledgeSourceLocatorSchemaTest.java`
- Test: `backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/knowledge/AihrKnowledgeSourceLocatorIngestionTest.java`
### Task 1.1:建立旁路定位表
新增 `aihr_knowledge_fragment_locator`,最小字段:
```sql
tenant_id
fragment_id
knowledge_id
doc_id
attachment_id
source_kind
page_number
slide_number
paragraph_start
paragraph_end
sheet_name
row_start
row_end
start_ms
end_ms
frame_ms
locator_version
create_time
update_time
```
约束:
- `UNIQUE(tenant_id, fragment_id)`;
- 查询索引覆盖 `(tenant_id, attachment_id, source_kind)`;
- `source_kind` 只允许 `TEXT/PDF/DOCX/PPTX/XLSX/IMAGE/VIDEO`;
- 时间、页码和段落不能为负数;
- 定位表缺失或无定位行时,查询仍可返回普通引用,不影响现有问答;
- 生产请求不得自动执行 DDL。
### Task 1.2:解析结果从纯字符串升级为结构化段
内部解析结果统一为:
```java
record LocatedSegment(
String text,
SourceKind sourceKind,
Integer pageNumber,
Integer slideNumber,
Integer paragraphStart,
Integer paragraphEnd,
String sheetName,
Integer rowStart,
Integer rowEnd,
Long startMs,
Long endMs,
Long frameMs
) {}
```
最低定位能力:
- TXT/Markdown:行或段落范围;
- PDF:页码;
- DOCX:段落范围;
- PPTX:幻灯片号;
- XLSX:工作表与行范围;
- IMAGE:关联原图,首版不要求 OCR 框坐标;
- VIDEO ASR:语句或分段开始/结束毫秒;
- VIDEO 画面:抽帧的真实毫秒位置。
### Task 1.3:切片、替换和重试一致性
- 片段与定位行在同一受控处理流程写入;
- 同名文档替换时同步清理旧定位行;
- 异步重试不得产生孤立定位行;
- 零片段媒体仍保持失败,不创建虚假定位;
- Qdrant payload 不存原始地址;如需要,可只增加 `fragmentId/sourceKind`。
### 阶段 1 验证
运行:
```powershell
mvn -f backend/pom.xml -pl ruoyi-modules/ruoyi-aihr -am `
-DskipTests=false -Dsurefire.failIfNoSpecifiedTests=false `
-Dtest=AihrKnowledgeSourceLocatorSchemaTest,AihrKnowledgeSourceLocatorIngestionTest test
```
在临时数据库上执行正式迁移两次,并导入六类固定样本。必须实际证明:
1. 迁移连续执行两次均成功;
2. 六类样本均有 `fragment + locator`;
3. PDF 第 2 页、PPT 指定页、图片附件、视频语句和画面时间均与样本基准一致;
4. 文档替换后旧 locator 为 0;
5. 解析失败后 locator 为 0;
6. 未执行运行时 DDL。
### 阶段 1 实际结果记录
| 字段 | 实际值 |
|---|---|
| 状态 | 实现中 |
| 构建源 | 阶段 1 实现已进入当前分支;2026-08-02 本轮未新增该阶段代码 |
| JUnit | 未运行:当前 PowerShell 未找到 `java`/`mvn` |
| 迁移幂等 | 本地 locator 迁移与回填已执行;未完成两次正式迁移的 JUnit 证明 |
| 六类样本定位 | 当前本地 seed 仅有 5 个文本附件、15 个 TEXT locator;PDF/DOCX/PPTX/XLSX/IMAGE/VIDEO 固定样本未完成 |
| 替换/失败清理 | 代码路径已实现,未完成后端自动化复跑 |
| 证据目录 | `output/citation-source-viewer/20260731-local/` |
| 遗留项 | 缺少 Java/Maven 运行时及六类公开合成附件样本 |
---
## 5. 阶段 2:受控引用详情、全文、图片与视频接口
**目标:** 客户端只持有不透明引用,服务端在点击时重新鉴权并返回最小必要内容。
### Files
- Modify: `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/knowledge/domain/AihrKnowledgeQueryDto.java`
- Create: `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/knowledge/service/AihrKnowledgeCitationDetailService.java`
- Create: `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/knowledge/controller/AihrKnowledgeCitationController.java`
- Modify: `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/knowledge/service/AihrKnowledgeResourceDownloadService.java`
- Test: `backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/knowledge/AihrKnowledgeCitationDetailServiceTest.java`
- Test: `backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/knowledge/AihrKnowledgeCitationControllerTest.java`
- Test: `backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/knowledge/AihrKnowledgeResourceRangeTest.java`
### Task 2.1:扩展回答引用
兼容新增字段:
```json
{
"fragmentId": 12345,
"title": "投诉处理培训",
"snippet": "……",
"mediaType": "VIDEO",
"detailRef": "opaque-short-lived-reference",
"locatorSummary": {
"pageNumber": null,
"startMs": 208000,
"endMs": 223000,
"frameMs": null
}
}
```
规则:
- 旧客户端忽略新增字段仍可工作;
- `detailRef` 不编码明文租户、附件、用户或 OSS 地址;
- 正式政策引用仍必须来自有效的 `FORMAL_POLICY + APPROVED` 来源;
- `DATA_TOOL`、个人记录和项目记录分别走现有领域详情,不伪装成企业文档。
### Task 2.2:详情解析接口
新增:
```text
GET /api/knowledge/citations/{detailRef}
```
返回三类详情:
- `TEXT`:标题、位置、当前片段、分页全文读取入口、原文件入口;
- `IMAGE`:标题、短期原图读取入口;
- `VIDEO`:标题、`startMs/endMs/frameMs`、短期流媒体入口。
点击时必须重新验证:
1. 当前 APP 登录身份;
2. 当前租户;
3. 调用应用与知识空间绑定;
4. 当前主体授权;
5. 附件仍为 `READY`、知识空间仍为 `ACTIVE`;
6. 正式来源仍有效且版本、哈希未漂移;
7. `fragmentId/docId/attachmentId` 仍属于同一来源。
### Task 2.3:全文和媒体读取
- 全文按页或游标读取,单次响应有硬上限;
- HTML 展示只生成受控纯文本结构,不渲染原文脚本、宏或外链;
- 图片继续通过当前授权资源链路读取;
- 视频流接口支持 `Range`,合法请求返回 `206`、`Content-Range` 和正确 MIME;
- 票据短期有效、单附件绑定、不可跨资源复用;
- 撤权后已签发但未使用的票据也必须拒绝或在最短有效期内失效。
### 阶段 2 验证
必须覆盖:
| 用例 | 预期 |
|---|---|
| 有权员工打开文本、图片、视频 | `200`;视频 Range 为 `206` |
| 伪造 `detailRef` | `404/403`,不泄露资源是否存在 |
| 跨租户引用 | 拒绝 |
| 无空间授权 | 拒绝 |
| 来源回答后被停用或撤权 | 再次点击拒绝 |
| 票据过期或跨附件复用 | 拒绝 |
| 旧客户端请求 | 原问答响应仍兼容 |
| 日志扫描 | 无 token、票据、OSS 地址、全文 |
### 阶段 2 实际结果记录
| 字段 | 实际值 |
|---|---|
| 状态 | 实现中 |
| 构建源 | 2026-08-02 本轮修复提交见 Git 历史;该提交不代表生产发布 |
| JUnit | `AihrKnowledgeResourceRangeTest`:`2/2` 通过,覆盖首次无 Range 请求的 inline/MIME 通路和带 Range 请求转发 |
| 正授权 | 本机已用已发布的合成文本、图片、视频资产通过 APP 登录态检索并打开详情 `3/3`;未执行生产正式账号验收 |
| 越权矩阵 | 未完成;源码保留租户、应用、主体、来源治理复验,未形成后端运行证据 |
| Range | 本机合成 MP4 完整请求 `200 video/mp4` 且 inline;Range 请求 `206 video/mp4`,返回合法 `Content-Range` 和 1024 字节 |
| 日志敏感信息扫描 | 未完成后端运行日志扫描;未在移动端脱敏证据中写入手机号、令牌或 OSS 原址 |
| 证据目录 | `output/citation-source-viewer/20260731-local/` |
| 遗留项 | 生产正式账号、撤权/跨租户/伪造/过期票据反例、日志扫描和完整权限矩阵仍待验证 |
---
## 6. 阶段 3:手机端引用点击与详情页面
**目标:** 在 `390×844` 和 Android App-Plus 中完成真实可点击的文本、图片和视频引用路径。
### Files
- Modify: `mobile-uni/src/types/api.ts`
- Modify: `mobile-uni/src/services/knowledge.ts`
- Modify: `mobile-uni/src/pages/user/sop/index.vue`
- Create: `mobile-uni/src/pages/user/sop/citation-detail.vue`
- Modify: `mobile-uni/src/pages.json`
- Test: `mobile-uni/tests/knowledge-citation-detail.test.mjs`
- Test: `mobile-uni/tests/agent.test.mjs`
### Task 3.1:引用卡交互
- 每条可查看的 `DOCUMENT` 引用使用可访问按钮;
- 显示来源类型、页码或视频时间摘要和“查看出处”;
- 没有 `detailRef` 的旧引用保持只读,不制造假入口;
- 点击时不携带租户、角色、空间或原始地址。
### Task 3.2:统一详情页
页面状态:
- `loading`:正在重新验证来源;
- `text`:全文分段加载、目标片段高亮并自动滚动;
- `image`:受控图片加载,支持全屏缩放;
- `video`:受控播放器加载,在 `loadedmetadata/canplay` 后 seek;
- `forbidden/expired/not-found`:明确说明来源已失效或无权查看;
- `error/retry`:网络失败可重试,不把权限错误当网络错误。
### Task 3.3:视频定位
- 语音引用使用 `startMs`;
- 画面引用使用 `frameMs` 并默认暂停展示目标画面;
- 页面展示 `mm:ss`,不显示内部片段或附件编号;
- 切换引用、离开页面或重试时释放 Blob URL 和播放器资源;
- 若平台无法保证逐帧 seek,业务定位门槛为目标时间 `±500ms`;需要证明目标帧时同时展示服务端保存的对应关键帧预览。
### Task 3.4:图片与文本
- 图片使用当前认证上下文读取,不保存永久公开地址;
- 文本高亮只基于服务端目标片段,不在前端用模糊搜索猜位置;
- 超长全文使用分页/虚拟加载;
- 原文件打开继续使用短期下载票据。
### 阶段 3 验证
自动化:
```powershell
node --test mobile-uni/tests/knowledge-citation-detail.test.mjs mobile-uni/tests/agent.test.mjs
npm --prefix mobile-uni run typecheck
npm --prefix mobile-uni run build:h5
npm --prefix mobile-uni run build:app
```
真实页面:
1. `390×844` 打开“问”,提出固定问题;
2. 展开引用并点击;
3. 文本详情自动滚动、高亮且无需水平滚动;
4. 图片可打开、缩放和返回;
5. 视频进入详情页并定位;
6. 撤权/过期状态有明确提示;
7. 返回问答页后会话和展开状态可预测;
8. 控制台无错误,页面无无限加载。
### 阶段 3 实际结果记录
| 字段 | 实际值 |
|---|---|
| 状态 | 实现中 |
| 构建源 | 2026-08-02 本轮修复提交见 Git 历史;该提交不代表生产发布 |
| Node tests | `node --test mobile-uni/tests/*.test.mjs`:`217/217` 通过,包含 H5 媒体地址不得落入 `/h5/dev-api/` 的回归测试 |
| typecheck | `npm --prefix mobile-uni run typecheck`:通过 |
| H5/App build | `npm --prefix mobile-uni run build:h5`:通过;本轮未重跑 App-Plus 构建 |
| 390×844 文本/图片/视频 | 本机合成已发布资产 `3/3`:文本详情及命中高亮、图片原图及放大、视频详情均按对应形式呈现 |
| 视频实播 | 合成 MP4 `readyState=4`、时长约 `10.03s`、尺寸 `320×176`,播放时间由 `0` 推进到约 `2.36s` |
| 错误状态 | 详情页无引用参数正确显示不可查看状态;点击“返回问答”回到首页 |
| 遗留项 | App-Plus/Android 真机、正式资源和正式账号验收仍未完成;本机合成素材不得作为生产样本 |
本次浏览器复核(2026-07-31,指定在职测试账号切换后):通过内置 Browser 完成退出旧会话、重新获取验证码、登录和进入员工端“问”页面;固定制度问题返回真实回答并展开 `3` 条引用。当前 3 条引用均为旧知识片段,DOM 中 `.citation-action` 与“查看出处”入口均为 `0`,因此没有可安全构造的 `detailRef`,未伪造详情 URL。已保存脱敏页面证据:`output/citation-source-viewer/20260731-local/question-citations-expanded.png`。本次验证结论为“引用展示通过,出处详情被定位数据缺失阻断”,不计入文本/图片/视频 `3/3` 详情门禁。
---
## 7. 阶段 4:历史资料定位回填与安全降级
**目标:** 为现有正式资料补定位能力,但不重写正式来源审批、文档哈希或既有片段事实。
### Files
- Create: `scripts/backfill-knowledge-fragment-locators.mjs`
- Create: `scripts/verify-knowledge-fragment-locators.mjs`
- Create: `backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/knowledge/service/AihrKnowledgeLocatorBackfillService.java`
- Test: `backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/knowledge/AihrKnowledgeLocatorBackfillServiceTest.java`
- Modify: `docs/KNOWLEDGE_PLATFORM_RUNBOOK.md`
### Task 4.1:只读计划
回填必须先输出:
- 候选附件数;
- 已有 locator 数;
- 可按内容哈希确定性匹配数;
- 需要重新解析数;
- 无原文件、解析失败或歧义数;
- 正式来源数量及其审批/哈希状态。
默认只读,不接受浏览器传入目录,不自动删除或替换资料。
### Task 4.2:确定性回填
- 优先按 `tenant + knowledge + docId + fragmentId` 和规范化文本哈希匹配;
- 重解析只生成 locator,不改变原始文件 SHA-256、治理审批或知识空间授权;
- 匹配不唯一时标记 `AMBIGUOUS`,不得猜测页码或时间;
- 旧视频只有粗 `[mm:ss]` 时标记定位精度;
- 无法精确回填的资料仍可打开全文/原文件,页面显示“暂无精确位置”。
### Task 4.3:生产执行边界
- 本地和测试库先跑;
- 生产先只读 `plan`;
- 正式写入属于数据库变更,必须生成完整 `MIGRATE_DB:<hash>` 授权串并单独获得授权;
- 每批限制数量,可中断、可重跑;
- 回填失败不得删除已有 fragment、embedding、Qdrant point 或正式来源记录。
### 阶段 4 验证
必须报告真实分母:
```text
attachments_total
locators_before
locators_created
exact
coarse
ambiguous
failed
formal_sources_unchanged
fragments_unchanged
qdrant_points_unchanged
```
随机抽检至少:
- 5 份文本/PDF/PPT;
- 3 张图片;
- 3 段视频;
- 所有 `AMBIGUOUS/FAILED` 小样本或最多 20 项。
### 阶段 4 实际结果记录
| 字段 | 实际值 |
|---|---|
| 状态 | 实现中 |
| 构建源 | 阶段 4 实现已进入当前分支;2026-08-02 本轮未新增该阶段代码 |
| 计划分母 | `attachments_total=5`,`locators_before=15` |
| exact/coarse/ambiguous/failed | `exact=15`,`coarse=0`,`ambiguous=0`,`failed=0`;`locators_created=0` |
| 抽检 | 已核对本地 5 个 seed 附件及 15 条 locator;未覆盖计划要求的 PDF/PPT/图片/视频抽检样本 |
| 正式来源未变化 | `formal_sources_unchanged=0`(本地无治理来源记录,未写入正式来源) |
| fragment/Qdrant 未变化 | fragment 与 Qdrant 未变化(只回填 locator) |
| 证据目录 | `output/citation-source-viewer/20260731-local/` |
| 遗留项 | 需要公开合成六类样本及生产正式来源状态后再完成阶段门禁 |
---
## 8. 阶段 5:全链路、安全、性能与回归门禁
**目标:** 证明新功能不会扩大权限、破坏问答、拖垮大文件播放或泄漏受保护资料。
### 自动化矩阵
必须覆盖:
- `TEXT/PDF/DOCX/PPTX/XLSX/IMAGE/VIDEO`;
- 新旧引用响应兼容;
- 当前员工正授权;
- 跨员工、跨项目、跨空间、跨租户、停用应用;
- 来源撤回、版本替换、哈希漂移;
- 票据过期、重放和跨资源复用;
- Range 首段、中段、尾段和非法范围;
- 500MB 契约下不要求客户端先下载整个视频;
- 连续打开/关闭 20 次无 Blob URL 和播放器泄漏;
- Agent 普通问答、无依据、冲突和越权原有行为不变。
### 固定技术阈值
| 指标 | 门槛 |
|---|---:|
| 跨租户/越权泄漏 | `0` |
| 正式政策无引用详情入口 | `0`,旧无定位资料除外且必须明确降级 |
| 文本目标片段定位 | `100%` 固定样本 |
| 图片原图匹配 | `100%` 固定样本 |
| 视频业务定位误差 | `≤500ms`;精确帧以保存帧图核验 |
| Range | 合法范围 `206`,非法范围正确拒绝 |
| 详情元数据 API p95 | 本地基准 `≤1s` |
| 内网视频首帧 | 固定样本 `≤5s` |
| 内网二次 seek 完成 | 固定样本 `≤3s` |
| 原问答回归 | 既有专项测试全部通过 |
| 敏感信息扫描 | `0` 个 token/票据/OSS 原址/手机号 |
性能结果必须同时记录机器、网络、样本大小和运行次数,不能只写平均值。
### 阶段 5 实际结果记录
| 字段 | 实际值 |
|---|---|
| 状态 | 实现中 |
| 构建源 | 2026-08-02 本轮修复提交见 Git 历史;该提交不代表生产发布 |
| 后端测试 | `AihrKnowledgeResourceRangeTest`:`2/2` 通过 |
| 移动端测试 | Node `217/217`、typecheck、H5 build 通过;本轮未重跑 App-Plus 构建 |
| 越权矩阵 | 未完成真实后端矩阵;仅完成源码级治理约束复核 |
| 定位误差 | 未测量;无真实视频引用数据 |
| Range | 本机合成授权视频完整请求 `200`、Range 请求 `206`;未覆盖首段/中段/尾段/非法范围完整矩阵 |
| p50/p95 | 未测量 |
| 资源泄漏循环 | Node 单元覆盖 Blob URL 释放,未完成 20 次真实页面循环 |
| 敏感信息扫描 | 移动端截图/DOM 证据未包含手机号、验证码、令牌、票据或 OSS 原址;后端日志扫描未完成 |
| 证据目录 | `output/citation-source-viewer/20260731-local/` |
| 遗留项 | 正反权限矩阵、完整 Range 矩阵、定位误差、性能基准、资源循环和生产/真机证据仍待补齐 |
---
## 9. 阶段 6:发布与 Android 真机收口
**目标:** 将完成门禁的 schema、后端和移动端作为独立发布单元部署,并用正式账号与新签名 APK 验证。
### 9.1 发布单元
| 单元 | 触发条件 | 授权与验证 |
|---|---|---|
| schema | 新定位表/索引 | 先备份和只读计划;独立 `MIGRATE_DB:<hash>` 授权 |
| 后端 JAR | 详情、鉴权、Range、回填服务 | `release-backend.sh plan`;独立 `DEPLOY_BACKEND:<hash>` 授权 |
| 管理端 | 仅当新增回填/状态管理页 | 单独构建、备份和静态发布;不得覆盖 `/h5` |
| H5 | 引用详情页面 | 备份 `/opt/wygj/www/h5` 后定向同步 |
| Android | `mobile-uni` 页面和逻辑变化 | 版本号与 `versionCode` 递增,重新出包、深检和真机安装 |
`0.1.17 (117)` 不包含本计划功能。Android 验收必须使用后续新包,不得以 H5 发布替代 APK。
Windows 上的后端发布计划明确使用 Git Bash:
```powershell
& "C:\Program Files\Git\bin\bash.exe" scripts/release-backend.sh plan ...
```
不得改用 WSL `bash`,不得绕过脚本手工覆盖 JAR。
### 9.2 生产发布后验证
后端与 schema:
1. 服务 `active`;
2. 公开健康入口 HTTP 200;
3. schema 预检通过且运行时 DDL 关闭;
4. 正式账号文本、图片、视频详情正例;
5. 无权限账号、撤权和过期票据反例;
6. 视频 Range `206`;
7. 日志无敏感数据。
H5:
1. `390×844` 实际点击三类引用;
2. 刷新、返回、弱网和失权状态;
3. 不破坏“今日/练/问/我”导航和既有 Agent 会话。
Android 真机:
1. 新签名 APK 覆盖安装与冷启动;
2. 正式员工账号真实发问并打开文本引用;
3. 打开图片引用并缩放;
4. 打开视频引用,自动定位语句和画面;
5. 切后台再返回,定位和权限状态正确;
6. 切断网络后有明确错误,恢复网络可重试;
7. 撤权后再次点击失败关闭;
8. 无 Crash/ANR,测试后恢复设备权限和清理临时文件。
至少在当前 vivo Android 15 真机完成主证据;扩大试点前再补一个不同 Android 大版本或厂商。手机号、验证码、令牌和受保护正文不得出现在截图、录像文件名或报告中。
### 阶段 6 实际结果记录
| 字段 | 实际值 |
|---|---|
| 状态 | 未开始 |
| release commit | 未发布;2026-08-02 本机修复提交不等于生产发布 |
| schema 版本/迁移结果 | 仅本地 Docker 数据库验证;未执行生产迁移 |
| 生产 JAR SHA-256 | 未生成/未部署 |
| H5 构建与远端哈希 | 本地 H5 构建完成;未生产发布、未计算远端哈希 |
| APK 版本/versionCode | App-Plus 构建输入完成;未出包、未安装真机 |
| APK SHA-256/签名摘要 | 未生成 |
| 生产正反权限 | 未验证 |
| 真机型号/Android | 未验证 |
| 文本/图片/视频 | `0/3`,未进行正式账号真机验收 |
| 视频定位误差 | 未测量 |
| Crash/ANR | 未验证 |
| 回滚物 | 未生成 |
| 证据目录 | `output/citation-source-viewer/20260731-local/`(仅本地浏览器证据) |
| 遗留项 | 未授权生产部署;未完成正式账号、Android 真机、签名 APK 和正式资源验收 |
---
## 10. Go/No-Go
只有以下全部成立,才能称“手机端引用详情与媒体定位已完成生产验证”:
- [ ] 阶段 1—5 均有实际结果且状态为 `技术通过`;
- [ ] schema、后端、H5/Android 实际发布范围与本地构建一致;
- [ ] 正式账号文本、图片、视频 `3/3` 通过;
- [ ] 跨租户、撤权、过期和伪造反例全部拒绝;
- [ ] 视频 Range 和定位误差达到门槛;
- [ ] 新 Android APK 完成真机验证;
- [ ] 无 Crash/ANR;
- [ ] 回滚物、哈希和证据目录完整;
- [ ] 未把 DCloud 测试签名描述为企业签名或应用商店包。
任一项缺失时,结论只能是:
- “本地技术通过,待生产发布”,或
- “生产后端/H5 已通过,待 Android 新包真机”,或
- “文本/图片可用,视频精确定位仍阻断”。
不得用“引用已经能展开”“原文件可以下载”或“视频可以播放”替代本计划的定位、权限和真机证据。