From 239868531f1f5d572c0263cbbfd83bf11d05edc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Mon, 10 Aug 2026 13:57:41 +0800 Subject: [PATCH] =?UTF-8?q?docs(spec):=20=E4=BF=AE=E5=A4=8D=E7=BE=A4?= =?UTF-8?q?=E6=8A=93=E5=8F=96(friend=5Flist/es+friendTypeYunke=3D2,?= =?UTF-8?q?=E6=9B=BF=E6=8D=A2=20getChatroomList)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude --- .../2026-08-10-yunke-fix-group-scraper.md | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-10-yunke-fix-group-scraper.md diff --git a/docs/superpowers/specs/2026-08-10-yunke-fix-group-scraper.md b/docs/superpowers/specs/2026-08-10-yunke-fix-group-scraper.md new file mode 100644 index 0000000..0262be6 --- /dev/null +++ b/docs/superpowers/specs/2026-08-10-yunke-fix-group-scraper.md @@ -0,0 +1,52 @@ +# 修复群抓取:friend_list/es + friendTypeYunke=2 + +> 日期:2026-08-10|分支:`feat/yunke-data-mirror`|scraper fix(后端) +> 云客真机核实(2026-08-10 Playwright):群数据 API = `POST /wechatweb/wxFriends/friend_list/es` body `{friendTypeYunke:"2", wechatId, ymdStart, ymdEnd, pageNum, pageSize, salesname}`——与好友共用端点,仅 friendTypeYunke 不同(1=好友,2=群)。 + +## 根因 + +`scrape_wechat_groups` 用 `POST /wechatweb/chatroom/getChatroomList` + body `{pageNum,pageSize}`——**端点错 + 缺 friendTypeYunke + 缺 wechatId**。云客返回 0(smoke.db chat_group 0 行)。 + +## 修复(spec + plan) + +### 改 `app/scraper/wechat.py` + +1. **`scrape_wechat_groups`**:改用 `FRIEND_LIST_PATH`(`/wechatweb/wxFriends/friend_list/es`)+ `_parse_friend_total` + `_parse_friend_rows`(复用好友的分页/解析,同端点同信封)+ 新 body builder `_build_group_body`(加 `friendTypeYunke:"2"`)。 +2. **`_build_group_body(page_num, page_size)`**:返回 `{pageNum, pageSize, friendTypeYunke: "2"}`(最小 body;与好友 body builder 同模式 + friendTypeYunke;若测试返回 0 再加 ymdStart/ymdEnd/salesname)。 +3. **`_normalize_chat_group`**:映射 friend_list/es 字段(替代 getChatroomList 字段): + +| ChatGroup 字段 | friend_list/es 来源 | +|---|---| +| room_id | `friendWxId` (e.g. `51983915836@chatroom`) | +| room_name | `friendWxNickname` | +| owner_account_id | `wxId` (staff account that has this group) | +| owner_account_nickname | `ownerName` | +| member_count | `friendNum` | +| head_image_url | `friendHeadImageUrl` | +| message_count | `messageCount` | +| own_message_count | `sendNum` | +| last_chat_time | `lastChatTime` (ms-epoch) | +| owner_user_id | `userId` | +| owner_user_phone | `phone` or `wechatPhone` | +| version | `version` | +| member_ids / member_ids_list | None / [](friend_list/es 不含 memberIds;GroupMember 暂不创建) | +| raw_json | 整条 record | + +4. **删除旧 getChatroomList 代码**:`CHATROOM_LIST_PATH`、`_parse_chatroom_total`、`_parse_chatroom_rows`、`_build_chatroom_body`。 +5. `sync_wechat_groups`(orchestrator)不改(仍调 `scrape_wechat_groups`;签名不变)。 + +### 测试 +```bash +cd yunke-server && DB_URL="sqlite+aiosqlite:///./smoke.db" .venv/Scripts/python.exe -m app.sync.cli sync --entity groups +``` +Expected: smoke.db `chat_group` > 0。若 0(body 不全),给 `_build_group_body` 加 `ymdStart:"0000-00-00"`/`ymdEnd:""`/`salesname:""`/按员工 `wechatId` 遍历。 + +### 验收 +- `pytest -q` 全绿(不破坏现有测试;group 相关测试如 `test_scraper_wechat` 或 `test_sync` 需更新 mock 响应——若有)。 +- smoke.db `chat_group` > 0(实跑后)。 +- `GET /api/groups` 返回群数据(GroupList 有数据)。 + +### 风险 +- **friend_list/es 无 wechatId 可能返回 0**(云客页面带 wechatId)。测试确认;若 0,需按员工 wxId 遍历(类似 sync_wechat_accounts → per-account friend_list/es)。 +- **GroupMember**:friend_list/es 不含 memberIds → GroupMember 表暂不填(群列表可用;成员详情后续)。 +- **测试 mock 更新**:现有 group 测试若 mock getChatroomList 响应,需改 mock 为 friend_list/es 响应。