报告人:波波 | 发现时间:2026-09-24 18:2x | 发现方式:车队包调研中查证 C端 category 参数时连带发现
严重等级:🟠 P1(内容展示事故,非资金事故) —— 详见 §四 影响面评估
当前状态:⏸️ 已取证完毕,等待裁定,未做任何 prod 数据改动
prod 环境 C端 商品接口返回的全部内容是演示种子数据:
| 接口 | 实际返回 |
|---|---|
GET /api/miniapp/products?limit=200 | 70 条,名称全为「测试套餐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
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)
status='active',没有 is_test=FALSEis_test=TRUE ⇒ 只要 status='active' 就会出现在 C端is_test=FALSE(并做了反证验证),所以没这个问题category 参数被完全忽略limit,没有 category 形参?category=X: category-package(package) / category-hiking(hiking) / category-vehicle(vehicle) / category-transport(transport) / category-hotel(hotel)
GET /api/miniapp/products?category={X}&limit=20,说明前端是按契约写的,后端没实现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
tag 列取值是 tag_0~tag_4(各 16 条),是种子生成的占位值,不是业务品类package_components.type(hotel 17 / guide 17 / driver 17 / ticket 16 / meal 16)| 项 | 结论 |
|---|---|
| 谁插的 | 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。
pages/home/home.ts:137 消费 products/featured ⇒ 线上真实游客打开首页,推荐位是「测试套餐001 ¥288」小冈今天 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:会把小冈的演示数据一起删掉,破坏他给刘总的演示环境。
做什么:给 miniapp_products.py 的 5 个查询端点加 AND is_test = FALSE
miniapp_products.py 是小齐的代码,跨域改动需你许可或转交在 A 的基础上补 category 参数支持。
packages 表没有 category 列,必须先定品类数据来源(加列 / 按 components 推导)——这是产品决策,我不能拍packages 而不影响其他表演示数据建议顺序:
packages 80 条全是测试数据,零真实套餐,这才是商品位空白的真正原因。属运营/内容侧,需你安排请你裁定:
| # | 问题 | 我的倾向 |
|---|---|---|
| 1 | 是否授权我改 miniapp_products.py(小齐域)补 is_test 过滤? | 建议授权,5 处 SQL 加一个条件,风险极低 |
| 2 | C端 商品位变空白是否可接受?还是需要先录入真实套餐? | 需你判断——空白比显示测试数据诚实,但对外形象要考虑 |
| 3 | category 过滤的品类数据来源怎么定?(加列 / 按 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 手段均未执行,等你裁定。