# duobibi-server 多比比(占坑版)后端 — 比价 / 估价服务。 > 与傻瓜比价后端**同一套代码**,部署到独立域名 `api.duobibi.com`(端口 8766,与旧 8765 并存)。 > 新增两个多比比专属端点:`/arena-quote`(比价擂台)、`/worth-buy`(AI 值不值得买)。 > 占坑期 LLM key 与极光 key 仍硬编码,上线前迁移到环境变量。 ## 技术栈 - Python 3.11+ - FastAPI 0.115+ / uvicorn (standard) / pydantic 2.9+ - 智谱 zai-sdk(`glm-5-turbo`,温度 0.0,确定性输出) - SQLite(WAL 模式,单文件) - cryptography(极光 RSA 解密手机号) - pytest + pytest-asyncio + httpx(测试) ## 工程结构 ``` duobibi-server/ ├── app/ │ ├── main.py # FastAPI 应用入口,所有 router 注册,启动时建表 │ ├── schemas.py # 全部请求/响应 Pydantic DTO │ ├── db.py # SQLite 连接管理 + WAL + 表初始化 │ ├── llm_client.py # 智谱客户端,DUOBIBI_MOCK_LLM 开关 │ ├── extractor.py # Android 控件树 → LLM → title/price 提取 │ ├── mock_extractor.py # 占坑期预置商品池(确定性哈希选) │ └── api/ │ ├── parse.py # POST /api/v1/parse Android 无障碍树识别 │ ├── parse_image.py # POST /api/v1/parse-image iOS 截图 OCR │ ├── parse_text.py # POST /api/v1/parse-text iOS 文本新建 │ ├── parse_ocr.py # POST /api/v1/parse-ocr OCR 文本识别(补齐缺失) │ ├── quick_quote.py # POST /api/v1/quick-quote 用户主动比价查询 │ ├── arena.py # POST /api/v1/arena-quote 比价擂台(多比比新增) │ ├── worth_buy.py # POST /api/v1/worth-buy AI 值不值(多比比新增) │ ├── protect.py # POST /api/v1/track-price 价保哨兵 │ ├── auth.py # POST /api/v1/auth/jverify-login 极光一键登录 │ └── wish.py # POST /api/v1/wish/{list,upsert} 心愿单同步 ├── tests/ # test_health / test_arena_worth / test_parse_image ├── deploy/ │ ├── duobibi-mock.service # systemd unit(生产) │ └── nginx/api.duobibi.com.conf ├── pyproject.toml ├── run-backend.ps1 # Windows 一键启动脚本 └── data.db # SQLite(开发本地,生产在 /opt/duobibi-server/data.db) ``` ## 接口 ### `GET /health` ```json { "status": "ok" } ``` ### `POST /api/v1/arena-quote` — 比价擂台(多比比新增) 请求: ```json { "title": "iPhone 15 Pro 256GB", "platforms": ["淘宝","京东","拼多多","抖音"] } ``` `platforms` 可省略(默认 4 大电商)。响应: ```json { "title": "iPhone 15 Pro 256GB", "typical_price": 8299.0, "quotes": [ {"platform": "淘宝", "price": 8099.0, "note": ""}, {"platform": "京东", "price": 8299.0, "note": "自营"}, {"platform": "拼多多", "price": 7888.0, "note": "百亿补贴"}, {"platform": "抖音", "price": 8190.0, "note": ""} ], "lowest_platform": "拼多多" } ``` - LLM 缺平台报价时,按 `(title, platform)` 哈希确定性合成,保证 4 条都齐 - LLM 整体失败返回 422 `llm_no_quote`,不抛 5xx - 设 `DUOBIBI_MOCK_LLM=1` 可离线测试 ### `POST /api/v1/worth-buy` — AI 值不值得买(多比比新增) 请求: ```json { "title": "iPhone 15 Pro 256GB", "price": 8099.0, "platform": "京东" } ``` 响应: ```json { "score": 78, "verdict": "buy", "headline": "现在入手划算", "reasons": ["低于市场常见价约 2%", "距下次大促还有一段时间"], "typical_price": 8299.0, "best_time": "现在就合适" } ``` - `score`: 0–100,80+ → `buy` / 40–79 → `neutral` / <40 → `wait` - LLM 抽风 / JSON 解析失败也会返回中性结论,不抛 5xx - Mock 模式按"当前价 / 市场常见价"水位确定性算分 ### 沿用傻瓜比价的端点 | 端点 | 方法 | 用途 | |---|---|---| | `/api/v1/parse` | POST | Android 无障碍树 → title / price / cluster_id | | `/api/v1/parse-image` | POST(multipart)| iOS 截图 OCR(图片 ≤ 5MB) | | `/api/v1/parse-text` | POST | iOS 文本新建 | | `/api/v1/parse-ocr` | POST | OCR 文本识别(本工程补齐 mock 实现) | | `/api/v1/quick-quote` | POST | 用户主动比价查询 | | `/api/v1/track-price` | POST | 价保哨兵(确定性日抖动模拟降价) | | `/api/v1/auth/jverify-login` | POST | 极光 loginToken → 真手机号(RSA 解密) | | `/api/v1/wish/list` | POST | 拉取心愿单(含软删) | | `/api/v1/wish/upsert` | POST | 批量上传心愿单修改(LWW) | > 修复:旧后端 `main.py` 引用 `app.api.parse_ocr` 但文件缺失会导致启动 `ImportError`; > 本工程补齐了 `app/api/parse_ocr.py`(mock 实现),服务可正常启动。 ## 包名 → 品牌映射 | 包名 | 品牌 | |---|---| | com.taobao.taobao | 淘宝 | | com.jingdong.app.mall | 京东 | | com.xunmeng.pinduoduo | 拼多多 | | com.ss.android.ugc.aweme | 抖音 | | com.sankuai.meituan | 美团 | | com.sankuai.meituan.takeoutnew | 美团外卖 | | me.ele | 饿了么 | 非白名单包名返回 `source_app: "未知"`。 ## 环境变量 | 变量 | 用途 | 默认值 | |---|---|---| | `DUOBIBI_DB_PATH` | SQLite 数据库路径 | `/opt/duobibi-server/data.db`(生产)/ `./data.db`(本地) | | `DUOBIBI_MOCK_LLM` | 设为非空值 → 跳过真实 LLM,返回确定性 mock 数据 | 空(生产用真实 LLM) | | `JG_PRIVATE_KEY_PATH` | 极光一键登录 RSA 私钥路径 | `/opt/duobibi-server/secrets/jverify_rsa_private.pem` | | `PYTHONUTF8` | Windows 控制台 UTF-8(`run-backend.ps1` 自动设) | 空 | **硬编码待迁移**(`app/llm_client.py` / `app/api/auth.py`):智谱 API key、极光 App key、极光 Master Secret。 ## 数据存储 SQLite 单文件 + WAL,启动时 `IF NOT EXISTS` 建表,无 migration 工具。 **`user`** — 占坑期登录用户 | 字段 | 类型 | 说明 | |---|---|---| | `phone` | TEXT PK | 用户手机号(占坑期主键) | | `created_at` | INTEGER | 创建时间(ms epoch) | | `last_seen_at` | INTEGER | 最近一次登录时间(ms epoch) | **`wish_item`** — 心愿单(跨设备同步) | 字段 | 类型 | 说明 | |---|---|---| | `id` | INTEGER PK AUTO | 自增 | | `phone` | TEXT FK | 用户手机号 | | `title` / `target_price` / `note` / `notify_enabled` | — | 业务字段 | | `created_at` / `updated_at` | INTEGER | ms epoch | | `deleted_at` | INTEGER | NULL = 未删,非空 = 软删,防跨设备"复活" | 索引:`idx_wish_phone_updated` on `(phone, updated_at DESC)`。 ## 本地启动 **环境要求** - Python 3.11+ - 默认 mock 模式无需任何 API key 即可启动(不调真实 LLM,返回确定性占位数据) **方式一:PowerShell 脚本(Windows 推荐)** ```powershell .\run-backend.ps1 # mock 模式,监听 127.0.0.1:8766 .\run-backend.ps1 -Lan # 监听 0.0.0.0,供真机/局域网访问 .\run-backend.ps1 -Real # 连真实智谱 LLM(需先配好 key) .\run-backend.ps1 -Reload # 热重载,代码改动自动重启 .\run-backend.ps1 -Port 8888 # 改端口 ``` **方式二:手动 pip + uvicorn** ```bash cd duobibi-server pip install -e ".[dev]" uvicorn app.main:app --reload --port 8766 ``` 首次启动会自动在当前目录创建 `data.db`(SQLite,WAL 模式),无需手动建表。 **验证启动** ```bash curl http://localhost:8766/health # {"status":"ok"} ``` **测试** ```bash pytest -q ``` **烟测核心端点** ```bash curl -X POST http://localhost:8766/api/v1/arena-quote \ -H 'Content-Type: application/json' \ -d '{"title":"iPhone 15 Pro 256GB"}' curl -X POST http://localhost:8766/api/v1/worth-buy \ -H 'Content-Type: application/json' \ -d '{"title":"iPhone 15 Pro 256GB","price":8099}' ``` ## 部署(生产) - 域名: `api.duobibi.com` (HTTPS) - 进程: `uvicorn app.main:app --host 127.0.0.1 --port 8766 --workers 1`,systemd 守护 (`deploy/duobibi-mock.service`) - 反代: nginx → 127.0.0.1:8766(`deploy/nginx/api.duobibi.com.conf`,`client_max_body_size 6MB`) - SQLite 单文件(`DUOBIBI_DB_PATH` env 可覆盖,默认 `/opt/duobibi-server/data.db`) - 极光 RSA 私钥不入 git,部署时单独 scp 到 `/opt/duobibi-server/secrets/jverify_rsa_private.pem` ## 待办(占坑期遗留) - `app/llm_client.py` 的智谱 API key 仍硬编码,上线前迁移到环境变量并替换为多比比自己的 key - `app/api/auth.py` 的极光 App key / Master Secret 同样硬编码,需迁移到 env - 接口无鉴权 / 无限流,后续加 `X-Client-Token` 头校验 + IP 限流 - 无 Dockerfile / docker-compose,目前只支持 systemd 部署