635d127f95
compare.py 透传壳新增后端 harvest(不再依赖客户端 POST /compare/record): - 帧0(客户端首帧不带 trace_id)由 app-server 用 uuid 签发 trace_id、注入转发 body、 回填响应顶层,并按 trace_id 建 running 行(harvest_running) - done 帧(command=done 且 continue=false)更新为 success/failed 终态,并对本次新落成 success 幂等发一次邀请奖(harvest_done + try_reward_on_compare) - /trace/finalize(用户终止/Phase1 未识别,无 done)更新为 cancelled/failed, 不降级已 success(harvest_abort) - 软鉴权 OptionalUser(deps.py 新增 get_current_user_optional):新客户端带 JWT 绑 user_id,老客户端匿名建行、user_id 暂空,由其后续 POST /compare/record 按 trace_id reconcile;新版覆盖够高后可收紧成硬鉴权 - 结构化日志(logging.py 加 trace_id_ctx ContextVar):请求级 trace_id 贯穿 harvest 各阶段 comparison_record 表结构调整(迁移 comparison_record_trace_unique): - 唯一键 (user_id,trace_id) → trace_id 单列(harvest 帧0 建行时 user_id 可能暂缺) - user_id 改可空 repositories/comparison.py 加 harvest_running/harvest_done/harvest_abort; compare_record.py 的 POST /record 降级为灰度期老客户端兼容(按 trace_id 幂等、不降级 success); 补 tests/test_compare_harvest.py,test_compare_proxy/milestone 适配。 文档同步: docs/database/comparison_record.md 更新表结构(唯一键/user_id 可空/status 取值) 与写入模型; docs/api/compare/compare-record-report.md 标注 POST /record 灰度定位。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
50 lines
2.5 KiB
Python
50 lines
2.5 KiB
Python
"""comparison_record: 唯一键 (user_id,trace_id) → trace_id 单列 + user_id 可空
|
|
|
|
Revision ID: comparison_record_trace_unique
|
|
Revises: feedback_type_reply
|
|
Create Date: 2026-07-03 12:00:00.000000
|
|
|
|
比价记录改「后端 harvest」:app-server 在帧0(pricebot 出 trace_id)即建行,随 done/finalize
|
|
逐步补全,客户端不再 POST 记录。故本表要两处调整:
|
|
1. user_id 改**可空**——harvest 建行那刻(软鉴权 / 老客户端匿名)可能还没有 user_id。
|
|
2. 唯一键 (user_id, trace_id) → **trace_id 单列**——trace_id 由 app-server 签发、全局唯一,
|
|
一次比价一行;harvest 建行时 user_id 还没有,不能再用复合键去重。
|
|
|
|
⚠️ 上线前(QA)务必确认 comparison_record 无重复 trace_id:旧复合唯一键**允许**同 trace_id
|
|
跨不同 user_id(实际上客户端 UUID 从不重复,但约束没拦),若真有重复,建 trace 单列唯一会失败。
|
|
查: SELECT trace_id, COUNT(*) c FROM comparison_record GROUP BY trace_id HAVING c > 1;
|
|
|
|
SQLite 不支持直接 drop/alter 约束/列,用 batch_alter_table(建临时表+拷数据+换名),
|
|
与 coupon_engage_per_package / store_mapping_* 同款;Postgres 直接执行原生 ALTER。
|
|
"""
|
|
from typing import Sequence, Union
|
|
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
|
|
# revision identifiers, used by Alembic.
|
|
revision: str = 'comparison_record_trace_unique'
|
|
down_revision: Union[str, Sequence[str], None] = 'feedback_type_reply'
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
with op.batch_alter_table('comparison_record', schema=None) as batch_op:
|
|
# harvest 帧0 建行时 user_id 可能暂缺 → 放开非空。
|
|
batch_op.alter_column('user_id', existing_type=sa.Integer(), nullable=True)
|
|
# 复合唯一 → trace_id 单列唯一。
|
|
batch_op.drop_constraint('uq_comparison_user_trace', type_='unique')
|
|
batch_op.create_unique_constraint('uq_comparison_trace', ['trace_id'])
|
|
|
|
|
|
def downgrade() -> None:
|
|
with op.batch_alter_table('comparison_record', schema=None) as batch_op:
|
|
batch_op.drop_constraint('uq_comparison_trace', type_='unique')
|
|
batch_op.create_unique_constraint(
|
|
'uq_comparison_user_trace', ['user_id', 'trace_id']
|
|
)
|
|
# 回退非空前提是当时无 null user_id 行(harvest 期可能有孤儿行,回滚需先清理)。
|
|
batch_op.alter_column('user_id', existing_type=sa.Integer(), nullable=False)
|