ForcePilot/backend/package/yuxi/channels/adapters/feishu/directory.py
Kris 1f78c44b03 refactor: 整理并清理项目中的冗余代码与格式问题
这是一个批量整理提交,包含以下主要改动:
1.  删除多处冗余的空行和未使用的导入
2.  修复文件末尾缺少换行符的问题
3.  调整部分模块的导入顺序与代码排版
4.  修复部分配置默认值与策略逻辑
5.  新增多个功能模块与辅助工具
6.  完善异常处理与日志记录
7.  修复速率限制、消息缓存、权限校验等逻辑bug
8.  废弃部分旧有API与配置项并添加警告提示
2026-05-12 14:51:53 +08:00

166 lines
5.5 KiB
Python

from __future__ import annotations
import asyncio
import time
from typing import Any
PAGE_SIZE_DEFAULT = 100
PAGE_SIZE_MAX = 200
CACHE_TTL_USERS_S = 300
CACHE_TTL_DEPARTMENTS_S = 600
class FeishuDirectoryClient:
def __init__(self, api_client: Any, page_size: int = PAGE_SIZE_DEFAULT):
self._api = api_client
self._page_size = min(page_size, PAGE_SIZE_MAX)
self._user_cache: dict[str, tuple[dict, float]] = {}
self._user_list_cache: list[dict] = []
self._user_list_age: float = 0
self._dept_cache: dict[str, tuple[dict, float]] = {}
self._dept_list_cache: list[dict] = []
self._dept_list_age: float = 0
async def get_user(self, user_id: str, *, user_id_type: str = "open_id") -> dict | None:
cache_key = f"{user_id_type}:{user_id}"
cached = self._user_cache.get(cache_key)
if cached is not None:
data, ts = cached
if time.monotonic() - ts < CACHE_TTL_USERS_S:
return data
del self._user_cache[cache_key]
path = f"/open-apis/contact/v3/users/{user_id}"
try:
resp = await self._api.get(path, user_id_type=user_id_type)
user = resp.get("data", {}).get("user", {})
if user:
self._user_cache[cache_key] = (user, time.monotonic())
return user
except Exception:
return None
async def list_users(
self,
*,
department_id: str = "",
page_token: str = "",
page_size: int = 0,
) -> dict[str, Any]:
size = min(page_size or self._page_size, PAGE_SIZE_MAX)
path = "/open-apis/contact/v3/users"
params: dict[str, Any] = {"page_size": size}
if department_id:
params["department_id"] = department_id
if page_token:
params["page_token"] = page_token
if not department_id and not page_token:
if time.monotonic() - self._user_list_age < CACHE_TTL_USERS_S and self._user_list_cache:
return {"items": self._user_list_cache, "has_more": False, "page_token": ""}
resp = await self._api.get(path, **params)
data = resp.get("data", {})
items = data.get("items", [])
if not department_id and not page_token:
self._user_list_cache = items
self._user_list_age = time.monotonic()
return {
"items": items,
"has_more": data.get("has_more", False),
"page_token": data.get("page_token", ""),
}
async def get_department(self, department_id: str) -> dict | None:
cache_key = department_id
cached = self._dept_cache.get(cache_key)
if cached is not None:
data, ts = cached
if time.monotonic() - ts < CACHE_TTL_DEPARTMENTS_S:
return data
del self._dept_cache[cache_key]
path = f"/open-apis/contact/v3/departments/{department_id}"
try:
resp = await self._api.get(path)
dept = resp.get("data", {}).get("department", {})
if dept:
self._dept_cache[cache_key] = (dept, time.monotonic())
return dept
except Exception:
return None
async def list_departments(
self,
*,
parent_department_id: str = "",
page_token: str = "",
page_size: int = 0,
) -> dict[str, Any]:
size = min(page_size or self._page_size, PAGE_SIZE_MAX)
path = "/open-apis/contact/v3/departments"
params: dict[str, Any] = {"page_size": size}
if parent_department_id:
params["parent_department_id"] = parent_department_id
if page_token:
params["page_token"] = page_token
if not parent_department_id and not page_token:
if time.monotonic() - self._dept_list_age < CACHE_TTL_DEPARTMENTS_S and self._dept_list_cache:
return {"items": self._dept_list_cache, "has_more": False, "page_token": ""}
resp = await self._api.get(path, **params)
data = resp.get("data", {})
items = data.get("items", [])
if not parent_department_id and not page_token:
self._dept_list_cache = items
self._dept_list_age = time.monotonic()
return {
"items": items,
"has_more": data.get("has_more", False),
"page_token": data.get("page_token", ""),
}
def invalidate_cache(self, scope: str = "all") -> None:
if scope in ("all", "users"):
self._user_cache.clear()
self._user_list_cache.clear()
self._user_list_age = 0
if scope in ("all", "departments"):
self._dept_cache.clear()
self._dept_list_cache.clear()
self._dept_list_age = 0
async def list_groups(client: Any) -> list[dict]:
resp = await client.chat.v1.list(page_size=200)
if not resp.success():
return []
items = []
data = resp.data
for group in getattr(data, "items", []) or []:
items.append({
"id": getattr(group, "chat_id", ""),
"name": getattr(group, "name", ""),
})
return items
async def list_peers(client: Any) -> list[dict]:
resp = await client.contact.v3.scope.list()
if not resp.success():
return []
items = []
data = resp.data
for user in getattr(data, "items", []) or []:
items.append({
"id": getattr(user, "open_id", ""),
"name": getattr(user, "name", ""),
})
return items