feat: 微信自动化客服(wechatauto-replica) 干净历史导入 - AI 自动回复/语音收发/朋友圈发布

This commit is contained in:
2026-09-16 10:12:49 +08:00
commit c3b98f4c4e
69 changed files with 19828 additions and 0 deletions
+127
View File
@@ -0,0 +1,127 @@
"""线程、进程与异步环境下的全局 UI 锁。"""
from __future__ import annotations
import asyncio
import functools
import inspect
import multiprocessing
import threading
from contextlib import asynccontextmanager, contextmanager
from typing import Any, Awaitable, Callable, TypeVar, overload
F = TypeVar("F", bound=Callable[..., Any])
AsyncReturn = TypeVar("AsyncReturn")
class LockManager:
"""提供跨线程/进程/异步的锁。
``process_lock``multiprocessing.Lock)不可重入:同一线程内嵌套
``acquire`` 会永久阻塞。因此用线程局部计数实现**同线程重入**——同一
线程重复获取时跳过进程锁(只需重入线程锁),保证
``@uilock`` 修饰的函数互相调用(如 ``Chat.ForwardVoiceMessage``
内部调用 ``VoiceMessage.forward_to``)不会死锁。
"""
process_lock = multiprocessing.Lock()
thread_lock = threading.RLock()
_async_lock: asyncio.Lock | None = None
_local = threading.local()
@classmethod
def _get_async_lock(cls) -> asyncio.Lock:
"""返回与当前事件循环绑定的 ``asyncio.Lock``。"""
loop = None
try:
loop = asyncio.get_running_loop()
except RuntimeError:
pass
lock = cls._async_lock
if lock is None or (loop and getattr(lock, "_loop", loop) is not loop):
lock = asyncio.Lock()
cls._async_lock = lock
return lock
@classmethod
@contextmanager
def acquire(cls):
"""同步环境下获取锁(同线程可重入)。"""
depth = getattr(cls._local, "depth", 0)
if depth > 0:
# 同线程嵌套:进程锁已被本线程持有,跳过它,只重入线程锁
with cls.thread_lock:
cls._local.depth = depth + 1
try:
yield
finally:
cls._local.depth = depth
return
with cls.process_lock:
with cls.thread_lock:
cls._local.depth = 1
try:
yield
finally:
cls._local.depth = 0
@classmethod
@asynccontextmanager
async def acquire_async(cls):
"""异步环境下获取锁(同线程可重入)。"""
depth = getattr(cls._local, "depth", 0)
if depth > 0:
async with cls._get_async_lock():
with cls.thread_lock:
cls._local.depth = depth + 1
try:
yield
finally:
cls._local.depth = depth
return
async with cls._get_async_lock():
with cls.process_lock:
with cls.thread_lock:
cls._local.depth = 1
try:
yield
finally:
cls._local.depth = 0
@overload
def uilock(func: Callable[..., Awaitable[AsyncReturn]]) -> Callable[..., Awaitable[AsyncReturn]]:
...
@overload
def uilock(func: F) -> F:
...
def uilock(func: F): # type: ignore[misc]
"""确保 UI 自动化操作串行执行的装饰器。"""
if inspect.iscoroutinefunction(func):
@functools.wraps(func)
async def async_wrapper(*args: Any, **kwargs: Any):
async with LockManager.acquire_async():
return await func(*args, **kwargs)
return async_wrapper
@functools.wraps(func)
def sync_wrapper(*args: Any, **kwargs: Any):
with LockManager.acquire():
return func(*args, **kwargs)
return sync_wrapper # type: ignore[return-value]
__all__ = ["LockManager", "uilock"]