| | |
| | | |
| | | class AddFriendsRequest(BaseModel): |
| | | """添加好友到忽略列表请求模型""" |
| | | |
| | | friends: List[str] |
| | | |
| | | |
| | | class RemoveFriendRequest(BaseModel): |
| | | """从忽略列表移除好友请求模型""" |
| | | |
| | | w_id: str |
| | | |
| | | |
| | | class IgnoreListResponse(BaseModel): |
| | | """忽略列表响应模型""" |
| | | |
| | | success: bool |
| | | message: str |
| | | data: Set[str] = None |
| | |
| | | async def get_ignore_list(): |
| | | """ |
| | | 获取当前的好友忽略列表 |
| | | |
| | | |
| | | Returns: |
| | | 忽略列表响应 |
| | | """ |
| | | try: |
| | | ignore_list = friend_ignore_service.get_ignore_list() |
| | | count = friend_ignore_service.get_ignore_list_count() |
| | | |
| | | |
| | | return IgnoreListResponse( |
| | | success=True, |
| | | message="获取忽略列表成功", |
| | | data=ignore_list, |
| | | count=count |
| | | success=True, message="获取忽略列表成功", data=ignore_list, count=count |
| | | ) |
| | | except Exception as e: |
| | | logger.error(f"获取忽略列表失败: {str(e)}") |
| | |
| | | async def add_friends_to_ignore_list(request: AddFriendsRequest): |
| | | """ |
| | | 添加好友到忽略列表 |
| | | |
| | | |
| | | Args: |
| | | request: 添加好友请求 |
| | | |
| | | |
| | | Returns: |
| | | 操作结果 |
| | | """ |
| | | try: |
| | | success = friend_ignore_service.add_friends_to_ignore_list(request.friends) |
| | | |
| | | |
| | | if success: |
| | | count = friend_ignore_service.get_ignore_list_count() |
| | | return IgnoreListResponse( |
| | | success=True, |
| | | message=f"成功添加 {len(request.friends)} 个好友到忽略列表", |
| | | count=count |
| | | count=count, |
| | | ) |
| | | else: |
| | | raise HTTPException(status_code=400, detail="添加好友到忽略列表失败") |
| | | |
| | | |
| | | except Exception as e: |
| | | logger.error(f"添加好友到忽略列表失败: {str(e)}") |
| | | raise HTTPException(status_code=500, detail=f"添加好友到忽略列表失败: {str(e)}") |
| | |
| | | async def remove_friend_from_ignore_list(request: RemoveFriendRequest): |
| | | """ |
| | | 从忽略列表中移除好友 |
| | | |
| | | |
| | | Args: |
| | | request: 移除好友请求 |
| | | |
| | | |
| | | Returns: |
| | | 操作结果 |
| | | """ |
| | | try: |
| | | success = friend_ignore_service.remove_friend_from_ignore_list(request.w_id) |
| | | |
| | | |
| | | if success: |
| | | count = friend_ignore_service.get_ignore_list_count() |
| | | return IgnoreListResponse( |
| | | success=True, |
| | | message=f"成功从忽略列表中移除好友: {request.w_id}", |
| | | count=count |
| | | count=count, |
| | | ) |
| | | else: |
| | | raise HTTPException(status_code=400, detail="从忽略列表移除好友失败") |
| | | |
| | | |
| | | except Exception as e: |
| | | logger.error(f"从忽略列表移除好友失败: {str(e)}") |
| | | raise HTTPException(status_code=500, detail=f"从忽略列表移除好友失败: {str(e)}") |
| | |
| | | async def clear_ignore_list(): |
| | | """ |
| | | 清空忽略列表 |
| | | |
| | | |
| | | Returns: |
| | | 操作结果 |
| | | """ |
| | | try: |
| | | success = friend_ignore_service.clear_ignore_list() |
| | | |
| | | |
| | | if success: |
| | | return IgnoreListResponse( |
| | | success=True, |
| | | message="成功清空忽略列表", |
| | | count=0 |
| | | ) |
| | | return IgnoreListResponse(success=True, message="成功清空忽略列表", count=0) |
| | | else: |
| | | raise HTTPException(status_code=400, detail="清空忽略列表失败") |
| | | |
| | | |
| | | except Exception as e: |
| | | logger.error(f"清空忽略列表失败: {str(e)}") |
| | | raise HTTPException(status_code=500, detail=f"清空忽略列表失败: {str(e)}") |
| | |
| | | async def sync_contacts_and_rebuild_ignore_list(): |
| | | """ |
| | | 重新同步联系人并重建忽略列表 |
| | | |
| | | |
| | | Returns: |
| | | 操作结果 |
| | | """ |
| | | try: |
| | | if not settings.ecloud_w_id: |
| | | raise HTTPException(status_code=400, detail="未配置ecloud_w_id") |
| | | |
| | | |
| | | success = contact_sync_service.sync_contacts_on_startup(settings.ecloud_w_id) |
| | | |
| | | |
| | | if success: |
| | | count = friend_ignore_service.get_ignore_list_count() |
| | | return IgnoreListResponse( |
| | | success=True, |
| | | message="联系人同步完成,忽略列表已重建", |
| | | count=count |
| | | success=True, message="联系人同步完成,忽略列表已重建", count=count |
| | | ) |
| | | else: |
| | | raise HTTPException(status_code=400, detail="联系人同步失败") |
| | | |
| | | |
| | | except Exception as e: |
| | | logger.error(f"联系人同步失败: {str(e)}") |
| | | raise HTTPException(status_code=500, detail=f"联系人同步失败: {str(e)}") |
| | |
| | | return { |
| | | "success": True, |
| | | "data": status_info, |
| | | "message": f"w_id {w_id} 状态检查完成" |
| | | "message": f"w_id {w_id} 状态检查完成", |
| | | } |
| | | |
| | | except Exception as e: |
| | |
| | | "success": True, |
| | | "data": whitelist, |
| | | "count": len(whitelist), |
| | | "message": "获取白名单成功" |
| | | "message": "获取白名单成功", |
| | | } |
| | | |
| | | except Exception as e: |
| | |
| | | "ignore_enabled": settings.friend_ignore_enabled, |
| | | "whitelist": settings.friend_ignore_whitelist, |
| | | "whitelist_count": len(settings.friend_ignore_whitelist), |
| | | "ignore_list_count": friend_ignore_service.get_ignore_list_count() |
| | | "ignore_list_count": friend_ignore_service.get_ignore_list_count(), |
| | | }, |
| | | "message": "获取配置信息成功" |
| | | "message": "获取配置信息成功", |
| | | } |
| | | |
| | | except Exception as e: |
| | |
| | | """ |
| | | 联系人信息模型 |
| | | """ |
| | | |
| | | from sqlalchemy import Column, String, Integer, DateTime, Text |
| | | from sqlalchemy.sql import func |
| | | from .database import Base |
| | |
| | | |
| | | class Contact(Base): |
| | | """联系人信息表""" |
| | | |
| | | __tablename__ = "contacts" |
| | | |
| | | |
| | | id = Column(Integer, primary_key=True, index=True, autoincrement=True) |
| | | wc_id = Column(String(100), unique=True, index=True, nullable=False, comment="微信ID/群ID") |
| | | wc_id = Column( |
| | | String(100), unique=True, index=True, nullable=False, comment="微信ID/群ID" |
| | | ) |
| | | user_name = Column(String(100), nullable=True, comment="微信用户名") |
| | | nick_name = Column(String(100), nullable=True, comment="昵称") |
| | | remark = Column(String(100), nullable=True, comment="备注") |
| | |
| | | small_head = Column(String(500), nullable=True, comment="小头像URL") |
| | | label_list = Column(Text, nullable=True, comment="标签列表") |
| | | v1 = Column(String(200), nullable=True, comment="v1数据") |
| | | |
| | | work_wc_id = Column(String(100), nullable=True, comment="企业微信id") |
| | | created_at = Column(DateTime, default=func.now(), comment="创建时间") |
| | | updated_at = Column(DateTime, default=func.now(), onupdate=func.now(), comment="更新时间") |
| | | |
| | | updated_at = Column( |
| | | DateTime, default=func.now(), onupdate=func.now(), comment="更新时间" |
| | | ) |
| | | |
| | | def __repr__(self): |
| | | return f"<Contact(wc_id='{self.wc_id}', nick_name='{self.nick_name}')>" |
| | |
| | | existing_contact.small_head = contact_data.get("smallHead") |
| | | existing_contact.label_list = contact_data.get("labelList") |
| | | existing_contact.v1 = contact_data.get("v1") |
| | | # 只有当新的workWcId不为空时才更新 |
| | | if contact_data.get("workWcId"): |
| | | existing_contact.work_wc_id = contact_data.get("workWcId") |
| | | updated_count += 1 |
| | | logger.debug(f"更新联系人信息: wc_id={wc_id}, nick_name={contact_data.get('nickName')}") |
| | | else: |
| | |
| | | small_head=contact_data.get("smallHead"), |
| | | label_list=contact_data.get("labelList"), |
| | | v1=contact_data.get("v1"), |
| | | work_wc_id=contact_data.get("workWcId"), # 添加企业微信ID |
| | | ) |
| | | db.add(new_contact) |
| | | saved_count += 1 |
| | |
| | | # 提交事务 |
| | | db.commit() |
| | | logger.info(f"联系人信息保存完成: 新增={saved_count}, 更新={updated_count}") |
| | | |
| | | # 同步联系人后更新Redis忽略列表 |
| | | self._update_redis_ignore_list(db) |
| | | |
| | | return True |
| | | |
| | | except Exception as e: |
| | |
| | | db.rollback() |
| | | return False |
| | | |
| | | def _update_redis_ignore_list(self, db: Session) -> bool: |
| | | """ |
| | | 同步联系人后更新Redis忽略列表 |
| | | 将数据库中contacts表中work_wc_id不为空的记录的work_wc_id作为wc_id添加到Redis忽略列表 |
| | | |
| | | Args: |
| | | db: 数据库会话 |
| | | |
| | | Returns: |
| | | 更新成功返回True,失败返回False |
| | | """ |
| | | try: |
| | | # 查询所有work_wc_id不为空且不为空字符串的联系人 |
| | | contacts_with_work_wc_id = db.query(Contact).filter( |
| | | Contact.work_wc_id.isnot(None) & (Contact.work_wc_id != "") |
| | | ).all() |
| | | |
| | | if not contacts_with_work_wc_id: |
| | | logger.info("没有找到work_wc_id不为空的联系人") |
| | | return True |
| | | |
| | | # 提取所有work_wc_id |
| | | work_wc_ids = [contact.work_wc_id for contact in contacts_with_work_wc_id] |
| | | logger.info(f"找到 {len(work_wc_ids)} 个work_wc_id不为空的联系人") |
| | | |
| | | # 将这些work_wc_id添加到Redis忽略列表 |
| | | success = friend_ignore_service.add_friends_to_ignore_list(work_wc_ids) |
| | | |
| | | if success: |
| | | logger.info(f"成功将 {len(work_wc_ids)} 个work_wc_id添加到Redis忽略列表") |
| | | else: |
| | | logger.error("添加work_wc_id到Redis忽略列表失败") |
| | | |
| | | return success |
| | | |
| | | except Exception as e: |
| | | logger.error(f"更新Redis忽略列表异常: error={str(e)}") |
| | | return False |
| | | |
| | | |
| | | # 全局联系人同步服务实例 |
| | | contact_sync_service = ContactSyncService() |
| | |
| | | """ |
| | | try: |
| | | with next(get_db()) as db: |
| | | contact = db.query(Contact).filter(Contact.nick_name == nickname).first() |
| | | contact = ( |
| | | db.query(Contact).filter(Contact.nick_name == nickname).first() |
| | | ) |
| | | if contact: |
| | | return contact.wc_id |
| | | wc_id = contact.wc_id |
| | | if contact.work_wc_id: |
| | | wc_id += f",{contact.work_wc_id}" |
| | | return wc_id |
| | | else: |
| | | logger.warning(f"未找到昵称为 '{nickname}' 的联系人") |
| | | return None |
| | |
| | | logger.info("好友列表为空,无需添加到忽略列表") |
| | | return True |
| | | |
| | | # 清空现有的忽略列表 |
| | | redis_queue.redis_client.delete(self.ignore_list_key) |
| | | |
| | | # 批量添加好友w_id到忽略列表 |
| | | # 批量添加好友w_id到忽略列表(不清空现有列表) |
| | | redis_queue.redis_client.sadd(self.ignore_list_key, *friends) |
| | | |
| | | |
| | | logger.info(f"已将 {len(friends)} 个好友添加到忽略列表") |
| | | return True |
| | | |
| | |
| | | |
| | | # 检查是否在白名单中(通过昵称) |
| | | whitelist_wids = self._get_whitelist_wids() |
| | | if w_id in whitelist_wids: |
| | | |
| | | if any(w_id in wids for wids in whitelist_wids): |
| | | logger.info(f"w_id在白名单中,不忽略消息: w_id={w_id}") |
| | | return False |
| | | |
| | | # 检查是否在忽略列表中 |
| | | is_in_ignore_list = redis_queue.redis_client.sismember(self.ignore_list_key, w_id) |
| | | is_in_ignore_list = redis_queue.redis_client.sismember( |
| | | self.ignore_list_key, w_id |
| | | ) |
| | | |
| | | if is_in_ignore_list: |
| | | # 如果在忽略列表中,检查是否在测试群组中 |
| | | if group_id and silence_service.is_test_group(group_id): |
| | | logger.info(f"测试群组中的好友消息不被忽略: w_id={w_id}, group_id={group_id}") |
| | | logger.info( |
| | | f"测试群组中的好友消息不被忽略: w_id={w_id}, group_id={group_id}" |
| | | ) |
| | | return False |
| | | |
| | | |
| | | logger.info(f"w_id在忽略列表中,忽略消息: w_id={w_id}") |
| | | |
| | | |
| | | return is_in_ignore_list |
| | | |
| | | except Exception as e: |
| | |
| | | "in_ignore_list": False, |
| | | "final_ignored": False, |
| | | "reason": "", |
| | | "whitelist_nicknames": settings.friend_ignore_whitelist |
| | | "whitelist_nicknames": settings.friend_ignore_whitelist, |
| | | } |
| | | |
| | | if not settings.friend_ignore_enabled: |
| | |
| | | info["reason"] = "在白名单中,不会被忽略" |
| | | return info |
| | | |
| | | info["in_ignore_list"] = redis_queue.redis_client.sismember(self.ignore_list_key, w_id) |
| | | info["in_ignore_list"] = redis_queue.redis_client.sismember( |
| | | self.ignore_list_key, w_id |
| | | ) |
| | | |
| | | if info["in_ignore_list"]: |
| | | info["final_ignored"] = True |
| | |
| | | |
| | | except Exception as e: |
| | | logger.error(f"获取忽略状态信息异常: w_id={w_id}, error={str(e)}") |
| | | return { |
| | | "w_id": w_id, |
| | | "error": str(e), |
| | | "final_ignored": False |
| | | } |
| | | return {"w_id": w_id, "error": str(e), "final_ignored": False} |
| | | |
| | | |
| | | # 全局好友忽略服务实例 |
| | |
| | | # 异常情况下返回原始content |
| | | return callback_data.get("data", {}).get("content", "") |
| | | |
| | | def parse_at_mentions(self, ai_answer: str) -> Tuple[str, List[str]]: |
| | | def parse_at_mentions(self, ai_answer: str, from_user: str) -> Tuple[str, List[str]]: |
| | | """ |
| | | 解析AI回复中的@字符,提取客服名称 |
| | | |
| | |
| | | db.query(Contact).filter(Contact.nick_name == name).first() |
| | | ) |
| | | if contact: |
| | | at_wc_ids.append(contact.wc_id) |
| | | logger.info( |
| | | f"找到客服联系人: name={name}, wc_id={contact.wc_id}" |
| | | ) |
| | | # 如果from_user包含@openim表示是企业微信,使用work_wc_id |
| | | if "@openim" in from_user: |
| | | at_wc_ids.append(contact.work_wc_id) |
| | | logger.info( |
| | | f"找到客服联系人-微信: name={name}, wc_id={contact.work_wc_id}" |
| | | ) |
| | | else: |
| | | at_wc_ids.append(contact.wc_id) |
| | | logger.info( |
| | | f"找到客服联系人-企业微信: name={name}, wc_id={contact.wc_id}" |
| | | ) |
| | | else: |
| | | logger.warning(f"未找到客服联系人: name={name}") |
| | | |
| | |
| | | return False |
| | | |
| | | # 3.2 获取群组中发言次数最多的用户昵称 |
| | | most_active_nickname = ( |
| | | group_stats_service.get_most_active_user_nickname(from_group) |
| | | ) |
| | | logger.info( |
| | | f"群组最活跃用户昵称: group={from_group}, nickname={most_active_nickname}" |
| | | ) |
| | | # most_active_nickname = ( |
| | | # group_stats_service.get_most_active_user_nickname(from_group) |
| | | # ) |
| | | # logger.info( |
| | | # f"群组最活跃用户昵称: group={from_group}, nickname={most_active_nickname}" |
| | | # ) |
| | | # 获取默认客服名称 |
| | | nickname = settings.customer_service_default_name |
| | | |
| | | # 3.3 获取用户在当前群组的conversation_id |
| | | conversation_id = redis_queue.get_conversation_id(from_user, from_group) |
| | |
| | | query=content, |
| | | user=from_user, |
| | | conversation_id=conversation_id, |
| | | nick_name=most_active_nickname, |
| | | nick_name=nickname, |
| | | ) |
| | | |
| | | if silence_service.is_silence_active(from_group): |
| | |
| | | success = False |
| | | if ai_answer: |
| | | # 解析AI回复中的@字符 |
| | | processed_answer, at_wc_ids = self.parse_at_mentions(ai_answer) |
| | | processed_answer, at_wc_ids = self.parse_at_mentions(ai_answer, from_user) |
| | | # 判断AI回复是否是结束字符串 |
| | | is_end_str = self.is_end_str(ai_answer) |
| | | # 发送消息,最多重试3次 |
| | |
| | | ) |
| | | else: |
| | | # 如果该群组静默模式未激活,激活静默模式 |
| | | silence_service.activate_silence_mode(from_group) |
| | | logger.info( |
| | | flag = silence_service.activate_silence_mode(from_group) |
| | | if flag: |
| | | logger.info( |
| | | f"AI回复@客服,群组静默模式已激活: fromUser={from_user}, fromGroup={from_group}" |
| | | ) |
| | | ) |
| | | else: |
| | | logger.info( |
| | | f"AI回复@客服,未激活静默模式: fromUser={from_user}, fromGroup={from_group}" |
| | | ) |
| | | success = True |
| | | break |
| | | else: |
| | |
| | | # 检查是否为测试群组,如果是则不激活静默模式 |
| | | if self.is_test_group(group_id): |
| | | logger.info(f"测试群组不激活静默模式: group_id={group_id}") |
| | | return True |
| | | return False |
| | | |
| | | if not settings.silence_mode_enabled: |
| | | logger.debug("静默模式功能已禁用") |
| | |
| | | 2025-08-26 15:35:24 | INFO | __main__:<module>:132 - 启动E云管家-DifyAI对接服务 |
| | | 2025-08-26 15:55:13 | INFO | __main__:<module>:132 - 启动E云管家-DifyAI对接服务 |
| | | 2025-08-26 16:08:41 | INFO | __main__:<module>:132 - 启动E云管家-DifyAI对接服务 |
| | | 2025-08-27 17:06:10 | INFO | __main__:<module>:132 - 启动E云管家-DifyAI对接服务 |
| | | 2025-08-27 17:15:21 | INFO | __main__:<module>:132 - 启动E云管家-DifyAI对接服务 |
| | | 2025-08-27 17:20:38 | INFO | __main__:<module>:132 - 启动E云管家-DifyAI对接服务 |
| | | 2025-08-27 17:20:55 | INFO | __main__:<module>:132 - 启动E云管家-DifyAI对接服务 |
| | | 2025-08-27 17:24:47 | INFO | __main__:<module>:132 - 启动E云管家-DifyAI对接服务 |
| | | 2025-08-27 17:25:26 | INFO | __main__:<module>:132 - 启动E云管家-DifyAI对接服务 |
| | | 2025-08-27 17:37:52 | INFO | __main__:<module>:132 - 启动E云管家-DifyAI对接服务 |
| | | 2025-08-27 17:42:02 | INFO | __main__:<module>:132 - 启动E云管家-DifyAI对接服务 |
| | | 2025-08-27 18:01:43 | INFO | __main__:<module>:132 - 启动E云管家-DifyAI对接服务 |
| | | 2025-08-27 18:11:34 | INFO | __main__:<module>:132 - 启动E云管家-DifyAI对接服务 |