feat: 累积功能变更 — 聊天集成、租户管理、小程序更新、ETL 增强、迁移脚本
包含多个会话的累积代码变更: - backend: AI 聊天服务、触发器调度、认证增强、WebSocket、调度器最小间隔 - admin-web: ETL 状态页、任务管理、调度配置、登录优化 - miniprogram: 看板页面、聊天集成、UI 组件、导航更新 - etl: DWS 新任务(finance_area_daily/board_cache)、连接器增强 - tenant-admin: 项目初始化 - db: 19 个迁移脚本(etl_feiqiu 11 + zqyy_app 8) - packages/shared: 枚举和工具函数更新 - tools: 数据库工具、报表生成、健康检查 - docs: PRD/架构/部署/合约文档更新 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
106
docs/prd/Neo_Specs/review-audit/P5.1-NS3-04.md
Normal file
106
docs/prd/Neo_Specs/review-audit/P5.1-NS3-04.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# P5.1→NS3 缺失项 #4:App4-App7 的缓存策略
|
||||
|
||||
## 简要结论
|
||||
- 状态:⚠️ 部分解决
|
||||
- 风险等级:🟠 中
|
||||
- cache_type 枚举和保留策略(500 条上限 + 清理机制)已实现,但缺少 expires_at 过期策略和主动失效条件的定义与实现。
|
||||
|
||||
## 详细审查
|
||||
|
||||
### 审查范围
|
||||
- `apps/backend/app/ai/cache_service.py` — AI 缓存读写服务
|
||||
- `apps/backend/app/ai/schemas.py` — CacheTypeEnum 枚举定义
|
||||
- `docs/database/ddl/zqyy_app__biz.sql` — ai_cache 表 DDL
|
||||
- `docs/prd/specs/P5-miniapp-ai-integration.md` — P5 spec 缓存策略定义
|
||||
|
||||
### 发现
|
||||
|
||||
#### ✅ 已实现部分
|
||||
|
||||
1. **cache_type 枚举**:`schemas.py` 中 `CacheTypeEnum` 定义了 7 个枚举值:
|
||||
```python
|
||||
APP2_FINANCE = "app2_finance"
|
||||
APP3_CLUE = "app3_clue"
|
||||
APP4_ANALYSIS = "app4_analysis"
|
||||
APP5_TACTICS = "app5_tactics"
|
||||
APP6_NOTE_ANALYSIS = "app6_note_analysis"
|
||||
APP7_CUSTOMER_ANALYSIS = "app7_customer_analysis"
|
||||
APP8_CLUE_CONSOLIDATED = "app8_clue_consolidated"
|
||||
```
|
||||
与 P5 spec 定义完全一致。
|
||||
|
||||
2. **保留策略**:`cache_service.py` 的 `_cleanup_excess()` 实现了 P5 spec 定义的"每个 (cache_type, site_id, target_id) 组合保留最近 500 条记录":
|
||||
```python
|
||||
def _cleanup_excess(self, ..., max_count: int = 500) -> int:
|
||||
```
|
||||
清理时机:每次 `write_cache()` 写入新记录后异步清理。
|
||||
|
||||
3. **target_id 约定**:各应用的 target_id 含义在 P5 spec 中有明确定义(App2=时间维度编码、App3/6/7/8=member_id、App4/5=assistant_id_member_id)。
|
||||
|
||||
4. **数据库表结构**:`ai_cache` 表包含 `expires_at timestamp with time zone` 字段,DDL 层面支持过期时间。
|
||||
|
||||
#### ❌ 未实现部分
|
||||
|
||||
1. **expires_at 过期策略**:
|
||||
- DDL 中 `expires_at` 字段存在但代码中从未设置有效值
|
||||
- `write_cache()` 接受 `expires_at` 参数,但所有调用方(App2-App8 的 `run()` 函数)均未传入 `expires_at`
|
||||
- 没有定时任务或查询逻辑检查 `expires_at` 并清理过期记录
|
||||
- `get_latest()` 查询时不过滤已过期记录
|
||||
|
||||
2. **主动失效条件**:
|
||||
- P5 spec 未定义具体的失效条件(如"数据源更新后旧缓存失效")
|
||||
- NS3 spec 提到"ai_cache 表但未定义策略"——确实如此
|
||||
- 当前实现是"只增不删"(除 500 条上限清理外),没有基于业务事件的缓存失效机制
|
||||
|
||||
3. **缓存读取时的新鲜度检查**:
|
||||
- `get_latest()` 仅按 `created_at DESC` 取最新一条,不检查缓存是否仍然有效
|
||||
- 前端读取缓存时无法判断数据是否过时
|
||||
|
||||
### 证据
|
||||
|
||||
`cache_service.py` 中 `write_cache()` 的 `expires_at` 参数:
|
||||
```python
|
||||
def write_cache(
|
||||
self,
|
||||
cache_type: str,
|
||||
site_id: int,
|
||||
target_id: str,
|
||||
result_json: dict,
|
||||
triggered_by: str | None = None,
|
||||
score: int | None = None,
|
||||
expires_at: datetime | None = None, # 接受但从未被调用方传入
|
||||
) -> int:
|
||||
```
|
||||
|
||||
App2 调用 `write_cache` 时未传入 `expires_at`:
|
||||
```python
|
||||
cache_svc.write_cache(
|
||||
cache_type=CacheTypeEnum.APP2_FINANCE.value,
|
||||
site_id=site_id,
|
||||
target_id=time_dimension,
|
||||
result_json=result,
|
||||
triggered_by=f"user:{user_id}",
|
||||
# 无 expires_at
|
||||
)
|
||||
```
|
||||
|
||||
DDL 中 `expires_at` 字段定义:
|
||||
```sql
|
||||
expires_at timestamp with time zone -- 存在但未被使用
|
||||
```
|
||||
|
||||
### 建议
|
||||
|
||||
1. **定义各应用的缓存过期策略**:
|
||||
| 应用 | 建议 expires_at | 理由 |
|
||||
|------|----------------|------|
|
||||
| App2 | 当日 08:00(营业日切点) | 财务数据每日更新,次日数据变化后旧缓存无意义 |
|
||||
| App3 | 7 天 | 消费数据线索在下次消费前有效 |
|
||||
| App4/5 | 3 天 | 关系分析和话术时效性较强 |
|
||||
| App6 | 无过期 | 备注分析结果不会因时间失效 |
|
||||
| App7 | 7 天 | 客户分析随新数据更新 |
|
||||
| App8 | 无过期 | 整合线索由上游触发更新 |
|
||||
|
||||
2. **实现过期检查**:在 `get_latest()` 中增加 `WHERE expires_at IS NULL OR expires_at > NOW()` 过滤。
|
||||
|
||||
3. **考虑增加缓存失效事件**:当 App3/App6 产出新内容后,可标记旧的 App7 缓存为失效,确保前端读到最新分析。
|
||||
Reference in New Issue
Block a user