Spaces:
Running
Running
Update pages/monitor.py
Browse files- pages/monitor.py +59 -30
pages/monitor.py
CHANGED
|
@@ -5,13 +5,17 @@ import json
|
|
| 5 |
import os
|
| 6 |
import threading
|
| 7 |
import re
|
|
|
|
| 8 |
from datetime import datetime, timedelta, timezone, time as dt_time
|
| 9 |
from collections import deque
|
| 10 |
from dotenv import load_dotenv
|
| 11 |
|
| 12 |
-
# --- 0.
|
| 13 |
st.set_page_config(page_title="影城防撤场监控系统", page_icon="🛡️", layout="wide")
|
| 14 |
|
|
|
|
|
|
|
|
|
|
| 15 |
# 尝试导入自动刷新组件
|
| 16 |
try:
|
| 17 |
from streamlit_autorefresh import st_autorefresh
|
|
@@ -84,7 +88,7 @@ def simplify_hall_name(raw_name):
|
|
| 84 |
return raw_name
|
| 85 |
|
| 86 |
|
| 87 |
-
# --- 3. 微信推送模块 ---
|
| 88 |
class WeChatPusher:
|
| 89 |
def __init__(self, app_id, app_secret):
|
| 90 |
self.app_id = app_id
|
|
@@ -92,9 +96,11 @@ class WeChatPusher:
|
|
| 92 |
self.token = None
|
| 93 |
self.token_expires_time = 0
|
| 94 |
|
| 95 |
-
def get_access_token(self):
|
| 96 |
-
"""获取并缓存微信 Access Token
|
| 97 |
-
|
|
|
|
|
|
|
| 98 |
return self.token
|
| 99 |
|
| 100 |
if not self.app_id or not self.app_secret:
|
|
@@ -116,42 +122,64 @@ class WeChatPusher:
|
|
| 116 |
return None
|
| 117 |
|
| 118 |
def send_template_message(self, user_ids, template_id, data_map):
|
| 119 |
-
"""发送模板消息给多个用户"""
|
| 120 |
-
token = self.get_access_token()
|
| 121 |
-
if not token:
|
| 122 |
-
return False
|
| 123 |
-
|
| 124 |
-
url = f"https://api.weixin.qq.com/cgi-bin/message/template/send?access_token={token}"
|
| 125 |
|
| 126 |
# 组装微信数据格式
|
| 127 |
formatted_data = {}
|
| 128 |
for key, value in data_map.items():
|
| 129 |
-
# 默认深蓝色
|
| 130 |
formatted_data[key] = {"value": value, "color": "#173177"}
|
| 131 |
|
| 132 |
# 清洗 user_ids
|
| 133 |
valid_ids = [uid.strip() for uid in user_ids if uid and uid.strip()]
|
| 134 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
|
| 136 |
for user_id in valid_ids:
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
|
| 154 |
-
return
|
| 155 |
|
| 156 |
|
| 157 |
# --- 4. API 管理模块 ---
|
|
@@ -323,6 +351,7 @@ class CinemaMonitor:
|
|
| 323 |
"keyword4": f"{ticket_count} 张",
|
| 324 |
"remark": "\n请尽快处理。"
|
| 325 |
}
|
|
|
|
| 326 |
if not self.wx_pusher.send_template_message(WX_USER_OPEN_IDS, WX_TEMPLATE_ID_TICKET, data):
|
| 327 |
self.stats["notify_fails"] += 1
|
| 328 |
|
|
|
|
| 5 |
import os
|
| 6 |
import threading
|
| 7 |
import re
|
| 8 |
+
import urllib3
|
| 9 |
from datetime import datetime, timedelta, timezone, time as dt_time
|
| 10 |
from collections import deque
|
| 11 |
from dotenv import load_dotenv
|
| 12 |
|
| 13 |
+
# --- 0. 基础配置 ---
|
| 14 |
st.set_page_config(page_title="影城防撤场监控系统", page_icon="🛡️", layout="wide")
|
| 15 |
|
| 16 |
+
# 屏蔽 HTTPS 证书警告
|
| 17 |
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
| 18 |
+
|
| 19 |
# 尝试导入自动刷新组件
|
| 20 |
try:
|
| 21 |
from streamlit_autorefresh import st_autorefresh
|
|
|
|
| 88 |
return raw_name
|
| 89 |
|
| 90 |
|
| 91 |
+
# --- 3. 微信推送模块 (含自动重试) ---
|
| 92 |
class WeChatPusher:
|
| 93 |
def __init__(self, app_id, app_secret):
|
| 94 |
self.app_id = app_id
|
|
|
|
| 96 |
self.token = None
|
| 97 |
self.token_expires_time = 0
|
| 98 |
|
| 99 |
+
def get_access_token(self, force_refresh=False):
|
| 100 |
+
"""获取并缓存微信 Access Token
|
| 101 |
+
:param force_refresh: 是否强制刷新 Token
|
| 102 |
+
"""
|
| 103 |
+
if not force_refresh and self.token and datetime.now().timestamp() < self.token_expires_time:
|
| 104 |
return self.token
|
| 105 |
|
| 106 |
if not self.app_id or not self.app_secret:
|
|
|
|
| 122 |
return None
|
| 123 |
|
| 124 |
def send_template_message(self, user_ids, template_id, data_map):
|
| 125 |
+
"""发送模板消息给多个用户 (带 40001 错误重试机制)"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
|
| 127 |
# 组装微信数据格式
|
| 128 |
formatted_data = {}
|
| 129 |
for key, value in data_map.items():
|
|
|
|
| 130 |
formatted_data[key] = {"value": value, "color": "#173177"}
|
| 131 |
|
| 132 |
# 清洗 user_ids
|
| 133 |
valid_ids = [uid.strip() for uid in user_ids if uid and uid.strip()]
|
| 134 |
+
|
| 135 |
+
if not valid_ids:
|
| 136 |
+
return False
|
| 137 |
+
|
| 138 |
+
success_any = False
|
| 139 |
|
| 140 |
for user_id in valid_ids:
|
| 141 |
+
# 每个用户最多尝试 2 次(1次正常,1次刷新 Token 后重试)
|
| 142 |
+
for attempt in range(2):
|
| 143 |
+
# 如果是重试(attempt > 0),强制刷新 Token
|
| 144 |
+
token = self.get_access_token(force_refresh=(attempt > 0))
|
| 145 |
+
|
| 146 |
+
if not token:
|
| 147 |
+
break # Token 都拿不到,不用重试了
|
| 148 |
+
|
| 149 |
+
url = f"https://api.weixin.qq.com/cgi-bin/message/template/send?access_token={token}"
|
| 150 |
+
payload = {
|
| 151 |
+
"touser": user_id,
|
| 152 |
+
"template_id": template_id,
|
| 153 |
+
"data": formatted_data
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
try:
|
| 157 |
+
resp = requests.post(url, json=payload, timeout=10)
|
| 158 |
+
result = resp.json()
|
| 159 |
+
errcode = result.get("errcode")
|
| 160 |
+
|
| 161 |
+
if errcode == 0:
|
| 162 |
+
# 发送成功
|
| 163 |
+
# print(f"✅ 微信发送成功 [{user_id}]")
|
| 164 |
+
success_any = True
|
| 165 |
+
break # 跳出重试循环,处理下一个用户
|
| 166 |
+
|
| 167 |
+
# 40001: Token 无效
|
| 168 |
+
# 40014: Token 不合法
|
| 169 |
+
# 42001: Token 过期
|
| 170 |
+
elif errcode in [40001, 40014, 42001]:
|
| 171 |
+
print(f"🔄 微信 Token 失效 ({errcode}),正在刷新重试 [{user_id}]...")
|
| 172 |
+
continue # 进入下一次循环,强制刷新 Token
|
| 173 |
+
|
| 174 |
+
else:
|
| 175 |
+
print(f"❌ 微信发送失败 [{user_id}]: {result}")
|
| 176 |
+
break # 其他错误,不重试
|
| 177 |
+
|
| 178 |
+
except Exception as e:
|
| 179 |
+
print(f"❌ 微信发送异常 [{user_id}]: {e}")
|
| 180 |
+
break
|
| 181 |
|
| 182 |
+
return success_any
|
| 183 |
|
| 184 |
|
| 185 |
# --- 4. API 管理模块 ---
|
|
|
|
| 351 |
"keyword4": f"{ticket_count} 张",
|
| 352 |
"remark": "\n请尽快处理。"
|
| 353 |
}
|
| 354 |
+
# 如果所有用户发送都失败才算失败
|
| 355 |
if not self.wx_pusher.send_template_message(WX_USER_OPEN_IDS, WX_TEMPLATE_ID_TICKET, data):
|
| 356 |
self.stats["notify_fails"] += 1
|
| 357 |
|