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
|
base_url: str = "" # 工单系统地址,如 http://aiot-platform.viewsh.com:48080
|
||||||
app_id: str = "" # 应用ID
|
app_id: str = "" # 应用ID
|
||||||
app_secret: str = "" # 应用密钥
|
app_secret: str = "" # 应用密钥
|
||||||
|
tenant_id: str = "1" # 租户编号
|
||||||
timeout: int = 10 # 请求超时(秒)
|
timeout: int = 10 # 请求超时(秒)
|
||||||
enabled: bool = False
|
enabled: bool = False
|
||||||
|
|
||||||
@@ -195,6 +196,7 @@ def load_settings() -> Settings:
|
|||||||
base_url=os.getenv("WORK_ORDER_BASE_URL", ""),
|
base_url=os.getenv("WORK_ORDER_BASE_URL", ""),
|
||||||
app_id=os.getenv("WORK_ORDER_APP_ID", ""),
|
app_id=os.getenv("WORK_ORDER_APP_ID", ""),
|
||||||
app_secret=os.getenv("WORK_ORDER_APP_SECRET", ""),
|
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")),
|
timeout=int(os.getenv("WORK_ORDER_TIMEOUT", "10")),
|
||||||
enabled=os.getenv("WORK_ORDER_ENABLED", "false").lower() == "true",
|
enabled=os.getenv("WORK_ORDER_ENABLED", "false").lower() == "true",
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -83,12 +83,51 @@ class WeChatService:
|
|||||||
return self._access_token
|
return self._access_token
|
||||||
|
|
||||||
def save_response_code(self, task_id: str, response_code: str):
|
def save_response_code(self, task_id: str, response_code: str):
|
||||||
"""保存卡片的 response_code(用于后续更新卡片状态)"""
|
"""保存卡片的 response_code(内存缓存 + 数据库持久化)"""
|
||||||
self._response_codes[task_id] = 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]:
|
def get_response_code(self, task_id: str) -> Optional[str]:
|
||||||
"""获取并消耗 response_code(只能用一次)"""
|
"""获取 response_code(优先内存缓存,回退数据库查询)"""
|
||||||
return self._response_codes.pop(task_id, None)
|
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._base_url = ""
|
||||||
self._app_id = ""
|
self._app_id = ""
|
||||||
self._app_secret = ""
|
self._app_secret = ""
|
||||||
|
self._tenant_id = "1"
|
||||||
self._timeout = 10
|
self._timeout = 10
|
||||||
|
|
||||||
def init(self, config):
|
def init(self, config):
|
||||||
@@ -34,6 +35,7 @@ class WorkOrderClient:
|
|||||||
self._base_url = config.base_url.rstrip("/")
|
self._base_url = config.base_url.rstrip("/")
|
||||||
self._app_id = config.app_id
|
self._app_id = config.app_id
|
||||||
self._app_secret = config.app_secret
|
self._app_secret = config.app_secret
|
||||||
|
self._tenant_id = getattr(config, "tenant_id", "1")
|
||||||
self._timeout = getattr(config, "timeout", 10)
|
self._timeout = getattr(config, "timeout", 10)
|
||||||
|
|
||||||
if self._enabled:
|
if self._enabled:
|
||||||
@@ -45,29 +47,27 @@ class WorkOrderClient:
|
|||||||
def enabled(self) -> bool:
|
def enabled(self) -> bool:
|
||||||
return self._enabled
|
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 签名
|
||||||
|
|
||||||
签名算法: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 = (
|
header_str = f"appId={self._app_id}&nonce={nonce}×tamp={timestamp}"
|
||||||
body_json
|
raw = f"{query_str}{body_json}{header_str}{self._app_secret}"
|
||||||
+ "appId=" + self._app_id
|
|
||||||
+ "&nonce=" + nonce
|
|
||||||
+ "×tamp=" + timestamp
|
|
||||||
+ self._app_secret
|
|
||||||
)
|
|
||||||
return hashlib.sha256(raw.encode("utf-8")).hexdigest()
|
return hashlib.sha256(raw.encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
def _build_headers(self, body_json: str) -> dict:
|
def _build_headers(self, body_json: str) -> dict:
|
||||||
"""构造请求头(含签名)"""
|
"""构造请求头(含签名 + tenant-id)"""
|
||||||
nonce = uuid.uuid4().hex[:16]
|
nonce = uuid.uuid4().hex[:16]
|
||||||
timestamp = str(int(time.time() * 1000))
|
timestamp = str(int(time.time() * 1000))
|
||||||
sign = self._sign(body_json, nonce, timestamp)
|
sign = self._sign(body_json, nonce, timestamp)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
"tenant-id": self._tenant_id,
|
||||||
"appId": self._app_id,
|
"appId": self._app_id,
|
||||||
"nonce": nonce,
|
"nonce": nonce,
|
||||||
"timestamp": timestamp,
|
"timestamp": timestamp,
|
||||||
|
|||||||
Reference in New Issue
Block a user