diff --git a/channel-service/app/api/wechat_personal.py b/channel-service/app/api/wechat_personal.py index 28853e6..3be3eec 100644 --- a/channel-service/app/api/wechat_personal.py +++ b/channel-service/app/api/wechat_personal.py @@ -14,6 +14,11 @@ router = APIRouter(prefix="/v1/wechat-personal", tags=["wechat-personal"]) class StartLoginRequest(BaseModel): account_id: str = Field(..., description="渠道账号唯一标识") account_name: Optional[str] = Field(None, description="账号显示名称") + tenant_code: str = Field("default", description="业务系统租户编码") + + +class LoginConfirmRequest(BaseModel): + tenant_code: str = Field("default", description="业务系统租户编码") class SendTextRequest(BaseModel): @@ -30,7 +35,7 @@ class SendImageRequest(BaseModel): @router.post("/login/start") def start_login(request: StartLoginRequest): - return login_service.start_login(request.account_id, request.account_name) + return login_service.start_login(request.account_id, request.account_name, request.tenant_code) @router.get("/login/{account_id}/status") @@ -39,8 +44,13 @@ def login_status(account_id: str): @router.post("/login/{account_id}/confirm") -def confirm_login(account_id: str): - return login_service.confirm_login(account_id) +def confirm_login(account_id: str, request: LoginConfirmRequest): + return login_service.confirm_login(account_id, request.tenant_code) + + +@router.post("/login/{account_id}/expire") +def expire_login(account_id: str, request: LoginConfirmRequest): + return login_service.expire_login(account_id, request.tenant_code) @router.post("/send/text") diff --git a/channel-service/app/clients/pywechat_client.py b/channel-service/app/clients/pywechat_client.py index 6480890..bf5372e 100644 --- a/channel-service/app/clients/pywechat_client.py +++ b/channel-service/app/clients/pywechat_client.py @@ -90,6 +90,20 @@ class PyWeChatClient: pass return self.state + def set_login_status(self, status: LoginStatus, error_message: Optional[str] = None) -> None: + old = self.state.login_status + self.state.login_status = status + if status == LoginStatus.SUCCESS: + self.state.online_status = OnlineStatus.ONLINE + self.state.last_heartbeat_at = time.time() + elif status in (LoginStatus.EXPIRED, LoginStatus.FAILED): + self.state.online_status = OnlineStatus.OFFLINE + if error_message: + self.state.error_message = error_message + + def set_online_status(self, status: OnlineStatus) -> None: + self.state.online_status = status + class ClientRegistry: def __init__(self): diff --git a/channel-service/app/services/login_service.py b/channel-service/app/services/login_service.py index 43a2519..7110240 100644 --- a/channel-service/app/services/login_service.py +++ b/channel-service/app/services/login_service.py @@ -4,15 +4,19 @@ import time from typing import Optional from app.clients.pywechat_client import ClientRegistry, LoginStatus, OnlineStatus, registry +from app.models.event import EventType, OutboundEvent +from app.repositories.event_store import EventStore class LoginService: def __init__(self, registry: ClientRegistry): self.registry = registry + self.store: EventStore | None = None - def start_login(self, account_id: str, account_name: Optional[str] = None) -> dict: + def start_login(self, account_id: str, account_name: Optional[str] = None, tenant_code: str = "default") -> dict: client = self.registry.get_or_create(account_id, account_name) login_id = client.start_login() + self._emit_login_changed(account_id, tenant_code, LoginStatus.PENDING, client.state.login_status, client.state.qr_code_url) return { "login_id": login_id, "qr_code_url": client.state.qr_code_url, @@ -35,19 +39,54 @@ class LoginService: "sent_count_today": state.sent_count_today, } - def confirm_login(self, account_id: str) -> dict: + def confirm_login(self, account_id: str, tenant_code: str = "default") -> dict: client = self.registry.get(account_id) if client is None: return {"success": False, "error": "account not found"} - client.confirm_login() + previous_login = client.state.login_status + previous_online = client.state.online_status + client.set_login_status(LoginStatus.SUCCESS) + self._emit_login_changed(account_id, tenant_code, previous_login, client.state.login_status) + self._emit_online_changed(account_id, tenant_code, previous_online, client.state.online_status) return {"success": True, "status": client.state.login_status.value} - def expire_login(self, account_id: str) -> dict: + def expire_login(self, account_id: str, tenant_code: str = "default") -> dict: client = self.registry.get(account_id) if client is None: return {"success": False, "error": "account not found"} - client.expire_login() + previous = client.state.login_status + client.set_login_status(LoginStatus.EXPIRED) + self._emit_login_changed(account_id, tenant_code, previous, client.state.login_status) return {"success": True, "status": client.state.login_status.value} + def _emit_login_changed(self, account_id: str, tenant_code: str, previous: LoginStatus, current: LoginStatus, qr_code_url: Optional[str] = None) -> None: + event = OutboundEvent( + event_type=EventType.LOGIN_STATUS_CHANGED, + account_id=account_id, + tenant_code=tenant_code, + data={ + "previous": previous.value, + "current": current.value, + "qr_code_url": qr_code_url, + }, + ) + if self.store is not None: + self.store.save(event) + + def _emit_online_changed(self, account_id: str, tenant_code: str, previous: OnlineStatus, current: OnlineStatus) -> None: + event = OutboundEvent( + event_type=EventType.ONLINE_STATUS_CHANGED, + account_id=account_id, + tenant_code=tenant_code, + data={ + "previous": previous.value, + "current": current.value, + }, + ) + if self.store is not None: + self.store.save(event) + login_service = LoginService(registry) +from app.repositories.event_store import event_store # noqa: E402 +login_service.store = event_store diff --git a/channel-service/tests/test_api.py b/channel-service/tests/test_api.py index 04ffa78..325f09e 100644 --- a/channel-service/tests/test_api.py +++ b/channel-service/tests/test_api.py @@ -24,7 +24,7 @@ def test_login_flow(): assert r.status_code == 200 assert r.json()["login_status"] == "pending" - r = client.post("/v1/wechat-personal/login/bot-001/confirm") + r = client.post("/v1/wechat-personal/login/bot-001/confirm", json={}) assert r.status_code == 200 assert r.json()["success"] is True @@ -35,7 +35,7 @@ def test_login_flow(): def test_send_after_login(): client.post("/v1/wechat-personal/login/start", json={"account_id": "bot-002"}) - client.post("/v1/wechat-personal/login/bot-002/confirm") + client.post("/v1/wechat-personal/login/bot-002/confirm", json={}) r = client.post("/v1/wechat-personal/send/text", json={ "account_id": "bot-002",