580 lines
15 KiB
Markdown
580 lines
15 KiB
Markdown
# llm-in-text 生产环境修复清单
|
||
|
||
## 文档目的
|
||
|
||
本文档是针对当前仓库在 2026-04-01 状态下基于代码的生产就绪性审查。
|
||
|
||
它回答三个问题:
|
||
|
||
1. 当前项目是否已准备好投入生产?
|
||
2. 自上次审查以来已修复了哪些问题?
|
||
3. 还有什么因素阻碍安全上线?
|
||
|
||
本次审查仅限于仓库中已有的内容和本地直接验证的内容,不包括外部基础设施、反向代理配置、云资源、CI 平台密钥或运行时运维的完整审计。
|
||
|
||
## 审查范围
|
||
|
||
- 前端构建和运行时入口
|
||
- 后端 FastAPI 端点和模型集成
|
||
- 补全、OCR 和文件转换请求路径
|
||
- 隐私相关行为和本地存储
|
||
- 基础测试覆盖率和构建验证
|
||
- PWA/Service Worker 状态
|
||
- 仓库中存在的部署和运维工件
|
||
|
||
## 已验证的事实
|
||
|
||
以下项目已针对当前仓库直接验证:
|
||
|
||
- 前端生产构建通过 `npm.cmd run build` 成功
|
||
- 后端测试通过 `pytest backend/tests -q`
|
||
- 当前后端测试结果:`8 passed, 1 skipped`
|
||
- 存在健康检查端点:`/health/live` 和 `/health/ready`
|
||
- 前端不再硬编码 API 密钥
|
||
- 后端不再需要旧的 `X-API-Key` 头
|
||
- 前端隐私模式现在默认为启用
|
||
- 后端错误响应现在已规范化,不再返回原始异常字符串
|
||
- OCR 和转换端点现在强制执行基本的文件大小和扩展名检查
|
||
|
||
## 当前评估
|
||
|
||
项目尚未准备好投入生产。
|
||
|
||
当前状态更接近于:
|
||
|
||
- 一个可用的原型
|
||
- 内部演示
|
||
- 具有部分加固的预发布候选版本
|
||
|
||
由于缺少几个核心生产基线,它尚未准备好面向互联网的生产使用:
|
||
|
||
- 真正的限流和并发保护
|
||
- 正式的部署工件和运行时拓扑
|
||
- CI/CD 和自动化质量门禁
|
||
- 结构化的可观测性和告警
|
||
- 更强的请求验证和更安全的文件处理隔离
|
||
- 前端自动化测试和端到端验证
|
||
|
||
## 已修复的问题
|
||
|
||
与之前的清单相比,以下项目不再作为阻碍因素:
|
||
|
||
### 已修复:硬编码的前端/后端共享 API 密钥
|
||
|
||
- `src/utils/api.js` 不再发送 `X-API-Key`
|
||
- `src/utils/convert.js` 不再发送 `X-API-Key`
|
||
- `backend/main.py` 不再强制执行旧的静态密钥
|
||
|
||
这消除了上次审查中最严重的问题之一。
|
||
|
||
遗留问题:
|
||
|
||
- `backend/tests/test_main_cancel.py` 仍然包含过时的 `X-API-Key` 头,但它们目前是无效的,表明测试假设已过时而非活动中的认证逻辑
|
||
|
||
### 已修复:危险的通配符 CORS 配置
|
||
|
||
当前后端 CORS 限制为:
|
||
|
||
- `http://localhost:5173`
|
||
- `http://localhost:3000`
|
||
|
||
并使用:
|
||
|
||
- `allow_credentials=False`
|
||
- `allow_methods=["POST", "OPTIONS"]`
|
||
- `allow_headers=["Content-Type", "X-Request-Id"]`
|
||
|
||
这比之前的通配符配置安全得多。
|
||
|
||
遗留问题:
|
||
|
||
- CORS 仍然针对本地开发硬编码,对于 staging/生产环境不是环境驱动的
|
||
|
||
### 已修复:默认收集前端公网 IP
|
||
|
||
- `src/stores/settings.js` 现在将 `privacyMode` 默认为 `true`
|
||
- `src/utils/api.js` 不再调用 `ipify`
|
||
- `src/utils/api.js` 不再发送 `X-Client-IP`
|
||
- `backend/main.py` 不再将 IP 派生的位置注入提示词
|
||
|
||
遗留问题:
|
||
|
||
- `backend/geoip.py` 和 GeoLite 数据库仍然存在于仓库中,这可能会造成对当前隐私模型的混淆
|
||
|
||
### 已修复:向客户端暴露原始异常字符串
|
||
|
||
当前后端响应使用 `_error_response(...)` 和结构化载荷,例如:
|
||
|
||
- `INTERNAL_ERROR`
|
||
- `OCR_FAILED`
|
||
- `CONVERT_FAILED`
|
||
- `FILE_TOO_LARGE`
|
||
- `INVALID_FILE_TYPE`
|
||
|
||
这比直接返回 `str(exception)` 更好。
|
||
|
||
遗留问题:
|
||
|
||
- 日志仍然记录用户派生内容的预览,这是另一个隐私/可观测性问题
|
||
|
||
### 部分修复:上传和转换输入边界
|
||
|
||
`backend/main.py` 中的当前后端保护包括:
|
||
|
||
- OCR 大小上限:10 MB
|
||
- 转换大小上限:50 MB
|
||
- OCR 和转换的扩展名白名单
|
||
- 在 `finally` 中清理临时转换文件
|
||
|
||
这是有意义的进展,但不足以投入生产。
|
||
|
||
## 生产阻碍因素
|
||
|
||
优先级含义:
|
||
|
||
- `P0`:必须在生产上线前完成
|
||
- `P1`:应在公开发布或广泛推广前完成
|
||
- `P2`:重要的后续加固和维护工作
|
||
|
||
---
|
||
|
||
## P0 阻碍因素
|
||
|
||
### P0-01 声明了限流和并发控制但未强制执行
|
||
|
||
当前状态:
|
||
|
||
- `backend/main.py` 定义了 `MAX_CONCURRENT_COMPLETIONS = 4`
|
||
- `backend/main.py` 定义了 `COMPLETION_RATE_LIMIT = 60`
|
||
- 没有任何实际的限流器使用这两个值
|
||
- OCR 和转换端点也没有真正的每客户端节流
|
||
|
||
风险:
|
||
|
||
- 容易滥用昂贵的补全/OCR/转换端点
|
||
- 可避免地对模型主机、CPU、内存和临时存储造成过载
|
||
- 突发流量下无法控制降级
|
||
|
||
必需的修复:
|
||
|
||
1. 在应用或网关中强制执行真正的每路由限流
|
||
2. 为补全作业添加真正的并发保护
|
||
3. 为 `/v1/completions`、`/v1/ocr`、`/v1/convert` 添加单独预算
|
||
4. 达到限制时返回明确的 `429`
|
||
5. 为节流的请求和队列深度发出指标
|
||
|
||
验收标准:
|
||
|
||
- 重复的突发流量触发确定性的 `429`
|
||
- 并发补全不能超过配置的预算
|
||
- 负载测试下服务保持稳定
|
||
|
||
---
|
||
|
||
### P0-02 仓库中不存在生产部署基线
|
||
|
||
当前状态:
|
||
|
||
- 后端仅通过 `uvicorn.run(...)` 暴露开发式启动
|
||
- 没有 `Dockerfile`
|
||
- 没有 compose 文件
|
||
- 没有 Kubernetes 清单或 Helm chart
|
||
- 没有 systemd 单元
|
||
- 没有反向代理参考配置
|
||
- 没有记录的生产环境契约
|
||
|
||
风险:
|
||
|
||
- 没有可复现的部署路径
|
||
- 没有明确的过程监督、重启或优雅的发布模型
|
||
- 没有记录的 ingress/请求体大小/超时/TLS 姿态
|
||
|
||
必需的修复:
|
||
|
||
1. 定义一个官方部署目标
|
||
2. 为该目标添加部署工件
|
||
3. 记录所需的环境变量、端口、探针和存储
|
||
4. 定义优雅关闭和发布行为
|
||
5. 记录反向代理限制和信任边界
|
||
|
||
验收标准:
|
||
|
||
- 新环境可以从仓库文档和工件部署
|
||
- 健康探针已接入所选运行时
|
||
- 回滚路径已记录
|
||
|
||
---
|
||
|
||
### P0-03 缺少 CI/CD 和仓库质量门禁
|
||
|
||
当前状态:
|
||
|
||
- 仓库级别没有找到 `.github/workflows`
|
||
- `package.json` 没有真正的前端测试脚本
|
||
- 没有 lint 脚本
|
||
- 没有类型检查脚本
|
||
- 没有依赖扫描或密钥扫描工作流
|
||
|
||
风险:
|
||
|
||
- 回归只能手动捕获
|
||
- 安全和打包漂移很可能发生
|
||
- 生产就绪性取决于本地开发者的规范
|
||
|
||
必需的修复:
|
||
|
||
1. 为构建和测试添加 CI 工作流
|
||
2. 添加前端自动化测试
|
||
3. 在适用的情况下添加 lint 和类型检查门禁
|
||
4. 添加依赖漏洞扫描
|
||
5. 添加密钥扫描和基本 SAST
|
||
|
||
验收标准:
|
||
|
||
- 每个 PR 都运行构建、后端测试、前端测试和 lint
|
||
- 失败的检查阻止合并
|
||
|
||
---
|
||
|
||
### P0-04 文件处理路径仍然缺乏生产级隔离
|
||
|
||
当前状态:
|
||
|
||
- 后端将完整 base64 载荷解码到内存中
|
||
- 转换写入临时文件并将其传递给 `markitdown`
|
||
- OCR 和转换主要依赖扩展名检查,而不是内容嗅探
|
||
- 没有工作进程隔离或用于转换的单独沙箱
|
||
- 转换任务没有队列或资源预算
|
||
|
||
风险:
|
||
|
||
- 大型或并发上传导致内存峰值
|
||
- 畸形或对抗性文档会给解析器带来压力
|
||
- 转换工作负载可能干扰核心补全可用性
|
||
|
||
必需的修复:
|
||
|
||
1. 明确验证 base64 解码失败
|
||
2. 添加 MIME/内容嗅探,而不仅仅是扩展名检查
|
||
3. 在入口和应用层添加更低的、路由特定的请求体限制
|
||
4. 将转换隔离到单独的工作进程/进程边界
|
||
5. 添加超时、并发上限和队列深度控制
|
||
|
||
验收标准:
|
||
|
||
- 畸形载荷失败并返回明确的 4xx 响应
|
||
- 转换不能饿死补全服务
|
||
- 压力下临时文件和内存增长保持有界
|
||
|
||
---
|
||
|
||
### P0-05 环境配置不一致且不安全
|
||
|
||
当前状态:
|
||
|
||
- 前端 `.env.example` 相当安全
|
||
- 后端 `.env.example` 过时且与代码不一致
|
||
- 代码读取 `OLLAMA_HOST`
|
||
- 后端示例仍然使用 `OLLAMA_BASE_URL`
|
||
- 后端示例仍然定义 `OPENAI_API_KEY=ollama`,这是误导性的
|
||
|
||
风险:
|
||
|
||
- 新环境配置不正确
|
||
- 运营商可能假设不支持的认证/配置行为
|
||
- staging/生产漂移很可能发生
|
||
|
||
必需的修复:
|
||
|
||
1. 用代码实际使用的变量替换后端环境示例
|
||
2. 在启动时验证所需的环境变量
|
||
3. 分离开发、staging 和生产环境契约
|
||
4. 缺失关键配置时快速失败
|
||
|
||
验收标准:
|
||
|
||
- 示例环境文件匹配真实运行时行为
|
||
- 无效或缺失关键配置导致启动失败
|
||
|
||
---
|
||
|
||
## P1 高优先级差距
|
||
|
||
### P1-01 日志仍然捕获用户派生内容预览
|
||
|
||
当前状态:
|
||
|
||
- `backend/main.py` 记录提示词派生的前缀和后缀预览
|
||
- 补全结果记录包含内容预览
|
||
- OCR 和转换记录文本预览长度和片段
|
||
|
||
风险:
|
||
|
||
- 日志可能包含敏感文档内容
|
||
- 隐私姿态与应用可见的隐私设置不一致
|
||
- 难以证明保留/合规姿态
|
||
|
||
必需的修复:
|
||
|
||
1. 默认情况下停止记录用户内容正文和预览
|
||
2. 仅保留请求元数据:路由、请求 ID、状态、延迟、大小
|
||
3. 引入结构化 JSON 日志
|
||
4. 脱敏或哈希任何敏感标识符
|
||
|
||
验收标准:
|
||
|
||
- 默认日志不包含用户文档文本
|
||
- 请求关联仍可通过请求 ID 和元数据工作
|
||
|
||
### P1-02 请求验证仍然过于宽松
|
||
|
||
当前状态:
|
||
|
||
- Pydantic 模型定义了字段但没有长度或枚举约束
|
||
- `prefix`、`suffix`、`filename` 和 `reason` 受到极小约束
|
||
- base64 字段在路由特定检查之前仍然可能非常大
|
||
- 前端转换路径在将完整文件读入 base64 之前不执行预验证
|
||
|
||
风险:
|
||
|
||
- 过大或畸形的请求太容易到达昂贵的逻辑
|
||
- 端点之间的 4xx 行为不一致
|
||
|
||
必需的修复:
|
||
|
||
1. 为 Pydantic 字段添加长度和枚举约束
|
||
2. 明确验证 base64 格式
|
||
3. 更防御性地规范化文件名处理
|
||
4. 添加前端预检查大小/类型作为 UX
|
||
|
||
验收标准:
|
||
|
||
- 畸形请求尽早失败并返回确定性的 4xx 响应
|
||
|
||
### P1-03 前端自动化覆盖率基本缺失
|
||
|
||
当前状态:
|
||
|
||
- 后端有针对性的单元/集成风格测试
|
||
- 前端没有配置测试运行器
|
||
- 核心用户路径没有 E2E 覆盖率
|
||
|
||
重要说明:
|
||
|
||
- `backend/tests/test_main_cancel.py` 仍然发送过时的 `X-API-Key` 头;测试通过仅仅是因为后端忽略它们
|
||
|
||
风险:
|
||
|
||
- 编辑器、上传、OCR、转换和设置的回归将会遗漏
|
||
|
||
必需的修复:
|
||
|
||
1. 添加前端单元/组件测试
|
||
2. 为补全、取消、上传、OCR 和转换添加 E2E 覆盖率
|
||
3. 从后端测试中移除过时的认证假设
|
||
|
||
验收标准:
|
||
|
||
- 核心用户旅程在 CI 中自动覆盖
|
||
|
||
### P1-04 健康检查端点存在,但就绪性浅且缺少可观测性
|
||
|
||
当前状态:
|
||
|
||
- `/health/live` 存在
|
||
- `/health/ready` 存在
|
||
- 就绪性实际上不检查上游模型可用性
|
||
- 没有指标端点
|
||
- 没有追踪
|
||
- 没有告警定义
|
||
|
||
风险:
|
||
|
||
- 运行时故障检测太晚
|
||
- 平台探针可能报告健康而上游依赖不可用
|
||
|
||
必需的修复:
|
||
|
||
1. 使就绪性反映关键依赖状态
|
||
2. 添加请求/延迟/错误指标
|
||
3. 为上游故障和饱和添加告警阈值
|
||
4. 为关键路由定义仪表板
|
||
|
||
验收标准:
|
||
|
||
- 运营商可以快速检测模型依赖故障
|
||
- 请求成功率和延迟可观测
|
||
|
||
### P1-05 Service Worker 实现存在但被禁用
|
||
|
||
当前状态:
|
||
|
||
- `public/sw.js` 存在
|
||
- `src/main.js` 用 `&& false` 硬禁用注册
|
||
- Service Worker 策略是手写的并通过静态缓存名称版本化
|
||
|
||
风险:
|
||
|
||
- 当前仓库包含未被实际使用的休眠 PWA 逻辑
|
||
- 如果随意重新启用,更新和缓存行为可能很脆弱
|
||
|
||
必需的修复:
|
||
|
||
1. 确定 PWA 是否在生产范围内
|
||
2. 如果是,采用维护的策略如 Vite PWA/Workbox
|
||
3. 如果不是,移除无用的 Service Worker 代码以减少混淆
|
||
|
||
验收标准:
|
||
|
||
- PWA 行为要么被有意支持和测试,要么被完全移除
|
||
|
||
### P1-06 背景图像持久化可能导致本地存储和内存膨胀
|
||
|
||
当前状态:
|
||
|
||
- 设置面板将上传的背景图像读取为 data URL
|
||
- 背景图像数据存储在 localStorage 中
|
||
- 没有对背景资产强制执行明确的大小上限
|
||
|
||
风险:
|
||
|
||
- 存储配额耗尽
|
||
- 大图像导致 UI 缓慢
|
||
- 跨浏览器的持久化行为脆弱
|
||
|
||
必需的修复:
|
||
|
||
1. 读取前在客户端添加大小上限
|
||
2. 持久化前调整大小/压缩
|
||
3. 对于较大的资产,优先使用 blob/object URL 或 IndexedDB
|
||
4. 为存储溢出添加迁移/错误处理
|
||
|
||
验收标准:
|
||
|
||
- 大图像不能降低启动或破坏设置持久化
|
||
|
||
---
|
||
|
||
## P2 重要后续工作
|
||
|
||
### P2-01 构建成功,但 bundle/chunk 策略仍然粗糙
|
||
|
||
验证的构建输出显示:
|
||
|
||
- `manualChunks` 生成许多空 chunk
|
||
- 一个与 Mermaid 相关的大型 chunk 超过 1 MB 压缩后
|
||
|
||
风险:
|
||
|
||
- 不必要的 chunk 开销
|
||
- 较弱的设备上冷启动较慢
|
||
|
||
必需的修复:
|
||
|
||
1. 简化 `manualChunks`
|
||
2. 延迟加载重型可选功能
|
||
3. 清理 chunk 后重新测量首次加载成本
|
||
|
||
### P2-02 OCR 缓存和图像哈希缓存没有明确的驱逐策略
|
||
|
||
当前状态:
|
||
|
||
- OCR 数据存储在内存中的 `Map`
|
||
- 哈希缓存也在内存中
|
||
- 没有 TTL
|
||
- 没有最大条目数
|
||
|
||
风险:
|
||
|
||
- 长时间会话会累积内存
|
||
|
||
必需的修复:
|
||
|
||
1. 添加 TTL 和条目边界
|
||
2. 如需要,暴露缓存指标用于调试
|
||
|
||
### P2-03 仓库仍然包含过时和混淆的工件
|
||
|
||
示例:
|
||
|
||
- `backend/geoip.py` 和 GeoLite DB 仍然存在,尽管 IP 地理定位在请求流程中不再活跃
|
||
- `backend/.env.example` 记录的变量不是代码使用的
|
||
- 后端测试仍然包含过时的 `X-API-Key`
|
||
|
||
风险:
|
||
|
||
- 未来维护者可能无意中重新引入已移除的行为
|
||
|
||
必需的修复:
|
||
|
||
1. 移除死代码和过时配置
|
||
2. 使测试和文档与当前实现保持一致
|
||
|
||
---
|
||
|
||
## 上线前必需的缺失证据
|
||
|
||
仓库目前不提供以下生产能力的证据:
|
||
|
||
- staging 部署管道
|
||
- 回滚程序
|
||
- 流量/负载测试结果
|
||
- 故障注入或混沌测试
|
||
- 备份/恢复程序
|
||
- 事件响应运行手册
|
||
- SLO/SLA 定义
|
||
- 安全扫描基线
|
||
- 依赖更新策略
|
||
- 隐私/数据保留文档
|
||
|
||
目前应将证据缺失视为未就绪,而不是隐式完成。
|
||
|
||
## 推荐的修复顺序
|
||
|
||
### 第一阶段:解除生产上线阻碍
|
||
|
||
1. 实现真正的限流和并发强制执行
|
||
2. 定义官方部署拓扑和工件
|
||
3. 添加 CI/CD 质量门禁
|
||
4. 加固和隔离文件处理工作负载
|
||
5. 修复后端环境配置契约
|
||
|
||
### 第二阶段:稳定运维和隐私姿态
|
||
|
||
1. 移除承载内容的日志
|
||
2. 加强请求验证
|
||
3. 深化就绪检查和指标
|
||
4. 添加前端和 E2E 自动化测试
|
||
|
||
### 第三阶段:性能和可维护性清理
|
||
|
||
1. 清理 chunk 策略
|
||
2. 限制 OCR/图像缓存
|
||
3. 移除过时代码和配置
|
||
4. 决定 PWA 支持是保留还是移除
|
||
|
||
## 最低上线门槛
|
||
|
||
至少在以下所有条件都满足之前,不应称该项目为生产就绪:
|
||
|
||
- 所有 `P0` 项目都已完成
|
||
- 日志不再捕获用户内容
|
||
- 前端和端到端自动化测试存在并在 CI 中运行
|
||
- 就绪性反映真实的上游依赖状态
|
||
- 部署和回滚已记录且可重现
|
||
- staging 环境已通过集成验证
|
||
- 至少执行了一次受控负载测试并经过审查
|
||
|
||
## 最终评估
|
||
|
||
与之前的清单相比,该项目已有实质性改进。几个严重的早期发现不再成立,特别是:
|
||
|
||
- 硬编码的认证密钥暴露
|
||
- 通配符式 CORS 姿态
|
||
- 默认公网 IP 收集
|
||
- 原始异常泄漏
|
||
|
||
然而,这一进展并不意味着已准备好投入生产。
|
||
|
||
当前仓库展示了有用的加固工作,但仍然缺乏生产服务预期的运维、测试、节流、部署和可观测性基线。
|