๐๏ธ 02 - ็ณป็ปๆถๆ โ
ๆฌ็ซ ่ฏฆ็ปไป็ป XiaoQing ็ๅ ้จๆถๆๅๅทฅไฝๅ็ใ
NOTE
ๆฌ็ซ ๅๅๆกๆถๅ ้จๅฎ็ฐ๏ผ้ๅๆณๆทฑๅ ฅไบ่งฃ็ๅผๅ่ ใๅฆๆๅชๆฏๅๆไปถ๏ผ็ดๆฅ็ 03-plugin-development.md ๅณๅฏใ
๐ญ ๆถๆๆป่ง โ
โโโโโโโโโโโโโโโโโโโ
โ QQ ๆๅกๅจ โ
โโโโโโโโโโฌโโโโโโโโโ
โ
โโโโโโโโโโผโโโโโโโโโ
โ OneBot ๅฎ็ฐ โ
โ (NapCat็ญ) โ
โโโโโโโโโโฌโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ โ
โผ โผ โผ
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
โ HTTP POST โ โ WebSocket โ โ HTTP API โ
โ (ไบไปถๆจ้) โ โ (ๅๅ้ไฟก) โ โ (ๅ้ๆถๆฏ) โ
โโโโโโโโโโฌโโโโโโโโโ โโโโโโโโโโฌโโโโโโโโโ โโโโโโโโโโฒโโโโโโโโโ
โ โ โ
โ โ โ
โโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโ
โ โ XiaoQing ๆกๆถ โ โ โ
โ โผ โผ โ โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โ โ
โ โ InboundServer โ โ OneBotWsClient โ โ โ
โ โ (server.py) โ โ (onebot.py) โ โ โ
โ โโโโโโโโโโฌโโโโโโโโโ โโโโโโโโโโฌโโโโโโโโโ โ โ
โ โ โ โ โ
โ โโโโโโโโโโโโฌโโโโโโโโโโโโ โ โ
โ โ ไบไปถ โ โ
โ โผ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ
โ โ Dispatcher (dispatcher.py) โ โ
โ โ โข ๆถๆฏ่งฃๆ โ โ
โ โ โข ่งฆๅๆกไปถๅคๆญ โ โ
โ โ โข ไผ่ฏ็ฎก็ โ โ
โ โ โข ๅฝไปค/้ฒ่่ทฏ็ฑ โ โ
โ โโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Router (router.py) โ โ
โ โ โข ๅฝไปค่งฆๅ่ฏๅน้
โ โ
โ โ โข ไผๅ
็บงๆๅบ โ โ
โ โโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ PluginManager (plugin_manager.py) โ โ
โ โ โข ๆไปถๅ ่ฝฝ/ๅธ่ฝฝ โ โ
โ โ โข ็ญ้่ฝฝ็ๆง โ โ
โ โ โข Context ๆๅปบ โ โ
โ โโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Plugin.handle() โ โ
โ โ ไฝ ็ๆไปถไปฃ็ โ โ
โ โโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โ ๆถๆฏๆฎต โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ OneBotHttpSender (onebot.py) โโโโโโโโโโโโโโ
โ โ ๅ้ๅๅบๆถๆฏ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
โ โ SessionManager โ โ SchedulerManagerโ โ ConfigManager โ
โ โ (session.py) โ โ (scheduler.py) โ โ (config.py) โ
โ โ ๅค่ฝฎๅฏน่ฏ็ฎก็ โ โ ๅฎๆถไปปๅก็ฎก็ โ โ ้
็ฝฎ็ญ้่ฝฝ โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ๏ธ ๆ ธๅฟ็ปไปถ โ
1. XiaoQingApp๏ผapp.py๏ผ โ
่่ดฃ๏ผๅบ็จๅ ฅๅฃ๏ผ็ฎก็ๆๆ็ปไปถ็็ๅฝๅจๆใ
class XiaoQingApp:
def __init__(self, root: Path):
# ๅๅงๅ้
็ฝฎ
self.config_manager = ConfigManager(...)
# ๅๅงๅๅ็ปไปถ
self.router = CommandRouter()
self.plugin_manager = PluginManager(...)
self.scheduler = SchedulerManager(...)
self.session_manager = SessionManager(...)
self.dispatcher = Dispatcher(...)
async def start(self):
# 1. ๅๅงๅๅนถๅๆงๅถ
concurrency = self.config.get("max_concurrency", 5)
self.dispatcher.semaphore = asyncio.Semaphore(concurrency)
# 2. ๅๅปบ HTTP ไผ่ฏ
self.http_session = aiohttp.ClientSession()
# 3. ๅ ่ฝฝๆๆๆไปถ
self.plugin_manager.load_all()
# 4. ๅฏๅจ้ไฟกๆๅก
if enable_ws_client:
self.ws_client.connect_and_listen(...)
if enable_inbound_server:
self.inbound_server.start()
async def stop(self):
# ไผ้
ๅ
ณ้ญๆๆ็ปไปถ
if self.ws_client:
await self.ws_client.stop()
# ...ๅ ณ้ฎๅฑๆง๏ผ
config- ้ ็ฝฎๅญๅ ธsecrets- ๆๆ้ ็ฝฎis_admin(user_id)- ๅคๆญๆฏๅฆ็ฎก็ๅ
2. Dispatcher๏ผdispatcher.py๏ผ โ
่่ดฃ๏ผๆถๆฏๅๅ็ๆ ธๅฟ๏ผ้็จ Handler ้พๅผๅค็ๆจกๅผใ
class Dispatcher:
def __init__(self, ...):
# Handler ้พ๏ผๆไผๅ
็บงไพๆฌกๅฐ่ฏๅค็
self._handlers: tuple[MessageHandler, ...] = (
BotNameHandler(self), # 1. ๅค็ไป
ๆๅๆบๅจไบบๅๅญ
CommandHandler(self), # 2. ๅฝไปคๅน้
SessionHandler(self), # 3. ๆดป่ทไผ่ฏ
SmalltalkHandler(self), # 4. ้ฒ่
)
async def handle_event(self, event: Dict) -> List[Dict]:
# 1. ๅนถๅๆงๅถ
async with self.semaphore:
return await self._handle_event(event)
async def _handle_event(self, event: Dict) -> List[Dict]:
# 2. ่งฃๆๆถๆฏ
text, user_id, group_id = normalize_message(event)
# 3. ๅณ็ญๅคๆญ
decision = self._make_decision(text, user_id, group_id)
if not decision.should_process:
return []
# 4. URL ๆฃๆต๏ผๅ
จๅฑ็ๅฌ๏ผ
if url_match and not has_prefix:
result = await url_plugin.handle_url(url, event, context)
if result:
return result
# 5. Handler ้พๅผๅค็
for handler in self._handlers:
result = await handler.handle(text, event, context)
if result is not None:
return result
return []Handler ้พๅทฅไฝๅ็๏ผ
ๆฏไธช Handler ๅฎ็ฐ็ธๅ็ๆฅๅฃ๏ผๆ้กบๅบๅฐ่ฏๅค็๏ผ
class MessageHandler(ABC):
@abstractmethod
async def handle(self, text: str, event: Dict, context) -> Optional[List[Dict]]:
"""ๅค็ๆถๆฏ๏ผ่ฟๅๆถๆฏๆฎตๅ่กจๆ None๏ผ่กจ็คบไธๅค็๏ผ"""
passIMPORTANT
็ญ่ทฏๆบๅถ๏ผไธๆฆๆไธช Handler ่ฟๅ้ None ็ปๆ๏ผๅ็ปญ Handler ไธไผๆง่กใ่ฟๆๅณ็ๅฝไปคๆปๆฏไผๅ
ไบ้ฒ่๏ผไผ่ฏๅค็ไผๅ
ไบๆฎ้ๅน้
ใ
ๆถๆฏๅค็ๅณ็ญๆ ๏ผ
ๆถๅฐๆถๆฏ
โ
โโ ็ง่๏ผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ> ่ฟๅ
ฅ Handler ้พ
โ
โโ ็พค่๏ผ
โ
โโ ๆๅฝไปคๅ็ผ๏ผๅฆ /help๏ผ๏ผโโโโโโโโโโโโโ> ่ฟๅ
ฅ Handler ้พ๏ผๅฝไปคไผๅ
๏ผ
โ
โโ ๅ
ๅซๆบๅจไบบๅๅญ๏ผๅฆ"ๅฐ้"๏ผ๏ผโโโโโโโโโ> ่ฟๅ
ฅ Handler ้พ๏ผๅฏ้ฒ่๏ผ
โ
โโ ็พค่ขซ้้ณ๏ผโโโโโโโโโโโโโโโโโโโโโโโโโ> ไธๅค็๏ผๅฝไปค้คๅค๏ผ
โ
โโ ๆดป่ทไผ่ฏ๏ผโโโโโโโโโโโโโโโโโโโโโโโโโ> ่ฟๅ
ฅ Handler ้พ๏ผไผ่ฏไผๅ
๏ผ
โ
โโ ้ๆบ่งฆๅ๏ผrandom_reply_rate๏ผ๏ผโโโโ> ่ฟๅ
ฅ Handler ้พ๏ผ้ฒ่ๆจกๅผ๏ผ
โ
โโ ๅฆๅ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ> ไธๅค็
Handler ้พๅค็ๆต็จ๏ผ
โ
โโ BotNameHandler๏ผไป
ๆบๅจไบบๅๅญ๏ผโโโโโโโโโโโ> ๅค็ๅนถ่ฟๅ
โ โ
โ โโ ๅฆ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ> ็ปง็ปญไธไธไธช Handler
โ
โโ CommandHandler๏ผๅฝไปคๅน้
ๆๅ๏ผโโโโโโโโโโโ> ๅค็ๅนถ่ฟๅ
โ โ
โ โโ ๅฆ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ> ็ปง็ปญไธไธไธช Handler
โ
โโ SessionHandler๏ผๆดป่ทไผ่ฏๅญๅจ๏ผโโโโโโโโโโโ> ๅค็ๅนถ่ฟๅ
โ โ
โ โโ ๅฆ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ> ็ปง็ปญไธไธไธช Handler
โ
โโ SmalltalkHandler๏ผsmalltalk_mode=True๏ผโโ> ๅค็ๅนถ่ฟๅ
โ
โโ ๅฆ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ> ่ฟๅ็ฉบๅ่กจxiaoqing_chat ็นๆฎๅค็๏ผ
ๅฝ smalltalk_provider ่ฎพ็ฝฎไธบ xiaoqing_chat ๆถ๏ผๅณ็ญ้ป่พ็นๆฎ๏ผ
- ๆๆ็พค่ๆถๆฏ้ฝ่ฟๅ
should_process=Trueๅsmalltalk_mode=True random_reply_rate้ ็ฝฎๅคฑๆxiaoqing_chatๆไปถๅ ้จๆ่ชๅทฑ็้ข็ๆงๅถๅๅๅคๆฆ็ๅคๆญ
3. Router๏ผrouter.py๏ผ โ
่่ดฃ๏ผๆ นๆฎ่งฆๅ่ฏๅน้ ๅฝไปคใ
@dataclass
class CommandSpec:
plugin: str # ๆๅฑๆไปถๅ
name: str # ๅฝไปคๅ
triggers: List[str] # ่งฆๅ่ฏๅ่กจ
help_text: str # ๅธฎๅฉๆๆฌ
admin_only: bool # ๆฏๅฆไป
็ฎก็ๅ
handler: Handler # ๅค็ๅฝๆฐ
priority: int # ไผๅ
็บง
class CommandRouter:
def register(self, spec: CommandSpec):
"""ๆณจๅๅฝไปค"""
self._commands.append(spec)
def resolve(self, text: str) -> Optional[Tuple[CommandSpec, str]]:
"""่งฃๆๅฝไปค"""
# ๆไผๅ
็บงๅ่งฆๅ่ฏ้ฟๅบฆๆๅบ๏ผ้ฟ็ไผๅ
๏ผ
for spec in sorted_commands:
for trigger in spec.triggers:
if text.startswith(trigger):
args = text[len(trigger):].strip()
return spec, args
return Noneไผๅ ็บง่งๅ๏ผ
priorityๆฐๅผ่ถๅคง่ถไผๅ - ๅไผๅ
็บงๆถ๏ผ่งฆๅ่ฏ่ถ้ฟ่ถไผๅ
๏ผ้ฟๅ
helpๆข่ตฐhelpme็ๅน้ ๏ผ
4. PluginManager๏ผplugin_manager.py๏ผ โ
่่ดฃ๏ผ็ฎก็ๆไปถ็ๅ ่ฝฝใๅธ่ฝฝๅ็ญ้่ฝฝใ
class PluginManager:
def load_all(self):
"""ๅ ่ฝฝ plugins/ ไธๆๆๆไปถ"""
for plugin_dir in self.plugins_dir.iterdir():
if self._is_plugin_dir(plugin_dir):
self.load_plugin(plugin_dir)
def load_plugin(self, plugin_dir: Path):
"""ๅ ่ฝฝๅไธชๆไปถ"""
# 1. ่ฏปๅ plugin.json
definition = self._load_definition(plugin_dir)
# 2. ๅฏผๅ
ฅ main.py ๆจกๅ
module = self._load_module(plugin_dir, definition)
# 3. ๆณจๅๅฝไปคๅฐ Router
self._register_commands(definition, module)
# 4. ่ฐ็จ init() ้ฉๅญ๏ผๅฆๆๅญๅจ๏ผ
if hasattr(module, "init"):
module.init()
async def reload_plugin(self, name: str):
"""็ญ้่ฝฝๆไปถ"""
await self.unload_plugin(name)
self.load_plugin(self.plugins_dir / name)
async def watch(self):
"""็ๆงๆไปถๆไปถๅๅ๏ผ่ชๅจ้่ฝฝ"""
while True:
await asyncio.sleep(self._poll_interval)
# ๆฃๆฅ mtime๏ผๅฆๆๅๅๅ้่ฝฝๆไปถๅ ่ฝฝๆต็จ๏ผ
plugins/echo/
โ
โโโ plugin.json โโ> PluginDefinition
โ (name, version, commands, schedule...)
โ
โโโ main.py โโโโโโ> Module
(handle, init, shutdown...)
โ
โผ
Router.register(CommandSpec)5. SessionManager๏ผsession.py๏ผ โ
่่ดฃ๏ผ็ฎก็ๅค่ฝฎๅฏน่ฏ็ไผ่ฏ็ถๆใ
@dataclass
class Session:
user_id: int
group_id: Optional[int] # None = ็ง่
plugin_name: str # ๆๅฑๆไปถ
data: Dict[str, Any] # ไผ่ฏๆฐๆฎ
timeout: float # ่ถ
ๆถๆถ้ด
def get(self, key, default=None): ...
def set(self, key, value): ...
def is_expired(self) -> bool: ...
class SessionManager:
# ไผ่ฏๅญๅจ๏ผ(user_id, group_id) -> Session
_sessions: Dict[tuple, Session]
async def create(self, user_id, group_id, plugin_name, initial_data, timeout):
"""ๅๅปบๆฐไผ่ฏ"""
async def get(self, user_id, group_id) -> Optional[Session]:
"""่ทๅไผ่ฏ๏ผ่ชๅจๆธ
็่ฟๆ๏ผ"""
async def delete(self, user_id, group_id) -> bool:
"""ๅ ้คไผ่ฏ"""ไผ่ฏ็ๅฝๅจๆ๏ผ
1. ็จๆทๅ้ๅฝไปค๏ผๅฆ /็ๆฐๅญ๏ผ
โ
โผ
2. ๆไปถ่ฐ็จ context.create_session()
โ
โผ
3. ไผ่ฏๅๅปบ๏ผๅญๅจๅๅงๆฐๆฎ
โ
โผ
4. ็จๆทๅ็ปญๆถๆฏ่ขซ่ทฏ็ฑๅฐ handle_session()
โ
โผ
5. ๆไปถๆดๆฐไผ่ฏๆฐๆฎ session.set()
โ
โโ ็ปง็ปญๅฏน่ฏ โโ> ๅๅฐๆญฅ้ชค 4
โ
โโ ๅฏน่ฏ็ปๆ โโ> context.end_session()
โ
โผ
ไผ่ฏ่ขซๅ ้ค6. SchedulerManager๏ผscheduler.py๏ผ โ
่่ดฃ๏ผ็ฎก็ๅฎๆถไปปๅกใ
class SchedulerManager:
def __init__(self, timezone: str):
self.scheduler = AsyncIOScheduler(timezone=timezone)
self.scheduler.start()
def add_job(self, job_id: str, func, cron: Dict):
"""ๆทปๅ ๅฎๆถไปปๅก"""
self.scheduler.add_job(func, trigger="cron", id=job_id, **cron)
def remove_job(self, job_id: str):
"""็งป้คไปปๅก"""
def clear_prefix(self, prefix: str):
"""็งป้คๆๅ็ผ็ๆๆไปปๅก๏ผ็จไบๆไปถๅธ่ฝฝ๏ผ"""Cron ่กจ่พพๅผ็คบไพ๏ผ
# ๆฏๅคฉ 8:00
{"hour": 8, "minute": 0}
# ๆฏ 2 ๅฐๆถ
{"hour": "*/2"}
# ๅทฅไฝๆฅ 9:00
{"day_of_week": "mon-fri", "hour": 9}
# ๆฏๆ 1 ๅท 0:00
{"day": 1, "hour": 0, "minute": 0}7. OneBot ้ไฟก๏ผonebot.py + server.py๏ผ โ
ไธค็ง้ไฟกๆนๅผ๏ผ
OneBotHttpSender - ๅ้ๆถๆฏ โ
class OneBotHttpSender:
async def send_action(self, action: Dict):
"""ๅ้ OneBot Action"""
url = f"{self.http_base}/{action['action']}"
await self.session.post(url, json=action['params'], headers=headers)OneBotWsClient - WebSocket ๅๅ้ไฟก โ
class OneBotWsClient:
async def connect_and_listen(self, handler):
"""่ฟๆฅๅนถๆ็ปญ็ๅฌ"""
async with websockets.connect(self.ws_uri) as ws:
async for message in ws:
event = json.loads(message)
await handler(event)
async def send_action(self, action: Dict):
"""้่ฟ WS ๅ้"""
await self._ws.send(json.dumps(action))InboundServer - ่ขซๅจๆฅๆถ โ
class InboundServer:
"""HTTP ๆๅกๅจ๏ผๆฅๆถ OneBot ๆจ้"""
async def post_event(self, request):
"""POST /event - ๆฅๆถไบไปถ"""
payload = await request.json()
actions = await self.handler(payload)
return web.json_response({"actions": actions})
async def ws_handler(self, request):
"""WebSocket ็ซฏ็น"""
# ๆไน
่ฟๆฅๅค็๐ ๆฐๆฎๆต่ฏฆ่งฃ โ
ๅฎๆด่ฏทๆฑๆต็จ โ
1. OneBot ๆจ้ไบไปถ
POST http://127.0.0.1:12000/event
{
"post_type": "message",
"message_type": "group",
"group_id": 123456,
"user_id": 789,
"message": [{"type": "text", "data": {"text": "/echo hello"}}]
}
2. InboundServer ๆฅๆถ
โโ ้ช่ฏ Authorization Token
โโ ่งฃๆ JSON
โโ ่ฐ็จ handler(event)
3. Dispatcher ๅค็
โโ normalize_message() ๆๅ text="echo hello", user_id=789, group_id=123456
โโ _make_decision() ๅคๆญ้่ฆๅค็๏ผๆๅฝไปคๅ็ผ๏ผ
โโ URL ๆฃๆต๏ผๆ URL๏ผ่ทณ่ฟ๏ผ
โโ Handler ้พๅค็๏ผ
โโ BotNameHandler๏ผไธๆฏไป
ๆบๅจไบบๅๅญ โ None
โโ CommandHandler๏ผๅน้
ๆๅ๏ผ
โ โโ router.resolve("echo hello") ๅพๅฐ (echoๆไปถ, "hello")
โ โโ ๆ้ๆฃๆฅ้่ฟ
โ โโ ๆๅปบ context
โ โโ ่ฐ็จ echo.handle("echo", "hello", event, context)
โโ ๏ผ็ญ่ทฏ๏ผๅ็ปญ Handler ไธๆง่ก๏ผ
4. ๆไปถๅค็
โโ ่ฟๅ [{"type": "text", "data": {"text": "hello"}}]
5. ๆๅปบๅๅบ
โโ build_action(segs, user_id, group_id)
โโ {
"action": "send_group_msg",
"params": {
"group_id": 123456,
"message": [{"type": "text", "data": {"text": "hello"}}]
}
}
6. ่ฟๅ็ป OneBot
โโ InboundServer ่ฟๅ {"actions": [...]}
โโ OneBot ๆง่ก action๏ผๅ้ๆถๆฏๅฐ QQไผ่ฏๅค็ๆต็จ็คบไพ โ
1. ็จๆทๅ้ /guess ๅฏๅจ็ๆฐๅญๆธธๆ
โโ guess.handle() ๅๅปบไผ่ฏ
โโ context.create_session(initial_data={"target": 42})
2. ็จๆทๅ็ปญๆถๆฏ "50"
โโ Dispatcher ๅค็
โโ Handler ้พ๏ผ
โโ BotNameHandler๏ผNone
โโ CommandHandler๏ผๆ ๅฝไปคๅน้
โ None
โโ SessionHandler๏ผๅ็ฐๆดป่ทไผ่ฏ๏ผ
โ โโ ่ฐ็จ guess.handle_session("50", event, context, session)
โ โโ ่ฟๅ ["ๅคชๅคงไบ๏ผ"]
โโ ๏ผ็ญ่ทฏ๏ผ
3. ็จๆท็ๆตๆญฃ็กฎ "42"
โโ SessionHandler ๅค็
โโ guess.handle_session() ๅคๆญๆญฃ็กฎ
โโ context.end_session() ๅ ้คไผ่ฏ
โโ ่ฟๅ ["ๆญๅไฝ ็ๅฏนไบ๏ผ"]โก ๅนถๅๆงๅถ โ
XiaoQing ไฝฟ็จ asyncio.Semaphore ๆงๅถๅนถๅ๏ผ
# app.py
concurrency = int(config.get("max_concurrency", 5))
self.dispatcher = Dispatcher(..., semaphore=asyncio.Semaphore(concurrency))
# dispatcher.py
async def handle_event(self, event):
async with self.semaphore: # ๆๅคๅๆถๅค็ 5 ๆกๆถๆฏ
return await self._handle_event(event)๐งฉ ๆไปถๅ ๅตๆๅก โ
้จๅๆไปถๅฏไปฅๅจๆกๆถไนๅค็ฌ็ซ่ฟ่ก้ๅ ๆๅกใๅ ธๅๆกไพๆฏ pendo ๆไปถ๏ผ
XiaoQing ไธป่ฟ็จ
โโโ ๆญฃๅธธๆถๆฏๅค็ๆต็จ๏ผDispatcher โ Plugin๏ผ
โโโ pendo ๆไปถ๏ผmain.py๏ผ
โโโ /pendo web start ๅฝไปค่งฆๅ
โโโ FastAPI Web Server๏ผuvicorn๏ผ
โโโ GET/POST /pendo/api/* # REST API๏ผJWT ้ดๆ๏ผ
โโโ GET /* # ้ๆ SPA ๆไปถ็น็น๏ผ
- Web Server ๅจ็ฌ็ซ็ asyncio Task ไธญ่ฟ่ก๏ผไธ้ปๅกๆถๆฏๅค็
- ้่ฟ
/pendo web start [port]ๆ้ๅฏๅจ๏ผ/pendo web stopๅ ณ้ญ - ๆฏๆ nginx ๅญ่ทฏๅพๅๅไปฃ็๏ผ
/pendoๅ็ผ๏ผ
โก๏ธ ไธไธๆญฅ โ
- ๆณๅผๅๆไปถ๏ผโ 03-plugin-development.md
- ๆณไบ่งฃๅๆจกๅๆบ็ ๏ผโ 04-core-modules.md
- ๆณไบ่งฃๆถๆฏๅค็ๆต็จ๏ผโ 08-message-flow.md