fix: response_code持久化到数据库 + 工单客户端加tenant-id
1. wechat_service: save/get_response_code 改为内存+数据库双写, 容器重启后边缘resolve仍能更新企微卡片 2. work_order_client: 请求头加 tenant-id,签名公式加 query_str 参数 3. config: WorkOrderConfig 新增 tenant_id 字段 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -81,6 +81,7 @@ class WorkOrderConfig:
|
||||
base_url: str = "" # 工单系统地址,如 http://aiot-platform.viewsh.com:48080
|
||||
app_id: str = "" # 应用ID
|
||||
app_secret: str = "" # 应用密钥
|
||||
tenant_id: str = "1" # 租户编号
|
||||
timeout: int = 10 # 请求超时(秒)
|
||||
enabled: bool = False
|
||||
|
||||
@@ -195,6 +196,7 @@ def load_settings() -> Settings:
|
||||
base_url=os.getenv("WORK_ORDER_BASE_URL", ""),
|
||||
app_id=os.getenv("WORK_ORDER_APP_ID", ""),
|
||||
app_secret=os.getenv("WORK_ORDER_APP_SECRET", ""),
|
||||
tenant_id=os.getenv("WORK_ORDER_TENANT_ID", "1"),
|
||||
timeout=int(os.getenv("WORK_ORDER_TIMEOUT", "10")),
|
||||
enabled=os.getenv("WORK_ORDER_ENABLED", "false").lower() == "true",
|
||||
),
|
||||
|
||||
@@ -83,12 +83,51 @@ class WeChatService:
|
||||
return self._access_token
|
||||
|
||||
def save_response_code(self, task_id: str, response_code: str):
|
||||
"""保存卡片的 response_code(用于后续更新卡片状态)"""
|
||||
"""保存卡片的 response_code(内存缓存 + 数据库持久化)"""
|
||||
self._response_codes[task_id] = response_code
|
||||
try:
|
||||
from app.models import get_session, AlarmEventExt
|
||||
db = get_session()
|
||||
try:
|
||||
ext = db.query(AlarmEventExt).filter(
|
||||
AlarmEventExt.alarm_id == task_id,
|
||||
AlarmEventExt.ext_type == "WECHAT_RESPONSE_CODE",
|
||||
).first()
|
||||
if ext:
|
||||
ext.ext_data = {"response_code": response_code}
|
||||
else:
|
||||
ext = AlarmEventExt(
|
||||
alarm_id=task_id,
|
||||
ext_type="WECHAT_RESPONSE_CODE",
|
||||
ext_data={"response_code": response_code},
|
||||
)
|
||||
db.add(ext)
|
||||
db.commit()
|
||||
finally:
|
||||
db.close()
|
||||
except Exception as e:
|
||||
logger.warning(f"持久化 response_code 失败: {e}")
|
||||
|
||||
def get_response_code(self, task_id: str) -> Optional[str]:
|
||||
"""获取并消耗 response_code(只能用一次)"""
|
||||
return self._response_codes.pop(task_id, None)
|
||||
"""获取 response_code(优先内存缓存,回退数据库查询)"""
|
||||
code = self._response_codes.pop(task_id, None)
|
||||
if code:
|
||||
return code
|
||||
try:
|
||||
from app.models import get_session, AlarmEventExt
|
||||
db = get_session()
|
||||
try:
|
||||
ext = db.query(AlarmEventExt).filter(
|
||||
AlarmEventExt.alarm_id == task_id,
|
||||
AlarmEventExt.ext_type == "WECHAT_RESPONSE_CODE",
|
||||
).first()
|
||||
if ext and ext.ext_data:
|
||||
return ext.ext_data.get("response_code", "")
|
||||
finally:
|
||||
db.close()
|
||||
except Exception as e:
|
||||
logger.warning(f"查询 response_code 失败: {e}")
|
||||
return None
|
||||
|
||||
# ==================== 媒体上传 ====================
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ class WorkOrderClient:
|
||||
self._base_url = ""
|
||||
self._app_id = ""
|
||||
self._app_secret = ""
|
||||
self._tenant_id = "1"
|
||||
self._timeout = 10
|
||||
|
||||
def init(self, config):
|
||||
@@ -34,6 +35,7 @@ class WorkOrderClient:
|
||||
self._base_url = config.base_url.rstrip("/")
|
||||
self._app_id = config.app_id
|
||||
self._app_secret = config.app_secret
|
||||
self._tenant_id = getattr(config, "tenant_id", "1")
|
||||
self._timeout = getattr(config, "timeout", 10)
|
||||
|
||||
if self._enabled:
|
||||
@@ -45,29 +47,27 @@ class WorkOrderClient:
|
||||
def enabled(self) -> bool:
|
||||
return self._enabled
|
||||
|
||||
def _sign(self, body_json: str, nonce: str, timestamp: str) -> str:
|
||||
def _sign(self, body_json: str, nonce: str, timestamp: str, query_str: str = "") -> str:
|
||||
"""
|
||||
SHA256 签名
|
||||
|
||||
签名算法:SHA256(body_json + "appId=" + appId + "&nonce=" + nonce + "×tamp=" + timestamp + appSecret)
|
||||
签名算法:SHA256(query_str + body_json + header_str + appSecret)
|
||||
- query_str: Query 参数按 key 字母升序排序拼接,无参数时为空串
|
||||
- header_str: 固定顺序 appId=&nonce=×tamp=
|
||||
"""
|
||||
raw = (
|
||||
body_json
|
||||
+ "appId=" + self._app_id
|
||||
+ "&nonce=" + nonce
|
||||
+ "×tamp=" + timestamp
|
||||
+ self._app_secret
|
||||
)
|
||||
header_str = f"appId={self._app_id}&nonce={nonce}×tamp={timestamp}"
|
||||
raw = f"{query_str}{body_json}{header_str}{self._app_secret}"
|
||||
return hashlib.sha256(raw.encode("utf-8")).hexdigest()
|
||||
|
||||
def _build_headers(self, body_json: str) -> dict:
|
||||
"""构造请求头(含签名)"""
|
||||
"""构造请求头(含签名 + tenant-id)"""
|
||||
nonce = uuid.uuid4().hex[:16]
|
||||
timestamp = str(int(time.time() * 1000))
|
||||
sign = self._sign(body_json, nonce, timestamp)
|
||||
|
||||
return {
|
||||
"Content-Type": "application/json",
|
||||
"tenant-id": self._tenant_id,
|
||||
"appId": self._app_id,
|
||||
"nonce": nonce,
|
||||
"timestamp": timestamp,
|
||||
|
||||
Reference in New Issue
Block a user