feat(login): emit login and online status events
This commit is contained in:
@@ -14,6 +14,11 @@ router = APIRouter(prefix="/v1/wechat-personal", tags=["wechat-personal"])
|
|||||||
class StartLoginRequest(BaseModel):
|
class StartLoginRequest(BaseModel):
|
||||||
account_id: str = Field(..., description="渠道账号唯一标识")
|
account_id: str = Field(..., description="渠道账号唯一标识")
|
||||||
account_name: Optional[str] = Field(None, 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):
|
class SendTextRequest(BaseModel):
|
||||||
@@ -30,7 +35,7 @@ class SendImageRequest(BaseModel):
|
|||||||
|
|
||||||
@router.post("/login/start")
|
@router.post("/login/start")
|
||||||
def start_login(request: StartLoginRequest):
|
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")
|
@router.get("/login/{account_id}/status")
|
||||||
@@ -39,8 +44,13 @@ def login_status(account_id: str):
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/login/{account_id}/confirm")
|
@router.post("/login/{account_id}/confirm")
|
||||||
def confirm_login(account_id: str):
|
def confirm_login(account_id: str, request: LoginConfirmRequest):
|
||||||
return login_service.confirm_login(account_id)
|
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")
|
@router.post("/send/text")
|
||||||
|
|||||||
@@ -90,6 +90,20 @@ class PyWeChatClient:
|
|||||||
pass
|
pass
|
||||||
return self.state
|
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:
|
class ClientRegistry:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|||||||
@@ -4,15 +4,19 @@ import time
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from app.clients.pywechat_client import ClientRegistry, LoginStatus, OnlineStatus, registry
|
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:
|
class LoginService:
|
||||||
def __init__(self, registry: ClientRegistry):
|
def __init__(self, registry: ClientRegistry):
|
||||||
self.registry = registry
|
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)
|
client = self.registry.get_or_create(account_id, account_name)
|
||||||
login_id = client.start_login()
|
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 {
|
return {
|
||||||
"login_id": login_id,
|
"login_id": login_id,
|
||||||
"qr_code_url": client.state.qr_code_url,
|
"qr_code_url": client.state.qr_code_url,
|
||||||
@@ -35,19 +39,54 @@ class LoginService:
|
|||||||
"sent_count_today": state.sent_count_today,
|
"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)
|
client = self.registry.get(account_id)
|
||||||
if client is None:
|
if client is None:
|
||||||
return {"success": False, "error": "account not found"}
|
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}
|
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)
|
client = self.registry.get(account_id)
|
||||||
if client is None:
|
if client is None:
|
||||||
return {"success": False, "error": "account not found"}
|
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}
|
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)
|
login_service = LoginService(registry)
|
||||||
|
from app.repositories.event_store import event_store # noqa: E402
|
||||||
|
login_service.store = event_store
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ def test_login_flow():
|
|||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
assert r.json()["login_status"] == "pending"
|
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.status_code == 200
|
||||||
assert r.json()["success"] is True
|
assert r.json()["success"] is True
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ def test_login_flow():
|
|||||||
|
|
||||||
def test_send_after_login():
|
def test_send_after_login():
|
||||||
client.post("/v1/wechat-personal/login/start", json={"account_id": "bot-002"})
|
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={
|
r = client.post("/v1/wechat-personal/send/text", json={
|
||||||
"account_id": "bot-002",
|
"account_id": "bot-002",
|
||||||
|
|||||||
Reference in New Issue
Block a user