Files
marco 4ee6de2548 feat(cps): 活动支持编辑(PATCH /activities/{id})
- CpsActivityUpdate schema + repo update_activity(部分更新,非 None 覆盖)
- router 按「合并后最终值」校验平台必填项(同新建口径),写审计 cps.activity.update
- 可改名/平台/对应字段/落地页图/备注/状态

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 22:13:35 +08:00

107 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 待办与技术债账本
> 用途:记"现在先简化、以后要补"的事,和暂不处理的技术债,免得忘。每条标优先级 + 触发背景。做掉的移到末尾「已解决」。
> 范围:跨栈账本——本仓 `shaguabijia-app-server`(转发层) + `shaguabijia-app-android`(无障碍引擎移植) + `pricebot-backend`(agent 上游,不改)。
> 大背景:把"后端当大脑、无障碍服务遥控前端操作其它 App"的比价/领券引擎,从 demo app `pricebot` 移植进正式版傻瓜比价。引擎移植采用「代码全搬、独立子包 `…shaguabijia.agent.*`、验证分步(先领券后比价)」。
> 最后更新:2026-05-31
---
## P1 · 鉴权与用户绑定(最该补)
- **现状**agent 系列接口 MVP 阶段**全部不鉴权**。领券 `coupon/step` **已落地不鉴权**(✅ 已去掉 `CurrentUser` 依赖);比价 4 个(`intent/recognize``price/step``ecom/intent/recognize``ecom/step`)端点尚未建(见 P2),建时同样先不加鉴权。
- **代价(为什么记这笔账)**:不验 JWT → 转发时 server 拿不到 `user_id` → agent 行为只能绑到 `device_id`(设备级),**采集不到"哪个用户领了/买了什么"的用户级画像**。而精准人群画像、私域分群运营是商业模式的核心资产,靠的就是这份用户级行为数据。`device_id` 仍照传(后端按设备串领券队列够用)。
- **待补**:① agent 接口加 JWT 鉴权;② 建立 `device_id ↔ user_id` 绑定(登录后上报一次即可);③ ~~领券/比价记录按 user 维度落库~~**比价记录已落地**(见文末「已解决」):走**独立的鉴权端点** `POST /api/v1/compare/record`,客户端在 done 帧后用带 JWT 的通道上报、不依赖透传链路。**所以透传端点本身仍不鉴权、本条 P1 主诉求(给透传链路补 JWT + device↔user 绑定)未解**;领券记录尚未落库。
- **连带**:补鉴权后,客户端引擎 `ApiClient` 要接回 JWT(复用 app 现有 `AuthInterceptor` 思路:注入带鉴权头的 OkHttpClient)。比价记录上报已**先行复用**这套(`authedClient``AuthInterceptor`+`RefreshAuthenticator`),可作透传链路补鉴权的参考样板。
---
## P2 · 引擎移植 · 后端转发层(本仓)
- **比价透传端点**:仿 `coupon_step` **纯 body 透传、不加 Pydantic schema**(协议归 pricebot 校验,否则它一改字段本仓就得跟着改,白白耦合)。client `/api/v1/xxx` → 转发 backend `/api/xxx`(去掉 v1,同 `coupon_step`)。
-**外卖 2 个已落地**(food MVP2026-05-27)`intent/recognize` + `price/step`,在 `app/api/v1/compare.py`(不鉴权、超时 60s),测试 `tests/test_compare_proxy.py`,文档 `docs/api/compare-*.md`
- 电商 2 个待接:`ecom/intent/recognize` + `ecom/step`——放开电商 scene 时在 `compare.py` 加两行即可。
-**转发超时已分离**(2026-05-27):比价用新增的 `PRICEBOT_COMPARE_TIMEOUT_SEC=60`(意图识别大上下文 LLM、price/step 每帧 LLM),与领券的 `PRICEBOT_REQUEST_TIMEOUT_SEC=30` 区分;真实耗时待真机实测再定终值。
- **生产 server 与 backend 同内网**`PRICEBOT_BASE_URL` 部署时设内网地址。比价是 30~80 步循环,每步多一跳,走公网延迟会累积。
- **nginx body 上限**:比价 debug 包带 `screenshot`(webp base64,几百 KB)。若 debug 联调走线上域名经 nginx,注意 `client_max_body_size`(release 不带截图、debug 直连本地都无此问题,仅"debug 走域名"才撞)。
---
## P2 · 引擎移植 · 客户端(android 仓)
**领券链路相关已完成**(详见文末「已解决」):无障碍身份改名、BuildConfig 包名、领券入口、SettingsScreen 权限卡、ApiClient 改址。剩余:
-**比价悬浮球(外卖)已接**(2026-05-27)`SUPPORTED_BIZ_PACKAGES` 收窄到 3 个外卖源(美团/淘宝闪购/京东外卖);scene 写死 food(`getSceneMode` 默认改「外卖比价」)。点球 → `startTask` → Phase1/2 → 比价结果悬浮窗,这条链客户端本就齐(随引擎移植搬入),本次只收窄入口 + 写死场景 + 接通 server。电商放开时再加回 PDD/抖音 + scene 选择。
- **仿腾讯权限引导**:搬来的 `AccessibilityGuideActivity`/`PermissionGuide*`/`GuideOverlayService` 尚未接入口;当前是"跳系统设置页"最简版(SettingsScreen 权限卡 + 领券前检查已用)。体验后续可升级成仿腾讯浮层。
- **类名可选改名**`PriceBotService` 等用户不可见,保留无妨;想整洁可改 `ShaguaAccessibilityService`(牵动引用,不急)。
---
## 技术债(暂不处理,知情即可)
- **agent 接口公网裸奔**:不鉴权 = 任何人可调,烧 pricebot 的 LLM 算力。与美团 3 接口现状同级,记账后随 P1 一起补。
- **既有债(已记录在别处)**:美团 `referral-link``sid` 客户端可覆盖刷分佣;`logout` 占位不吊销 token`SMS_MOCK=true` 全站免密登录;`JWT_SECRET_KEY` 默认值可伪造 token。详见 [后端技术实现.md](../后端技术实现.md) 与 [api/](./api/)。
---
## P2 · DB 与 Alembic 治理(2026-05-29 新增)
### Alembic 多 head 风险 — 团队流程问题
**触发背景**:2026-05-29 用 PG 跑 `alembic upgrade head` 报 "Multiple head revisions are present"。
- 实习生的 `feat/postgres-migration-and-bootstrap-script` 分支基于 `4d444b7` (已合 PR #4 feedback) 创建,但新迁移 `ef96beb47b1e``down_revision` 挂在 `c8d9e0f1a2b3` (ad_reward),跟 PR #4 引入的 `d1e2f3a4b5c6` (feedback) 并行成两个 head
- **根因**:跨分支并行开发,PR 合并时没人检查 alembic 迁移链是否线性
**临时修复**:`alembic merge -m "merge pg_jsonb and feedback heads" ef96beb47b1e d1e2f3a4b5c6` 生成合并迁移 `f01db5d77dac_merge_pg_jsonb_and_feedback_heads.py`(已合进 main)。
**团队规约(待写进 CONTRIBUTING)**:
- 新建包含 alembic 迁移的 PR 前,在分支上跑 `alembic heads`,确认**只有一个 head**;有多个就先 rebase 自己的迁移文件的 `down_revision`
- review 别人含 alembic 迁移的 PR 时,同样检查 heads 数
- 合并 main 之后再跑一次 `alembic heads` 兜底——本次撞到就是 review 没拦住
### `init_postgres.py` 已知小 bug(2026-05-29 review)
`scripts/init_postgres.py` 是实习生为 PG 切换写的 bootstrap 脚本(建用户/库/写 .env/跑迁移),有 5 处可改进项:
1. **`if val:` 把空字符串当未设** → `PG_SUPER_PASS=""` 不能传(本机 trust 认证想跳过密码就只能给占位字符串)
2. **用户自定义 `APP_DB_PASS` 含 `@:/` 会破坏 URL** → 应 `urllib.parse.quote(db_pass, safe='')`(自动生成的 `secrets.token_urlsafe` 是 URL 安全字符,但用户传值不一定)
3. **subprocess 跑 alembic 没显式注 `DATABASE_URL`** → 父 shell 残留旧值会覆盖刚写入 .env 的新值,可能把表建到错的库
4. **强制 TCP localhost** → macOS Homebrew(默认 trust)和 Linux apt(默认 peer)的默认认证模式都不直接走 TCP,新机首次跑大概率失败
5. **承诺的 JSONB GIN 索引没真加** → commit message 和文档都说"PG 上可建 GIN 索引",但 `savings_record.dishes` 实际没加;要么补 `CREATE INDEX ... USING GIN (dishes)`,要么把"GIN"措辞从理由里删掉
短期不阻塞使用(熟手按预期参数能跑通),但累积起来会让新人踩坑。低优先级清理。
### `MEDIA_ROOT` 进程内 serve
`/media` 路径当前由 FastAPI StaticFiles 挂载 serve(头像/反馈截图)。生产建议改 nginx 直 serve `MEDIA_ROOT` 目录,绕过应用进程减少压力。一行 `location /media/ { alias /opt/shaguabijia-app-server/data/media/; }` 即可。
---
## 阶段 1 领券落地后的遗留(待清理,不阻塞跑通)
- **FloatingButton 死代码**:Running 面板已换成 ComposeView 承载 `CouponProgressPanel`(=复用 AgentFloat)。旧原生面板字段(`line1~4View`/`elapsedView`/`progressBarView`/`historyContainerView` 等)+ `updateStatus(StepStatus)` 里保留的那段更新逻辑 + `appendActionLine`/`markCurrentActionDone`/`markPlatformDone`/`addCancelButton` 全部 no-op,待删。
- **ComparingScreen / CompResultScreen 弃用**:领券改走悬浮窗,不再走全屏假动画。`Routes.COMPARING` / `COMP_RESULT` 成死路由,连同两个 Screen 文件待删(`CouponPromptDialog` 已抽到 `ui/coupon/`,删 ComparingScreen 不影响它)。
- **ApiClient 常量 unused**:`getBackendUrl()` 改用 `BuildConfig.BASE_URL` 后,`DEFAULT_BACKEND_URL`/`KEY_BACKEND_URL` 常量 unused,待清。
- **领券大数字语义**:悬浮窗左侧"已领券"大数字目前接 `progress.current`(正在领第几张),非"已成功数"。中间帧后端不回传已领明细,要精确需后端在 status.progress 加 `completed[]`(协议文档第四节已留扩展点)。
- **正式版 versionCode 必须 > 占坑版线上最高**:占坑版(`shaguabijia-android`)与正式版同包名 `com.jishisongfu.shaguabijia` 原地覆盖。正式版当前 `versionCode=1`,真机占坑版是 5 → 同签名降级被 Android 拒装,**正式版无法覆盖更新线上占坑版用户**。上线前 versionCode 要提到 > 线上占坑版最高值。
## 已解决
-**短信验证码接入极光(real 模式,2026-06-03)**:`/sms/send`+`/sms/login` 从 mock 升级为真实可用。`integrations/sms.py` 走**自定义验证码模式**(本服务 `secrets` 生成 6 位码 + 进程内存存储 + 极光 `/v1/messages` 只负责发 + 本地校验一次性/防爆破);鉴权**复用极光一键登录的 `JG_APP_KEY`/`JG_MASTER_SECRET`**(同一极光应用,**上线只需 `SMS_MOCK=false`**,无需额外凭证;`sign_id=31729`/`temp_id=1` 已审核就绪)。防刷四层(单号冷却 + 单号每日上限 + 单 IP `rate_limit` + 单码失败次数上限);`SmsError.status_code` 让发送失败按类型返回 429/503/400。**mock 保留"任意6位通过"**(不动其他 11 个测试文件的 `_login` helper);`tests/test_auth.py` 加 real 模式单测(极光协议 / 一次性 / 防爆破 / 余额回滚)。文档 [integrations/sms.md](../integrations/sms.md)。**遗留**:验证码进程内存,多 worker 需迁 DB/Redis(下方技术债)。
-**比价记录落库(server + client,2026-05-31)**:每次比价 done 后客户端上报、按 user 落库,作「我的比价记录」数据源 + 用户级画像沉淀。
- **server**(本仓):新表 `comparison_record`(独立于 `savings_record`;结构化列 + `items`/`comparison_results`/`skipped_dish_names`/`raw_payload` 四个 JSON(B) 列)+ 3 个**鉴权**端点 `POST /api/v1/compare/record`(`(user_id,trace_id)` 幂等 upsert,best/saved/is_source_best/status 服务端从 comparison_results 派生)/ `GET /api/v1/compare/records`(游标分页)/ `GET /records/{id}`(含 raw_payload);`models/comparison.py` + `repositories/comparison.py` + `schemas/compare_record.py` + `api/v1/compare_record.py` + 迁移 `comparison_record_table`(head `b2c3d4e5f6a7`)+ `tests/test_compare_record.py`(8 例全过)+ `docs/api/compare-record-*.md`
- **client**(android 仓):`PriceBotService.runTask()` 比价 done 后(`lastDoneParams!=null`,成功/引擎失败都报)用独立 IO 协程尽力上报;`Protocol.CompareRecordRequest.fromComparison()` 从 calibration+done.params 零翻译组装;`ApiClient.reportCompareRecord()` 走新建的 `authedClient`(复用 `AuthInterceptor`+`RefreshAuthenticator`,自动 Bearer+401 刷新)。领券不报(非价格对比,本期范围只外卖)。
- **未做**:UI(「我的比价记录」页)等原型;真机端到端待验。
-**领券链路接通**(阶段 1):首页「去领取」→ `CouponPromptDialog` → 权限检查 → `startCouponClaim` → 循环 `/api/v1/coupon/step`;Running 悬浮窗换皮(ComposeView 承载 `CouponProgressPanel`/AgentFloat,`OverlayLifecycleOwner` 撑 Compose)。
-**coupon/step 去鉴权**:去掉 `CurrentUser`,MVP 不鉴权(device_id 透传)。
-**ApiClient 改址**:`BuildConfig.BASE_URL` + `/api/v1/` 前缀。
-**SettingsScreen 权限卡**:5 项(无障碍/悬浮窗/录屏<API30/电池/自启动)真实检测 + 点击跳转 + 自启动无法检测态 + onResume 自动刷新 + 刷新按钮;逻辑抽到 `PermissionHelper`(HomeScreen 领券前检查复用)。
-**无障碍身份统一傻瓜比价**:服务名/图标继承 app;`accessibility_service_description`/前台通知/`GuideCopy`/引导 Activity 里的 "PriceBot" 全改「傻瓜比价」;app 图标换成完整 `logo1`(adaptive 居中+黄边,不裁切)。
-**GuideOverlayService 组件名 bug**:批量 sed 改包名误把无障碍服务 ComponentName 的 packageName 改成 `…shaguabijia.agent`(应是 applicationId `…shaguabijia`)→ 无障碍开启检测永远 false;改用 `PermissionHelper.isAccessibilityEnabled` 动态构造。
-**BuildConfig 包名**:`com.pricebot.app.BuildConfig` import 改本 app 包名。
-**SQLite → PostgreSQL 切换**(2026-05-29,PR #6 + alembic 合并迁移):新增 `scripts/init_postgres.py` 一键脚本、`psycopg[binary]>=3.1` 依赖、`savings_record.dishes` 列改 JSONB、连接池参数(`pool_size=10/overflow=20/recycle=3600`)。SQLite 仍可作开发兜底(只改 `DATABASE_URL`)。详见 [postgres-migration.md](../database/postgres-migration.md)。
-**外卖比价透传 2 端点**(2026-05-27,`feat(compare)`):`/api/v1/intent/recognize` + `/api/v1/price/step``compare.py`,纯 body 透传到 pricebot-backend,`PRICEBOT_COMPARE_TIMEOUT_SEC=60` 独立超时。电商 2 个待接(`compare.py` 加两行即可)。
-**用户资料 + 帮助反馈接口**(2026-05-28,PR #4):`PATCH /api/v1/user/profile` 改昵称、`POST /api/v1/user/avatar` 上传头像(魔数嗅探)、`DELETE /api/v1/user` 注销账号(软删+匿名化)、`POST /api/v1/feedback` 提交反馈(含截图);新建 `feedback` 表 + `core/media.py` + `/media` 静态服务。
-**美团 3 端点未配凭证降级**(2026-05-28,`feat(meituan)`):未配 `MT_CPS_APP_KEY``coupons`/`feed`/`referral-link` 直接返空(不再 502),解决新开发机首屏炸的问题。**坏副作用**:`/feed` 跟"已配但调用失败"路径在响应上无法区分(都是 200 + 空 items),靠 `/health``mt_cps_configured` 字段区分。
-**看广告 4 态 CTA + 本轮冷却**(2026-05-29,PR #5):`reward-status``round_count` + `cooldown_until` 字段,客户端 CTA 4 态(Normal/Loading/Capped/CoolingDown)+ 弹窗 limit note("本轮看完"/"今日看完")全由后端权威派生,跨设备/重装/杀进程都一致。**冷却仅 UX**,发奖不受影响。