线上问题报告:C端 商品位展示 70 条「测试套餐NNN」

报告人:波波 | 发现时间:2026-09-24 18:2x | 发现方式:车队包调研中查证 C端 category 参数时连带发现

严重等级:🟠 P1(内容展示事故,非资金事故) —— 详见 §四 影响面评估

当前状态:⏸️ 已取证完毕,等待裁定,未做任何 prod 数据改动


一、现象

prod 环境 C端 商品接口返回的全部内容是演示种子数据:

接口实际返回
GET /api/miniapp/products?limit=20070 条,名称全为「测试套餐001」~「测试套餐070」
GET /api/miniapp/products/featured?limit=6首页推荐位 = 测试套餐001~006
GET /api/miniapp/products/curated?limit=20甄选页 = 测试套餐001~020
GET /api/miniapp/products?category=transport仍返回同样 20 条(category 参数被忽略)

实测证据(prod 100.64.1.230:8088):

首页推荐位实际展示:
  · 测试套餐001  ¥288.0
  · 测试套餐002  ¥688.0
  · 测试套餐003  ¥98.0
  …
名称含「测试」的条数: 70 / 70

二、根因(两个独立缺陷叠加)

缺陷 A:C端 商品查询没有 is_test 过滤

backend/app/routers/miniapp_products.py:94(作者:小齐,2026-08-30,commit b65fc7e5)

async def list_products(limit: int = Query(50, ge=1, le=200)):
    rows = await pool.fetch(
        "SELECT id, name, tag, ... FROM packages WHERE status = 'active' "
        "ORDER BY sort_order, id LIMIT $1", limit)

缺陷 B:category 参数被完全忽略

category-package(package) / category-hiking(hiking) / category-vehicle(vehicle) / category-transport(transport) / category-hotel(hotel)

附加事实:数据模型无法支撑 category 过滤

packages 表没有 category 列,只有:

id, name, tag, description, price_fen, days, cover_url, images, status,
sort_order, created_at, updated_at, is_test, completion_rule, destination_id

三、数据来源追溯

项结论
谁插的backend/scripts/seed_demo_data.py(WIP-285 演示数据种子脚本,规范化版)
命名规律脚本 L202 "name": f"测试套餐{i:03d}" —— 与 prod 数据完全吻合
插入量脚本声明 products: {"rows": 80} —— 与 prod packages 80 条完全吻合
is_test全量 is_test=TRUE(脚本设计要点第 7 条明写「全量 is_test=true,与真实业务物理隔离」)
默认库脚本 DATABASE_URL 默认指向 staging(kailash-staging-pg)
prod 为何有脚本支持 DATABASE_URL= 覆盖,推断是有人显式指向 prod 跑的(我无法从代码确定是谁、何时、为何;prod 无本地 git 记录)
与 055 种子的关系无关。055_demo_seed.sql 只插 1 条「神山转山3日演示行程」,不是这 80 条
⚠️ 脚本的设计意图是「使全链可点(下单→收款确认→核销→对账)」,本身没错;错在 C端 查询没做 is_test 隔离,导致演示数据溢出到游客可见面。

四、影响面评估(查库为证)

✅ 好消息:无资金损失、无真实交易污染

核查项结果
orders 表 is_test 分布138 条全部 is_test=TRUE(paid 40 / completed 36 / in_service 27 / booked 14 / refunding 8 / refunded 7 / cancelled 6)
真实用户的订单0 条
引用测试套餐的真实订单0 条
verifications 核销记录0 条
用户构成81 个用户中 79 测试 / 2 真实

⇒ 没有真实用户付过钱、没有真实核销发生,所以这是内容展示事故,不是资金/对账事故。严重等级因此从 P0 降为 P1。

🟠 坏消息:游客看到的是纯演示内容

⚠️ 止损的制约(重要)

小冈今天 13:3x 刚交付「b端工作台静态演示画廊」(commit 6ac36879),明确用这批 is_test 数据给刘总演示:

「演示账号=测试商户001号(is_test),演示数据小冈自造入库:8001开通hotel+restaurant业态授权 + 3房型/12房间/4预订(挂真实orders)/3客人/2挂账/8菜品/8桌台/4餐饮订单(全is_test)」

⇒ 不能直接 --purge:会把小冈的演示数据一起删掉,破坏他给刘总的演示环境。


五、处置建议(三个选项,请你裁定)

选项 A:只补 C端 过滤,不动数据 ⭐我推荐

做什么:给 miniapp_products.py 的 5 个查询端点加 AND is_test = FALSE

选项 B:A + 实现 category 过滤

在 A 的基础上补 category 参数支持。

选项 C:purge 演示数据


六、我的建议与请你裁定项

建议顺序:

  1. 先做选项 A(补 C端 is_test 过滤)—— 立刻止血、零副作用、不影响演示
  2. category 过滤另立任务(选项 B),因为它卡在「套餐品类数据从哪来」这个产品决策上
  3. 真实套餐内容录入是根本解 —— 目前 prod packages 80 条全是测试数据,零真实套餐,这才是商品位空白的真正原因。属运营/内容侧,需你安排

请你裁定:

#问题我的倾向
1是否授权我改 miniapp_products.py(小齐域)补 is_test 过滤?建议授权,5 处 SQL 加一个条件,风险极低
2C端 商品位变空白是否可接受?还是需要先录入真实套餐?需你判断——空白比显示测试数据诚实,但对外形象要考虑
3category 过滤的品类数据来源怎么定?(加列 / 按 components 推导 / 用 tag)需你定产品口径,我不擅自决定
4小冈的演示画廊数据是否需要保留?建议保留,我不动 purge
5是否要通知小齐(他 08-30 写的这个适配层)和小冈(演示数据相关)?建议知会,避免各自重复排查

七、附:本轮已完成的取证动作(全部只读,零写入)

动作方式是否改数据
查 packages 表结构与 is_test 分布psql SELECT❌ 只读
实测 4 个 C端 接口返回内容curl GET❌ 只读
追溯种子脚本来源grep 代码❌ 只读
查真实用户订单/核销psql SELECT❌ 只读
查 v1/v2 前端消费点Grep 工具❌ 只读
git blame 确认归属git❌ 只读
prod 数据未做任何改动。所有 DELETE/purge 手段均未执行,等你裁定。