diff --git a/2_1banben/routes/api.py b/2_1banben/routes/api.py index ed9ca8c..df489ac 100644 --- a/2_1banben/routes/api.py +++ b/2_1banben/routes/api.py @@ -7,7 +7,9 @@ from sqlalchemy import desc, or_ from extensions import db from models import Device, DeviceHistory, MaintenanceLog -# --- 尝试导入服务模块 --- +# ========================================================= +# 模块动态导入 (防止循环引用或缺失报错) +# ========================================================= try: from services.core import execute_monitor_task except ImportError: @@ -28,6 +30,7 @@ api_bp = Blueprint('api', __name__, url_prefix='/api') def calculate_offset(latest_time_str): """ 计算时间滞后天数 + 用于前端展示设备数据是否过时 """ if not latest_time_str or latest_time_str == "N/A": return "从未同步" @@ -44,6 +47,7 @@ def calculate_offset(latest_time_str): def check_data_quality(content_data, source_type, data_time_str=None): """ 数据质量分析算法 (融合版:旧版核心规则 + 新版夜间/IoT过滤) + 用于判断设备状态颜色 (绿色ok/黄色warning/红色error) """ if not content_data: return 'ok' @@ -169,49 +173,61 @@ def check_data_quality(content_data, source_type, data_time_str=None): def save_iot_cards_to_db(card_list): """ - [新功能] IoT数据入库逻辑 + [核心修复] IoT数据入库逻辑 - 增量更新模式 1. 只操作 source='iot_card' 的记录。 - 2. 必须保留 is_whitelist 状态,防止被自动同步覆盖。 + 2. 核心:使用 'update' 逻辑而不是 'replace' 逻辑。 + 即使自动任务运行,也不会弄丢白名单 (is_whitelist) 或其他已存在的数据。 """ if not card_list: return 0, None update_count = 0 try: for card in card_list: - iccid = card.get('iccid') + iccid = card.get('iccid') or card.get('card_id') # 兼容字段名 if not iccid: continue - # 1. 查找是否存在 + # 1. 查找是否存在该 SIM 卡记录 sim_record = Device.query.filter_by(name=iccid, source='iot_card').first() - current_whitelist = False + + # 初始化旧数据容器 + old_json = {} if not sim_record: - # 插入新数据 + # 插入新卡片 sim_record = Device(name=iccid, source='iot_card', install_site="IoT库") db.session.add(sim_record) - db.session.flush() + db.session.flush() # 立即获取ID else: - # 旧卡:读取并保留旧的白名单设置 + # 旧卡:尝试读取现有 JSON,确保不丢失之前的数据 try: - old_json = json.loads(sim_record.json_data) - current_whitelist = old_json.get('is_whitelist', False) + if sim_record.json_data: + old_json = json.loads(sim_record.json_data) except: - current_whitelist = False + old_json = {} - # 2. 更新字段 - sim_record.status = str(card.get('cardStatus', '')) - - card_data = { + # 2. 准备需要更新的 API 数据 (只更新变动的字段) + api_updates = { "iccid": iccid, "usedTraffic": str(card.get('usedTraffic') or '0'), "stopDate": card.get('stopDate', 'N/A'), "cardStatus": card.get('cardStatus'), - "tag": card.get('tag', ''), - "is_whitelist": current_whitelist # 写回保留的状态 + "tag": card.get('tag', '') } - sim_record.json_data = json.dumps(card_data, ensure_ascii=False) + # 3. [关键步骤] 合并数据 + # 如果 old_json 里有 is_whitelist,update 不会覆盖它,因为 api_updates 里没有这个key + old_json.update(api_updates) + + # 4. 兜底保障:如果这是新卡,或者旧卡丢失了 whitelist 字段,默认设为 False + if 'is_whitelist' not in old_json: + old_json['is_whitelist'] = False + + # 5. 更新数据库字段 + sim_record.status = str(card.get('cardStatus', '')) + # 序列化并写回 + sim_record.json_data = json.dumps(old_json, ensure_ascii=False) sim_record.check_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + update_count += 1 return update_count, None @@ -429,15 +445,19 @@ def run_monitor(): device.latest_time = target_time device.check_time = current_time - # [新代码逻辑] 合并模式 (update),防止覆盖掉 bound_iccid + # [关键逻辑] 合并模式 (update),防止覆盖掉 bound_iccid + # 先读取数据库里已有的 json_data old_json = {} try: - old_json = json.loads(device.json_data) + if device.json_data: + old_json = json.loads(device.json_data) except: - pass + old_json = {} + # 只有当 raw_json 是字典时才进行合并 new_json = d_raw if isinstance(d_raw, dict) else item.get('raw_json', {}) if isinstance(new_json, dict): + # 使用 update 方法,这样 old_json 里存在的 bound_iccid 不会被删掉 old_json.update(new_json) device.json_data = json.dumps(old_json, ensure_ascii=False) @@ -461,6 +481,7 @@ def run_monitor(): # --- B. 执行 IoT 同步 (写入数据库) --- if sync_iot_data_service: iot_list = sync_iot_data_service() + # 复用已修复的 save_iot_cards_to_db,确保不会丢失数据 c, e = save_iot_cards_to_db(iot_list) if e: msg_list.append(f"IoT错: {e}")