提交 d6d89335 authored 作者: PGY's avatar PGY

feat(Mqtt_Universal_Tool): 重构Android信息上报脚本以支持设备配对模式

- 将CSV配置从列表改为字典,区分心跳和设备信息配置
- 修改线程模型:每个线程固定负责一个设备,持续发送心跳和设备信息对
- 调整发送策略:每30秒发送一次心跳和设备信息,而非循环发送
- 增强错误处理:添加连续错误检测,超过阈值自动退出线程
- 优化日志输出:减少冗余日志,按优先级分级显示
- 添加资源监控:集成psutil监控内存和CPU使用情况
- 改进路径处理:修复BOM编码问题和路径导入逻辑
- 优化线程管理:调整线程启动间隔和超时设置
上级 4e9152d6
import csv
csv_file = r'E:\ubains-module-test\ubains-module-test\Mqtt_Universal_Tool\TestData\ReservationSystem_DoorScreen\MQTT设备信息上报_100条.csv'
print("=" * 80)
print("测试不同编码的读取结果")
print("=" * 80)
# 测试1: utf-8 (不带sig)
print("\n【测试1】使用 encoding='utf-8' (Mqtt类使用的编码):")
with open(csv_file, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
configs = [row for row in reader]
first = configs[0]
keys = list(first.keys())[:3]
bom_topic = '\ufefftopic'
print(f" 前3个键: {keys}")
print(f" 第一个键的repr: {repr(keys[0])}")
print(f" topic值: '{first.get('topic', 'MISSING')}'")
print(f" BOM+topic值: '{first.get(bom_topic, 'MISSING')}'")
# 测试2: utf-8-sig (带BOM处理)
print("\n【测试2】使用 encoding='utf-8-sig' (正确处理BOM):")
with open(csv_file, 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
configs = [row for row in reader]
first = configs[0]
keys = list(first.keys())[:3]
print(f" 前3个键: {keys}")
print(f" 第一个键的repr: {repr(keys[0])}")
print(f" topic值: '{first.get('topic', 'MISSING')}'")
print("\n" + "=" * 80)
print("结论: CSV文件有BOM,必须用utf-8-sig编码读取!")
print("=" * 80)
import csv
import os
# 获取当前脚本的绝对路径
current_dir = os.path.dirname(os.path.abspath(__file__))
csv_file = os.path.join(current_dir, '../TestData/ReservationSystem_DoorScreen/MQTT设备信息上报_100条.csv')
print("=" * 80)
print("调试CSV读取结果")
print("=" * 80)
with open(csv_file, 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
configs = [row for row in reader]
if configs:
first = configs[0]
print(f"配置类型: {type(first)}")
print(f"是否为dict: {isinstance(first, dict)}")
if isinstance(first, dict):
all_keys = list(first.keys())
print(f"\n所有键名 ({len(all_keys)}个):")
for i, key in enumerate(all_keys[:15]):
print(f" [{i}] '{key}' -> 值长度:{len(str(first[key])) if first[key] else 0}")
# 查找topic相关键
topic_keys = [k for k in all_keys if 'topic' in k.lower()]
print(f"\n包含'topic'的键: {topic_keys}")
for tk in topic_keys:
value = first.get(tk)
print(f" 键'{tk}'的值: '{value}' (类型:{type(value)}, 长度:{len(str(value)) if value else 0})")
# 检查topic键
topic_value = first.get('topic', 'KEY_NOT_FOUND')
print(f"\nfirst.get('topic'): '{topic_value}' (类型:{type(topic_value)})")
# 尝试直接访问
try:
direct_topic = first['topic']
print(f"first['topic']: '{direct_topic}'")
except KeyError as e:
print(f"first['topic'] 抛出KeyError: {e}")
print("=" * 80)
import os
import sys
import csv
# 获取当前脚本的绝对路径
current_dir = os.path.dirname(os.path.abspath(__file__))
预定系统_path = os.path.abspath(os.path.join(current_dir, '..', '..', '..'))
sys.path.append(预定系统_path)
from 预定系统.Base.Mqtt_Send import Mqtt
# CSV文件路径
csv_file = os.path.join(current_dir, '../TestData/ReservationSystem_DoorScreen/MQTT设备信息上报_100条.csv')
print("=" * 80)
print("调试CSV文件读取")
print("=" * 80)
print(f"文件路径: {csv_file}")
print(f"文件存在: {os.path.exists(csv_file)}")
print()
# 读取配置
configs = Mqtt.read_config_from_csv(csv_file)
print(f"读取到的配置数量: {len(configs)}")
print()
# 检查前5条配置
print("前5条配置的topic字段:")
for i, config in enumerate(configs[:5]):
topic = config.get('topic', 'MISSING')
print(f" [{i}] topic='{topic}', 类型={type(topic)}, 长度={len(str(topic)) if topic else 0}")
# 显示所有键
if i == 0:
print(f"\n第一条配置的所有键 ({len(config.keys())}个):")
for key in config.keys():
value = config[key]
display_value = value if len(str(value)) < 50 else str(value)[:50] + "..."
print(f" - {key}: '{display_value}'")
print()
# 检查是否有空topic
empty_topic_count = sum(1 for c in configs if not c.get('topic'))
print(f"topic为空的配置数量: {empty_topic_count}")
if empty_topic_count > 0:
print("\n⚠️ 发现空topic配置! 这些配置会导致发送失败。")
print("\n空topic配置示例:")
for i, config in enumerate(configs):
if not config.get('topic'):
print(f" [{i}] 完整配置: {dict(list(config.items())[:5])}")
if i >= 2: # 只显示前3个
break
else:
print("✓ 所有配置都有topic字段")
print()
print("=" * 80)
import os
import sys
import csv
# 直接使用csv模块测试,不依赖Mqtt类
csv_file = r'E:\ubains-module-test\ubains-module-test\Mqtt_Universal_Tool\TestData\ReservationSystem_DoorScreen\MQTT设备信息上报_100条.csv'
print("=" * 80)
print("验证CSV配置数据完整性")
print("=" * 80)
print(f"文件路径: {csv_file}")
print(f"文件存在: {os.path.exists(csv_file)}")
print()
# 读取配置
with open(csv_file, 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
configs = [row for row in reader]
print(f"原始配置数量: {len(configs)}")
# 模拟过滤逻辑
valid_configs = []
invalid_count = 0
for config in configs:
if config and isinstance(config, dict) and config.get("topic"):
valid_configs.append(config)
else:
invalid_count += 1
print(f"有效配置数量: {len(valid_configs)}")
print(f"无效配置数量: {invalid_count}")
print()
if invalid_count > 0:
print("⚠️ 发现无效配置!")
print("\n无效配置示例:")
for i, config in enumerate(configs):
if not config or not isinstance(config, dict) or not config.get("topic"):
print(f" [{i}] 配置类型: {type(config)}")
if isinstance(config, dict):
print(f" 所有键: {list(config.keys())[:5]}")
else:
print(f" 值: {config}")
if i >= 2:
break
else:
print("✓ 所有配置都有效!")
print()
# 检查前3条有效配置
if valid_configs:
print("前3条有效配置示例:")
for i, config in enumerate(valid_configs[:3]):
topic = config.get('topic', 'MISSING')
client_id = config.get('clientId', 'MISSING')
conference_name = config.get('conferenceName', 'MISSING')
print(f" [{i}] topic={topic}, clientId={client_id}, conferenceName={conference_name}")
print()
print("=" * 80)
topic,clientId,deviceId
/uams/android/broadcast,48134e6047a19a0001,48134e6047a19a0001
/uams/android/broadcast,48134e6047a19a0002,48134e6047a19a0002
/uams/android/broadcast,48134e6047a19a0003,48134e6047a19a0003
/uams/android/broadcast,48134e6047a19a0004,48134e6047a19a0004
/uams/android/broadcast,48134e6047a19a0005,48134e6047a19a0005
/uams/android/broadcast,48134e6047a19a0006,48134e6047a19a0006
/uams/android/broadcast,48134e6047a19a0007,48134e6047a19a0007
/uams/android/broadcast,48134e6047a19a0008,48134e6047a19a0008
/uams/android/broadcast,48134e6047a19a0009,48134e6047a19a0009
/uams/android/broadcast,48134e6047a19a0010,48134e6047a19a0010
/uams/android/broadcast,48134e6047a19a0011,48134e6047a19a0011
/uams/android/broadcast,48134e6047a19a0012,48134e6047a19a0012
/uams/android/broadcast,48134e6047a19a0013,48134e6047a19a0013
/uams/android/broadcast,48134e6047a19a0014,48134e6047a19a0014
/uams/android/broadcast,48134e6047a19a0015,48134e6047a19a0015
/uams/android/broadcast,48134e6047a19a0016,48134e6047a19a0016
/uams/android/broadcast,48134e6047a19a0017,48134e6047a19a0017
/uams/android/broadcast,48134e6047a19a0018,48134e6047a19a0018
/uams/android/broadcast,48134e6047a19a0019,48134e6047a19a0019
/uams/android/broadcast,48134e6047a19a0020,48134e6047a19a0020
/uams/android/broadcast,48134e6047a19a0021,48134e6047a19a0021
/uams/android/broadcast,48134e6047a19a0022,48134e6047a19a0022
/uams/android/broadcast,48134e6047a19a0023,48134e6047a19a0023
/uams/android/broadcast,48134e6047a19a0024,48134e6047a19a0024
/uams/android/broadcast,48134e6047a19a0025,48134e6047a19a0025
/uams/android/broadcast,48134e6047a19a0026,48134e6047a19a0026
/uams/android/broadcast,48134e6047a19a0027,48134e6047a19a0027
/uams/android/broadcast,48134e6047a19a0028,48134e6047a19a0028
/uams/android/broadcast,48134e6047a19a0029,48134e6047a19a0029
/uams/android/broadcast,48134e6047a19a0030,48134e6047a19a0030
/uams/android/broadcast,48134e6047a19a0031,48134e6047a19a0031
/uams/android/broadcast,48134e6047a19a0032,48134e6047a19a0032
/uams/android/broadcast,48134e6047a19a0033,48134e6047a19a0033
/uams/android/broadcast,48134e6047a19a0034,48134e6047a19a0034
/uams/android/broadcast,48134e6047a19a0035,48134e6047a19a0035
/uams/android/broadcast,48134e6047a19a0036,48134e6047a19a0036
/uams/android/broadcast,48134e6047a19a0037,48134e6047a19a0037
/uams/android/broadcast,48134e6047a19a0038,48134e6047a19a0038
/uams/android/broadcast,48134e6047a19a0039,48134e6047a19a0039
/uams/android/broadcast,48134e6047a19a0040,48134e6047a19a0040
/uams/android/broadcast,48134e6047a19a0041,48134e6047a19a0041
/uams/android/broadcast,48134e6047a19a0042,48134e6047a19a0042
/uams/android/broadcast,48134e6047a19a0043,48134e6047a19a0043
/uams/android/broadcast,48134e6047a19a0044,48134e6047a19a0044
/uams/android/broadcast,48134e6047a19a0045,48134e6047a19a0045
/uams/android/broadcast,48134e6047a19a0046,48134e6047a19a0046
/uams/android/broadcast,48134e6047a19a0047,48134e6047a19a0047
/uams/android/broadcast,48134e6047a19a0048,48134e6047a19a0048
/uams/android/broadcast,48134e6047a19a0049,48134e6047a19a0049
/uams/android/broadcast,48134e6047a19a0050,48134e6047a19a0050
/uams/android/broadcast,48134e6047a19a0051,48134e6047a19a0051
/uams/android/broadcast,48134e6047a19a0052,48134e6047a19a0052
/uams/android/broadcast,48134e6047a19a0053,48134e6047a19a0053
/uams/android/broadcast,48134e6047a19a0054,48134e6047a19a0054
/uams/android/broadcast,48134e6047a19a0055,48134e6047a19a0055
/uams/android/broadcast,48134e6047a19a0056,48134e6047a19a0056
/uams/android/broadcast,48134e6047a19a0057,48134e6047a19a0057
/uams/android/broadcast,48134e6047a19a0058,48134e6047a19a0058
/uams/android/broadcast,48134e6047a19a0059,48134e6047a19a0059
/uams/android/broadcast,48134e6047a19a0060,48134e6047a19a0060
/uams/android/broadcast,48134e6047a19a0061,48134e6047a19a0061
/uams/android/broadcast,48134e6047a19a0062,48134e6047a19a0062
/uams/android/broadcast,48134e6047a19a0063,48134e6047a19a0063
/uams/android/broadcast,48134e6047a19a0064,48134e6047a19a0064
/uams/android/broadcast,48134e6047a19a0065,48134e6047a19a0065
/uams/android/broadcast,48134e6047a19a0066,48134e6047a19a0066
/uams/android/broadcast,48134e6047a19a0067,48134e6047a19a0067
/uams/android/broadcast,48134e6047a19a0068,48134e6047a19a0068
/uams/android/broadcast,48134e6047a19a0069,48134e6047a19a0069
/uams/android/broadcast,48134e6047a19a0070,48134e6047a19a0070
/uams/android/broadcast,48134e6047a19a0071,48134e6047a19a0071
/uams/android/broadcast,48134e6047a19a0072,48134e6047a19a0072
/uams/android/broadcast,48134e6047a19a0073,48134e6047a19a0073
/uams/android/broadcast,48134e6047a19a0074,48134e6047a19a0074
/uams/android/broadcast,48134e6047a19a0075,48134e6047a19a0075
/uams/android/broadcast,48134e6047a19a0076,48134e6047a19a0076
/uams/android/broadcast,48134e6047a19a0077,48134e6047a19a0077
/uams/android/broadcast,48134e6047a19a0078,48134e6047a19a0078
/uams/android/broadcast,48134e6047a19a0079,48134e6047a19a0079
/uams/android/broadcast,48134e6047a19a0080,48134e6047a19a0080
/uams/android/broadcast,48134e6047a19a0081,48134e6047a19a0081
/uams/android/broadcast,48134e6047a19a0082,48134e6047a19a0082
/uams/android/broadcast,48134e6047a19a0083,48134e6047a19a0083
/uams/android/broadcast,48134e6047a19a0084,48134e6047a19a0084
/uams/android/broadcast,48134e6047a19a0085,48134e6047a19a0085
/uams/android/broadcast,48134e6047a19a0086,48134e6047a19a0086
/uams/android/broadcast,48134e6047a19a0087,48134e6047a19a0087
/uams/android/broadcast,48134e6047a19a0088,48134e6047a19a0088
/uams/android/broadcast,48134e6047a19a0089,48134e6047a19a0089
/uams/android/broadcast,48134e6047a19a0090,48134e6047a19a0090
/uams/android/broadcast,48134e6047a19a0091,48134e6047a19a0091
/uams/android/broadcast,48134e6047a19a0092,48134e6047a19a0092
/uams/android/broadcast,48134e6047a19a0093,48134e6047a19a0093
/uams/android/broadcast,48134e6047a19a0094,48134e6047a19a0094
/uams/android/broadcast,48134e6047a19a0095,48134e6047a19a0095
/uams/android/broadcast,48134e6047a19a0096,48134e6047a19a0096
/uams/android/broadcast,48134e6047a19a0097,48134e6047a19a0097
/uams/android/broadcast,48134e6047a19a0098,48134e6047a19a0098
/uams/android/broadcast,48134e6047a19a0099,48134e6047a19a0099
/uams/android/broadcast,48134e6047a19a0100,48134e6047a19a0100
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -3,20 +3,20 @@
#===============================================================================
# 脚本名称:auto_clean_deleted_ubains_v3.sh
# 功能描述:已删除大文件自动清理与容器重启脚本
# 版本:V3.1
# 版本:V3.2
# 创建日期:2026-01-27
# 更新日期:2026-03-30
# 更新日期:2026-04-23
#
# 监测对象:
# 1. 进程占用的已删除大文件(>1GB)
# 1. 进程占用的已删除大文件(>100MB)
# 2. 匹配关键字: ubains-INFO-AND-ERROR
# 3. 自动清理并重启关联容器
# 4. 日志文件自动轮转(5MB轮转、保留7天)
#
# 清理策略:
# 1. 扫描所有进程的fd目录,查找deleted标记文件
# 2. 文件大小超过1GB时执行自动处理
# 3. 强制杀死占用进程
# 2. 文件大小超过100MB时执行自动处理
# 3. 优雅杀死占用进程(先SIGTERM,失败再SIGKILL)
# 4. 重启docker容器ujava2
# 5. 若进程属于特定应用,启动该应用
#
......@@ -30,7 +30,7 @@
# ================= 配置区域 =================
TARGET_KEY="ubains-INFO-AND-ERROR"
MIN_SIZE=$((1024*1024*1024)) # 1GB (单位:字节)
MIN_SIZE=$((100*1024*1024)) # 100MB (单位:字节)
LOG_FILE="/var/log/scripts/auto_clean_deleted_ubains.log"
MAX_LOG_SIZE=$((5*1024*1024)) # 5MB 日志大小限制
LOG_RETENTION_DAYS=7 # 日志保留天数
......@@ -46,6 +46,10 @@ log() {
# 日志轮转
rotate_logs() {
# 确保日志目录存在
log_dir=$(dirname "$LOG_FILE")
[ ! -d "$log_dir" ] && mkdir -p "$log_dir"
if [ -f "$LOG_FILE" ]; then
FILE_SIZE=$(stat -c%s "$LOG_FILE")
if [ "$FILE_SIZE" -ge "$MAX_LOG_SIZE" ]; then
......@@ -54,7 +58,8 @@ rotate_logs() {
log "日志文件超过 5MB,已自动轮转。"
fi
fi
find "$(dirname "$0")" -name "auto_clean_deleted_ubains.log.*" -mtime +$LOG_RETENTION_DAYS -exec rm -f {} \;
# 修复:日志清理路径与日志文件路径保持一致
find "$log_dir" -name "auto_clean_deleted_ubains.log.*" -mtime +$LOG_RETENTION_DAYS -exec rm -f {} \;
}
rotate_logs
......@@ -62,12 +67,13 @@ rotate_logs
log "==============================================="
log "开始扫描:检测 deleted 大文件并自动清理 (内核直读版)"
log "匹配关键字: $TARGET_KEY"
log "大于尺寸: 1GB"
log "大于尺寸: 100MB"
log "==============================================="
FOUND=0
FOUND=0 # 标记是否发现匹配文件
PROCESSED=0 # 标记是否处理了文件(处理才需要重启)
NEED_RESTART=0
NEED_APP_START=0 # 新增:标记是否需要启动特定应用
NEED_APP_START=0 # 标记是否需要启动特定应用
# 遍历所有进程的 fd 目录,寻找匹配关键字且标记为 deleted 的文件
for fd_path in /proc/[0-9]*/fd/*; do
......@@ -79,13 +85,20 @@ for fd_path in /proc/[0-9]*/fd/*; do
# 提取 PID 和 FD
pid=$(echo "$fd_path" | cut -d'/' -f3)
# 检查进程是否存在(避免已退出的进程)
if [ ! -d "/proc/$pid" ]; then
log "进程 $pid 已退出,跳过。"
continue
fi
fd=$(echo "$fd_path" | cut -d'/' -f5)
# 获取进程名
proc_name="unknown"
[ -f "/proc/$pid/comm" ] && proc_name=$(cat "/proc/$pid/comm")
# 获取文件大小 (字节) - 使用 stat -L 获取链接指向的实际文件状态
# 获取文件大小 (字节)
size_bytes=$(stat -L -c %s "$fd_path" 2>/dev/null || echo 0)
size_mb=$((size_bytes / 1024 / 1024))
......@@ -98,20 +111,37 @@ for fd_path in /proc/[0-9]*/fd/*; do
log "大小: $size_mb MB"
if [ "$size_bytes" -ge "$MIN_SIZE" ]; then
log "⚠ 文件超过1GB,执行自动处理。"
log "➡ 杀死进程 PID: $pid"
kill -9 "$pid" 2>/dev/null
sleep 1
NEED_RESTART=1
log "⚠ 文件超过100MB,执行自动处理。"
# 获取进程的当前工作目录,判断是否为特定应用
# 修复:在杀死进程前判断是否为特定应用(进程被杀死后无法获取cwd)
proc_cwd=$(readlink /proc/$pid/cwd 2>/dev/null)
if [ "$proc_cwd" == "$APP_PATH" ]; then
log "检测到被杀死的进程属于特定应用:$APP_PATH,将在容器重启后尝试启动。"
log "检测到进程属于特定应用:$APP_PATH,将在容器重启后尝试启动。"
NEED_APP_START=1
fi
# 修复:先尝试优雅终止(SIGTERM),失败再强制杀死(SIGKILL)
log "➡ 终止进程 PID: $pid(先发送SIGTERM)"
if kill -15 "$pid" 2>/dev/null; then
sleep 2
# 检查进程是否已终止
if [ -d "/proc/$pid" ]; then
log "➡ 进程未响应SIGTERM,发送SIGKILL强制终止"
kill -9 "$pid" 2>/dev/null
sleep 1
else
log "✔ 进程已优雅终止"
fi
else
log "➡ 发送SIGTERM失败,发送SIGKILL强制终止"
kill -9 "$pid" 2>/dev/null
sleep 1
fi
PROCESSED=1
NEED_RESTART=1
else
log "⏩ 文件不足 1GB,跳过。"
log "⏩ 文件不足 100MB,跳过。"
fi
fi
fi
......
......@@ -463,7 +463,7 @@ class Mqtt:
return json.dumps({
"type":"heartbeat",
"clientId" : config['clientId'],
"appId":"com.ubains.uniplatform",
"appId":"com.ubains.local.gviewer",
"deviceId": config['deviceId']
})
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论