# ============================================================================= # 企微IT智能服务台 — 企微 JS-SDK 签名 API (v0.5.4 应急页用) # ============================================================================= # 说明:提供前端 wx.config / wx.agentConfig 所需的鉴权签名。 # 对应企微文档:https://developer.work.weixin.qq.com/document/path/90506 # # 流程: # 1. 前端调 GET /api/wecom/jsapi-config?url=xxx 拿签名 # 2. 后端用 jsapi_ticket + url 算 sha1 签名 # 3. 前端用 wx.config({...}) 鉴权后,即可调企微 JS-SDK(如 wx.agentConfig) # # BC/DR 设计:不依赖 session/auth,公开访问(只返回签名,不返回敏感数据) # ============================================================================= import logging import secrets import time from fastapi import APIRouter, Query from app.config import settings from app.dependencies import get_shared_wecom_service from app.utils.response import AppException, success_response logger = logging.getLogger(__name__) router = APIRouter() @router.get("/wecom/jsapi-config") async def get_jsapi_config( url: str = Query(..., description="当前页面 URL(不含 # 及其后)"), ): """获取企微 JS-SDK 鉴权配置。 供前端 wx.config 和 wx.agentConfig 使用。 Returns: { "code": 0, "data": { "corp_id": "wwa8c87970b2011f41", "agent_id": "1000133", "timestamp": 1718500000, "nonce_str": "5K8264ILTKCH...", "signature": "f7c8e9..." } } """ try: wecom_service = get_shared_wecom_service() # 1. 获取 jsapi_ticket ticket = await wecom_service.get_jsapi_ticket() # 2. 生成时间戳和随机串 timestamp = int(time.time()) nonce_str = secrets.token_hex(8) # 16 字符 # 3. 计算签名 signature = wecom_service.generate_jsapi_signature( ticket=ticket, nonce_str=nonce_str, timestamp=timestamp, url=url, ) logger.info( f"生成 JS-SDK 签名: url={url[:80]}... timestamp={timestamp}" ) return success_response( { "corp_id": settings.wecom_corp_id, "agent_id": str(settings.wecom_agent_id), "timestamp": timestamp, "nonce_str": nonce_str, "signature": signature, } ) except Exception as e: logger.error(f"生成 JS-SDK 签名失败: {e}", exc_info=True) raise AppException( code=5001, message=f"生成 JS-SDK 签名失败: {str(e)}", ) from e # ============================================================================= # 应急页身份检测 (v0.5.4) # ============================================================================= # 流程: # 1. 前端用 wx.agentConfig 拿到当前 userid # 2. 前端调 GET /api/wecom/check-role?userid=xxx # 3. 后端用企微通讯录 API 查 userid 是否在"IT支持-咨询坐席"标签里 # 4. 返回 "user" 或 "agent" # ============================================================================= @router.get("/wecom/check-role") async def check_emergency_role( userid: str = Query(..., description="企微 userid"), ): """检测当前账号在应急页场景下的角色。 实现方式(优先级递减): 1. 企微通讯录标签检测(若配置 WECOM_AGENT_TAG_ID) 2. 后台硬编码名单(若配置 WECOM_AGENT_USERIDS 环境变量) 3. 默认 "user" (兜底) Args: userid: 企微 userid(从 wx.agentConfig 拿) Returns: { "code": 0, "data": { "role": "user" | "agent", "userid": "...", "method": "tag" | "hardcoded" | "default" } } """ wecom_service = get_shared_wecom_service() # 方式 1:企微标签检测 tag_id = getattr(settings, "wecom_agent_tag_id", None) if tag_id: try: access_token = await wecom_service.get_access_token() url = f"https://qyapi.weixin.qq.com/cgi-bin/tag/get?access_token={access_token}&tagid={tag_id}" import httpx async with httpx.AsyncClient(timeout=5.0) as client: resp = await client.get(url) result = resp.json() if result.get("errcode", 0) == 0: user_list = result.get("userlist", []) # userlist 元素可能是 str(老版)或 dict(新版带 name) user_ids = [ u if isinstance(u, str) else u.get("userid", "") for u in user_list ] if userid in user_ids: logger.info(f"标签检测: userid={userid} 是坐席") return success_response( {"role": "agent", "userid": userid, "method": "tag"} ) else: logger.info(f"标签检测: userid={userid} 是员工") return success_response( {"role": "user", "userid": userid, "method": "tag"} ) else: logger.warning( f"标签 API 失败: errcode={result.get('errcode')}, " f"errmsg={result.get('errmsg')}, 降级到硬编码" ) except Exception as e: logger.warning(f"标签检测失败(降级): {e}") # 方式 2:硬编码名单 hardcoded = getattr(settings, "wecom_agent_userids", None) if hardcoded: agent_ids = [x.strip() for x in hardcoded.split(",") if x.strip()] if userid in agent_ids: logger.info(f"硬编码名单: userid={userid} 是坐席") return success_response( {"role": "agent", "userid": userid, "method": "hardcoded"} ) else: return success_response( {"role": "user", "userid": userid, "method": "hardcoded"} ) # 方式 3:默认 user logger.info(f"未配置检测方式, userid={userid} 默认 user") return success_response( {"role": "user", "userid": userid, "method": "default"} )