v0.5.5: 应急页 v0.5.4 + 移除IT设备升级 + admin登录修复 + 内容审核架构 + 知识库
This commit is contained in:
@@ -463,6 +463,101 @@ class WecomService:
|
||||
logger.error(f"获取部门成员网络错误: dept_id={department_id}, error={e}")
|
||||
raise Exception(f"获取部门成员网络错误: {e}") from e
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# JS-SDK 票据 (v0.5.4:应急页身份检测用)
|
||||
# --------------------------------------------------------------------------
|
||||
async def get_jsapi_ticket(self) -> str:
|
||||
"""获取企微 JS-SDK 票据 jsapi_ticket。
|
||||
|
||||
对应企微API:
|
||||
GET https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=TOKEN
|
||||
|
||||
jsapi_ticket 用于计算 JS-SDK 签名(sha1),让前端 wx.config/wx.agentConfig 鉴权通过。
|
||||
有效期 7200 秒,缓存到 Redis(提前 300 秒刷新)。
|
||||
|
||||
Returns:
|
||||
str: jsapi_ticket 字符串
|
||||
|
||||
Raises:
|
||||
Exception: 获取失败
|
||||
"""
|
||||
cache_key = "wecom:jsapi_ticket"
|
||||
|
||||
# 1. Redis 缓存
|
||||
if self.redis:
|
||||
try:
|
||||
cached = await self.redis.get(cache_key)
|
||||
if cached:
|
||||
logger.debug("从缓存获取 jsapi_ticket")
|
||||
return cached.decode("utf-8")
|
||||
except Exception as e:
|
||||
logger.warning(f"Redis 读取 jsapi_ticket 失败(降级): {e}")
|
||||
|
||||
# 2. 调用企微 API
|
||||
access_token = await self.get_access_token()
|
||||
url = f"https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token={access_token}"
|
||||
|
||||
try:
|
||||
response = await self.client.get(url)
|
||||
result = response.json()
|
||||
|
||||
if result.get("errcode", 0) != 0:
|
||||
logger.error(
|
||||
f"获取 jsapi_ticket 失败: "
|
||||
f"errcode={result.get('errcode')}, errmsg={result.get('errmsg')}"
|
||||
)
|
||||
raise Exception(f"获取 jsapi_ticket 失败: {result.get('errmsg')}")
|
||||
|
||||
ticket = result.get("ticket", "")
|
||||
expires_in = result.get("expires_in", 7200)
|
||||
|
||||
# 3. 缓存到 Redis(TTL = expires_in - 300s)
|
||||
cache_ttl = max(expires_in - 300, 60)
|
||||
if self.redis:
|
||||
try:
|
||||
await self.redis.setex(cache_key, cache_ttl, ticket)
|
||||
except Exception as e:
|
||||
logger.warning(f"Redis 写入 jsapi_ticket 失败(降级): {e}")
|
||||
|
||||
logger.info(f"jsapi_ticket 获取成功,缓存 TTL={cache_ttl}秒")
|
||||
return ticket
|
||||
|
||||
except httpx.HTTPError as e:
|
||||
logger.error(f"获取 jsapi_ticket 网络错误: {e}")
|
||||
raise Exception(f"企微API网络错误: {e}") from e
|
||||
|
||||
@staticmethod
|
||||
def generate_jsapi_signature(
|
||||
ticket: str, nonce_str: str, timestamp: int, url: str
|
||||
) -> str:
|
||||
"""生成 JS-SDK 签名(sha1)。
|
||||
|
||||
对应企微JS-SDK签名算法:
|
||||
1. 拼接:jsapi_ticket={ticket}&noncestr={nonce_str}×tamp={timestamp}&url={url}
|
||||
2. sha1(拼接字符串)
|
||||
|
||||
注意:
|
||||
- url 不含 # 及其后面部分
|
||||
- url 不含 ?
|
||||
- url 是前端调用 wx.config 的页面 URL
|
||||
|
||||
Args:
|
||||
ticket: jsapi_ticket
|
||||
nonce_str: 随机字符串(前端生成,16位)
|
||||
timestamp: 当前时间戳(秒)
|
||||
url: 当前页面 URL(不含 # 后面)
|
||||
|
||||
Returns:
|
||||
str: sha1 签名字符串(40 字符)
|
||||
"""
|
||||
import hashlib
|
||||
|
||||
# 拼接签名字符串
|
||||
raw = f"jsapi_ticket={ticket}&noncestr={nonce_str}×tamp={timestamp}&url={url}"
|
||||
# sha1 哈希
|
||||
signature = hashlib.sha1(raw.encode("utf-8")).hexdigest()
|
||||
return signature
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 上传临时素材
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user