Files
inquiry_robot/inquiry-agent/agent/channel/wecom/jsapi.py
T

111 lines
3.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
企微 JS-SDK 签名(给复制页写剪贴板)。
本文件职责:取 jsapi_ticket、按当前页 URL 签名。请求线程只打缓存/短 HTTP。
禁止:把 access_token / ticket 写进页面或日志;禁止在这里改工单。
"""
from __future__ import annotations
import hashlib
import logging
import secrets
import time
from typing import Any, Optional
import httpx
from agent.channel.outbox.sender import WeComAppClient
from agent.config import get_settings
from agent.redis_coord.keys import TOKEN_JSAPI
logger = logging.getLogger(__name__)
_TICKET_TTL = 3600
def build_jsapi_signature(*, url: str, kind: str = "corp") -> dict[str, Any]:
"""
给前端 ww.register 用的签名。
kind=corp → get_jsapi_ticket;kind=agent → agent_config ticket。
URL 必须与浏览器地址栏一致(不含 hash)。
"""
page = (url or "").strip()
if not page:
raise ValueError("url_empty")
ticket = _jsapi_ticket(kind="agent" if kind == "agent" else "corp")
if not ticket:
raise RuntimeError("jsapi_ticket_missing")
nonce = secrets.token_hex(8)
timestamp = str(int(time.time()))
raw = f"jsapi_ticket={ticket}&noncestr={nonce}&timestamp={timestamp}&url={page}"
signature = hashlib.sha1(raw.encode("utf-8")).hexdigest()
return {
"timestamp": int(timestamp),
"nonceStr": nonce,
"signature": signature,
}
def _jsapi_ticket(*, kind: str) -> str:
cached = _cache_get(kind)
if cached:
return cached
settings = get_settings()
if not (settings.wecom_corp_id and settings.wecom_secret):
return ""
client = WeComAppClient(
corp_id=settings.wecom_corp_id,
secret=settings.wecom_secret,
agent_id=int(settings.wecom_agent_id or "0") or 0,
)
token = client.get_access_token()
if kind == "agent":
api = "https://qyapi.weixin.qq.com/cgi-bin/ticket/get"
params = {"access_token": token, "type": "agent_config"}
else:
api = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket"
params = {"access_token": token}
with httpx.Client(timeout=8.0) as http:
data = http.get(api, params=params).json()
errcode = int(data.get("errcode") or 0)
ticket = str(data.get("ticket") or "")
if errcode != 0 or not ticket:
logger.warning("jsapi ticket 失败 kind=%s errcode=%s", kind, errcode)
return ""
_cache_set(kind, ticket)
return ticket
def _cache_get(kind: str) -> Optional[str]:
redis = _try_redis()
if redis is None:
return None
try:
val = redis.raw.get(redis.key(TOKEN_JSAPI, kind))
except Exception: # noqa: BLE001
return None
if val is None:
return None
return val if isinstance(val, str) else val.decode("utf-8")
def _cache_set(kind: str, ticket: str) -> None:
redis = _try_redis()
if redis is None:
return
try:
redis.raw.set(redis.key(TOKEN_JSAPI, kind), ticket, ex=_TICKET_TTL)
except Exception as exc: # noqa: BLE001
logger.warning("jsapi ticket 缓存失败:%s", exc)
def _try_redis():
try:
from agent.redis_coord import get_redis_runtime
return get_redis_runtime(get_settings()).client
except Exception: # noqa: BLE001
return None