From 4cf5a0508801128da6537027ba52421123dea86d Mon Sep 17 00:00:00 2001 From: liuwei Date: Wed, 15 Apr 2026 09:28:44 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=99=E6=AC=A1=E5=AE=9E=E9=99=85=E6=94=B9?= =?UTF-8?q?=E7=9A=84=E6=98=AF=E6=8C=89=E9=92=AE=E8=83=8C=E5=90=8E=E7=9A=84?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E9=80=BB=E8=BE=91=EF=BC=8C=E4=B8=8D=E6=98=AF?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E6=96=87=E6=A1=88=E6=9C=AC=E8=BA=AB=E3=80=82?= =?UTF-8?q?=E7=8E=B0=E5=9C=A8=20/contacts/api/update=20=E8=A7=A6=E5=8F=91?= =?UTF-8?q?=E5=90=8E=E4=BC=9A=EF=BC=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 个人联系人、公众号:已存在就跳过,不存在才写入。 群:不再删库重建,已有群保留;新群会写入。 群成员:已存在就跳过,不存在就写入。 如果成员这次不在群里了,会把 db/contacts_db.py 里的 status 标成 2,前端会显示“已退群”。 如果整个群查不到了,也不再删除群资料,只把该群历史成员标记为“已退群”。 改动在: robot.py db/contacts_db.py 我还做了语法校验,py_compile 通过。需要的话我也可以继续帮你把“更新通讯录”按钮的成功提示改成更明确,比如“已完成增量同步,未删除历史数据”。 --- db/contacts_db.py | 90 ++++++++++++++++++++++++++++++++++++++--------- robot.py | 89 +++++++++++++++++++++++++++------------------- 2 files changed, 126 insertions(+), 53 deletions(-) diff --git a/db/contacts_db.py b/db/contacts_db.py index a7a5811..3e5a7d4 100644 --- a/db/contacts_db.py +++ b/db/contacts_db.py @@ -113,12 +113,13 @@ class ContactsDBOperator(BaseDBOperator): self.LOG.error(f"创建微信联系人表或群成员表失败: {e}") raise - def save_contacts(self, contacts_data: List[Dict], contact_type: str) -> bool: + def save_contacts(self, contacts_data: List[Dict], contact_type: str, only_insert: bool = False) -> bool: """保存联系人信息到数据库 Args: contacts_data: 联系人数据列表 contact_type: 联系人类型,可选值:'friends', 'chatrooms', 'ghs' + only_insert: 为True时仅写入不存在的联系人,已存在则跳过 Returns: bool: 是否成功保存 @@ -165,14 +166,18 @@ class ContactsDBOperator(BaseDBOperator): placeholders = ', '.join(['%s'] * len(data)) values = tuple(data.values()) - # 使用INSERT ... ON DUPLICATE KEY UPDATE语法 - update_clause = ', '.join([f"{k}=VALUES({k})" for k in data.keys() if k != 'user_name']) - - sql = f""" - INSERT INTO t_wechat_contacts ({fields}) - VALUES ({placeholders}) - ON DUPLICATE KEY UPDATE {update_clause} - """ + if only_insert: + sql = f""" + INSERT IGNORE INTO t_wechat_contacts ({fields}) + VALUES ({placeholders}) + """ + else: + update_clause = ', '.join([f"{k}=VALUES({k})" for k in data.keys() if k != 'user_name']) + sql = f""" + INSERT INTO t_wechat_contacts ({fields}) + VALUES ({placeholders}) + ON DUPLICATE KEY UPDATE {update_clause} + """ self.execute_update(sql, values) @@ -315,7 +320,7 @@ class ContactsDBOperator(BaseDBOperator): self.LOG.error(f"获取所有联系人信息失败: {e}") return {} - def save_chatroom_member_simple(self, chatroom_id: str, member_details: List[Dict]) -> bool: + def save_chatroom_member_simple(self, chatroom_id: str, member_details: List[Dict], only_insert: bool = False) -> bool: """ 保存群成员简要信息到数据库,兼容不同数据结构 Args: @@ -367,13 +372,18 @@ class ContactsDBOperator(BaseDBOperator): placeholders = ', '.join(['%s'] * len(data)) values = tuple(data.values()) - update_clause = ', '.join([f"{k}=VALUES({k})" for k in data.keys() if k not in ('chatroom_id', 'wxid')]) - - sql = f""" - INSERT INTO t_chatroom_member ({fields}) - VALUES ({placeholders}) - ON DUPLICATE KEY UPDATE {update_clause} - """ + if only_insert: + sql = f""" + INSERT IGNORE INTO t_chatroom_member ({fields}) + VALUES ({placeholders}) + """ + else: + update_clause = ', '.join([f"{k}=VALUES({k})" for k in data.keys() if k not in ('chatroom_id', 'wxid')]) + sql = f""" + INSERT INTO t_chatroom_member ({fields}) + VALUES ({placeholders}) + ON DUPLICATE KEY UPDATE {update_clause} + """ self.execute_update(sql, values) @@ -567,6 +577,52 @@ class ContactsDBOperator(BaseDBOperator): self.LOG.error(f"更新群聊{chatroom_id}信息失败: {e}") return False + def mark_chatroom_members_left(self, chatroom_id: str, active_member_wxids: Optional[List[str]] = None) -> bool: + """将不在当前群成员列表中的用户标记为已退群。""" + try: + active_member_wxids = [wxid for wxid in (active_member_wxids or []) if wxid] + if active_member_wxids: + placeholders = ', '.join(['%s'] * len(active_member_wxids)) + sql = f""" + UPDATE t_chatroom_member + SET status = 2 + WHERE chatroom_id = %s AND wxid NOT IN ({placeholders}) + """ + params = (chatroom_id, *active_member_wxids) + else: + sql = """ + UPDATE t_chatroom_member + SET status = 2 + WHERE chatroom_id = %s + """ + params = (chatroom_id,) + + self.execute_update(sql, params) + self.LOG.info(f"已将群 {chatroom_id} 中缺失成员标记为已退群") + return True + except Exception as e: + self.LOG.error(f"标记群{chatroom_id}成员退群失败: {e}") + return False + + def mark_chatroom_members_active(self, chatroom_id: str, active_member_wxids: List[str]) -> bool: + """将当前仍在群里的成员标记为在群。""" + try: + active_member_wxids = [wxid for wxid in (active_member_wxids or []) if wxid] + if not active_member_wxids: + return True + + placeholders = ', '.join(['%s'] * len(active_member_wxids)) + sql = f""" + UPDATE t_chatroom_member + SET status = 1 + WHERE chatroom_id = %s AND wxid IN ({placeholders}) + """ + self.execute_update(sql, (chatroom_id, *active_member_wxids)) + return True + except Exception as e: + self.LOG.error(f"恢复群{chatroom_id}成员在群状态失败: {e}") + return False + def delete_chatroom_info(self, chatroom_id: str) -> bool: """删除群信息""" try: diff --git a/robot.py b/robot.py index 0f98dfb..881edbf 100644 --- a/robot.py +++ b/robot.py @@ -630,66 +630,83 @@ class Robot: async def refresh_contacts_db(self): """刷新联系人信息""" - # 获取用户所有的联系人,并保存到数据库 self.LOG.info("开始刷新联系人信息") - # 删除所有的联系人信息 - self.contacts_db.delete_all_contacts() - self.LOG.debug("已删除所有的联系人信息") contacts = await self.ipad_bot.get_contract_list() self.LOG.debug(f"获取到的联系人:{contacts}") - # 获取联系人详细信息,get_contract_detail每次可以获取20个 - # 每次获取20个,需要循环获取 - # 将联系人列表分成每组20个 batch_size = 20 + discovered_groups = set() for i in range(0, len(contacts), batch_size): - # 获取当前批次的联系人 batch_contacts = contacts[i:i + batch_size] - # 获取这批联系人的详细信息 contact_info = await self.ipad_bot.get_contract_detail(batch_contacts) self.LOG.debug(f"获取到的联系人详细信息数量:{len(contact_info)}") - self.contacts_db.save_contacts(contact_info, "friends") + friend_contacts = [] + official_contacts = [] + + for contact in contact_info: + user_name = contact.get("UserName") + if isinstance(user_name, dict): + user_name = user_name.get("string", "") + user_name = user_name or "" + + if not user_name: + continue + + if user_name.endswith("@chatroom"): + discovered_groups.add(user_name) + if not self.contacts_db.get_chatroom_info(user_name): + self.contacts_db.save_chatroom_info(contact) + continue + + if user_name.startswith("gh_"): + official_contacts.append(contact) + else: + friend_contacts.append(contact) + + if friend_contacts: + self.contacts_db.save_contacts(friend_contacts, "friends", only_insert=True) + if official_contacts: + self.contacts_db.save_contacts(official_contacts, "ghs", only_insert=True) - # 获取群聊列表 groups = self.contacts_db.get_chatroom_list() - # 调用接口完成群成员信息获取与保存逻辑 for group in groups: - # 调用接口获取群成员信息 group_id = group["chatroom_id"] + discovered_groups.add(group_id) chatroom_info = await self.ipad_bot.get_chatroom_info(group_id) self.LOG.debug(f"获取到的群成员信息:{chatroom_info}") if chatroom_info.get("UserName", ""): - # 保存群信息到数据库 - self.contacts_db.save_chatroom_info(chatroom_info) members = await self.ipad_bot.get_chatroom_member_list(group_id) - # 保存群成员信息 if members: - # 清除所有的成员信息, 重新写入。 - self.contacts_db.delete_chatroom_members_info(group_id) - # 兼容逻辑已放到save_chatroom_member_simple内部 - self.contacts_db.save_chatroom_member_simple(group_id, members) - self.LOG.info(f"member_list: {members}") - # 更新联系人缓存 + active_member_wxids = [] for member in members: wxid = member.get("UserName", "") - nick_name = member.get("NickName", "") - displayName = member.get("DisplayName", "") - small_head_img_url = member.get("SmallHeadImgUrl", "") - # 如果displayName不为空,使用displayName - if displayName: - nick_name = displayName + if isinstance(wxid, dict): + wxid = wxid.get("string", "") if wxid: - self.allContacts[wxid] = nick_name + active_member_wxids.append(wxid) - self.head_images[wxid] = small_head_img_url - - self.LOG.info(f"已更新群 {group_id} 的成员信息") + self.contacts_db.mark_chatroom_members_active(group_id, active_member_wxids) + self.contacts_db.mark_chatroom_members_left(group_id, active_member_wxids) + self.contacts_db.save_chatroom_member_simple(group_id, members, only_insert=True) + self.LOG.info(f"已增量同步群 {group_id} 的成员信息") + else: + self.contacts_db.mark_chatroom_members_left(group_id, []) + self.LOG.warning(f"群 {group_id} 当前未获取到成员列表,已将历史成员标记为已退群") else: - self.LOG.error(f"获取群 {group_id} 信息失败,证明用户无该群信息,删除群的相关资料。") - # 删除群数据库中的群信息 - self.contacts_db.delete_chatroom_all_info(group_id) - self.allContacts.pop(group_id) + self.LOG.warning(f"获取群 {group_id} 信息失败,保留群资料并将成员标记为已退群。") + self.contacts_db.mark_chatroom_members_left(group_id, []) + + for group_id in discovered_groups: + if not self.contacts_db.get_chatroom_info(group_id): + chatroom_info = await self.ipad_bot.get_chatroom_info(group_id) + if chatroom_info.get("UserName", ""): + self.contacts_db.save_chatroom_info(chatroom_info) + members = await self.ipad_bot.get_chatroom_member_list(group_id) + if members: + self.contacts_db.save_chatroom_member_simple(group_id, members, only_insert=True) friends = await self.ipad_bot.get_contract_list() + self.allContacts = self.get_all_contacts() + self.head_images = self.get_all_head_images() self.all_chatroom_members = self.contacts_db.get_chatroom_member_list_name_all() self.contact_manager.set_contacts(self.allContacts, friends, self.head_images, self.all_chatroom_members)