From 10af1ca2ad639cde7abbc37e2225477fb2e01202 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Mon, 9 Mar 2026 13:50:08 +0800 Subject: [PATCH] bugfix --- config/tenants.json | 16 ++++++++ im_platform/adapters/base.py | 8 ++++ im_platform/adapters/feishu.py | 19 +++++++++ im_platform/adapters/wecom.py | 20 +++++++++ im_platform/api/webhooks.py | 17 ++++++++ im_platform/core/tenant_manager.py | 24 +++++++++++ im_platform/core/token_cache.py | 17 ++++++++ im_platform/models/im_tenant.json | 63 +++++++++++++++++++++++++++++ im_platform/server.py | 16 ++++++++ im_platform/workers/event_worker.py | 13 ++++++ pyproject.toml | 9 +++++ 11 files changed, 222 insertions(+) create mode 100644 config/tenants.json create mode 100644 im_platform/adapters/base.py create mode 100644 im_platform/adapters/feishu.py create mode 100644 im_platform/adapters/wecom.py create mode 100644 im_platform/api/webhooks.py create mode 100644 im_platform/core/tenant_manager.py create mode 100644 im_platform/core/token_cache.py create mode 100644 im_platform/models/im_tenant.json create mode 100644 im_platform/server.py create mode 100644 im_platform/workers/event_worker.py create mode 100644 pyproject.toml diff --git a/config/tenants.json b/config/tenants.json new file mode 100644 index 0000000..b90c5cf --- /dev/null +++ b/config/tenants.json @@ -0,0 +1,16 @@ + +[ + { + "org_id":"org_demo", + "platform":"feishu", + "app_id":"cli_xxx", + "app_secret":"xxx" + }, + { + "org_id":"org_demo2", + "platform":"wecom", + "corp_id":"wwxxx", + "agent_id":"1000002", + "secret":"xxx" + } +] diff --git a/im_platform/adapters/base.py b/im_platform/adapters/base.py new file mode 100644 index 0000000..2a8dc39 --- /dev/null +++ b/im_platform/adapters/base.py @@ -0,0 +1,8 @@ + +class BaseAdapter: + + def __init__(self, tenant): + self.tenant = tenant + + async def send_text(self, user, text): + raise NotImplementedError() diff --git a/im_platform/adapters/feishu.py b/im_platform/adapters/feishu.py new file mode 100644 index 0000000..382ec5c --- /dev/null +++ b/im_platform/adapters/feishu.py @@ -0,0 +1,19 @@ + +import aiohttp +from im_platform.adapters.base import BaseAdapter + +class FeishuAdapter(BaseAdapter): + + async def send_text(self, user, text): + + url = "https://open.feishu.cn/open-apis/im/v1/messages" + + payload = { + "receive_id": user, + "msg_type": "text", + "content": {"text": text} + } + + async with aiohttp.ClientSession() as s: + async with s.post(url, json=payload) as r: + return await r.text() diff --git a/im_platform/adapters/wecom.py b/im_platform/adapters/wecom.py new file mode 100644 index 0000000..fb0782a --- /dev/null +++ b/im_platform/adapters/wecom.py @@ -0,0 +1,20 @@ + +import aiohttp +from im_platform.adapters.base import BaseAdapter + +class WeComAdapter(BaseAdapter): + + async def send_text(self, user, text): + + url = "https://qyapi.weixin.qq.com/cgi-bin/message/send" + + payload = { + "touser": user, + "msgtype": "text", + "agentid": self.tenant["agent_id"], + "text": {"content": text} + } + + async with aiohttp.ClientSession() as s: + async with s.post(url, json=payload) as r: + return await r.text() diff --git a/im_platform/api/webhooks.py b/im_platform/api/webhooks.py new file mode 100644 index 0000000..4df386c --- /dev/null +++ b/im_platform/api/webhooks.py @@ -0,0 +1,17 @@ + +from aiohttp import web + +async def feishu_webhook(request): + + data = await request.json() + print("feishu event", data) + + return web.json_response({"ok": True}) + + +async def wecom_webhook(request): + + data = await request.json() + print("wecom event", data) + + return web.json_response({"ok": True}) diff --git a/im_platform/core/tenant_manager.py b/im_platform/core/tenant_manager.py new file mode 100644 index 0000000..3001cf6 --- /dev/null +++ b/im_platform/core/tenant_manager.py @@ -0,0 +1,24 @@ + +import json +from pathlib import Path + +TENANT_CACHE = {} + +async def load_tenants(): + global TENANT_CACHE + p = Path(__file__).resolve().parent.parent / "config" / "tenants.json" + if p.exists(): + TENANT_CACHE = json.loads(p.read_text()) + return TENANT_CACHE + +def get_tenant_by_org(org_id): + for t in TENANT_CACHE: + if t["org_id"] == org_id: + return t + return None + +def get_tenant_by_platform(platform, key): + for t in TENANT_CACHE: + if t["platform"] == platform and (t.get("app_id")==key or t.get("corp_id")==key): + return t + return None diff --git a/im_platform/core/token_cache.py b/im_platform/core/token_cache.py new file mode 100644 index 0000000..64d0f36 --- /dev/null +++ b/im_platform/core/token_cache.py @@ -0,0 +1,17 @@ + +import asyncio +import time + +CACHE = {} + +async def get_token(key): + v = CACHE.get(key) + if not v: + return None + token, expire = v + if expire < time.time(): + return None + return token + +async def set_token(key, token, ttl): + CACHE[key] = (token, time.time() + ttl - 60) diff --git a/im_platform/models/im_tenant.json b/im_platform/models/im_tenant.json new file mode 100644 index 0000000..fa27f27 --- /dev/null +++ b/im_platform/models/im_tenant.json @@ -0,0 +1,63 @@ +{ + "summary": [ + { + "name": "im_tenant", + "title": "IM\u79df\u6237", + "primary": "id", + "catelog": "relation" + } + ], + "fields": [ + { + "name": "id", + "title": "ID", + "type": "str", + "length": 32 + }, + { + "name": "org_id", + "title": "\u7ec4\u7ec7ID", + "type": "str", + "length": 32 + }, + { + "name": "platform", + "title": "\u5e73\u53f0", + "type": "str", + "length": 16 + }, + { + "name": "app_id", + "title": "APPID", + "type": "str", + "length": 64 + }, + { + "name": "app_secret", + "title": "APPSECRET", + "type": "str", + "length": 128 + }, + { + "name": "corp_id", + "title": "\u4f01\u4e1aID", + "type": "str", + "length": 64 + }, + { + "name": "agent_id", + "title": "AgentID", + "type": "str", + "length": 32 + } + ], + "indexes": [ + { + "name": "idx_org", + "idxtype": "index", + "idxfields": [ + "org_id" + ] + } + ] +} \ No newline at end of file diff --git a/im_platform/server.py b/im_platform/server.py new file mode 100644 index 0000000..b946feb --- /dev/null +++ b/im_platform/server.py @@ -0,0 +1,16 @@ + +from aiohttp import web +from im_platform.api.webhooks import feishu_webhook, wecom_webhook + +async def init_app(): + app = web.Application() + + app.router.add_post("/webhook/feishu", feishu_webhook) + app.router.add_post("/webhook/wecom", wecom_webhook) + + return app + +if __name__ == "__main__": + import asyncio + app = asyncio.run(init_app()) + web.run_app(app, port=8080) diff --git a/im_platform/workers/event_worker.py b/im_platform/workers/event_worker.py new file mode 100644 index 0000000..2b0ad26 --- /dev/null +++ b/im_platform/workers/event_worker.py @@ -0,0 +1,13 @@ + +import asyncio + +QUEUE = asyncio.Queue() + +async def push_event(event): + await QUEUE.put(event) + +async def worker(): + + while True: + event = await QUEUE.get() + print("process event", event) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..c5baa02 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,9 @@ + +[project] +name = "im_platform" +version = "1.0.0" +dependencies = [ +"ahserver", +"cryptography", +"redis" +]