diff --git a/1.1/dabao/test1.py b/1.1/dabao/test1.py index dbed975..a74bd83 100644 --- a/1.1/dabao/test1.py +++ b/1.1/dabao/test1.py @@ -26,17 +26,17 @@ def get_base_path(): def get_static_path(): - """获取 Vue 静态资源 dist 的路径""" + """获取 Vue 静态资源 web_dist 的路径""" if getattr(sys, 'frozen', False): # PyInstaller 打包时,资源文件会被解压到 sys._MEIPASS 临时目录 - # 我们需要在打包命令中指定 --add-data "dist;dist" - return os.path.join(sys._MEIPASS, 'dist') + # 我们需要在打包命令中指定 --add-data "web_dist;web_dist" + return os.path.join(sys._MEIPASS, 'web_dist') # 开发环境 - return os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dist') + return os.path.join(os.path.dirname(os.path.abspath(__file__)), 'web_dist') # --- Flask 初始化 --- -# static_folder 指向 Vue 打包后的 dist 目录 +# static_folder 指向 Vue 打包后的 web_dist 目录 # static_url_path='' 表示静态文件不需要 /static 前缀 dist_folder = get_static_path() app = Flask(__name__, static_folder=dist_folder, static_url_path='') @@ -303,7 +303,7 @@ def serve_index(): @app.route('/') def serve_static_files(path): - # 尝试在 dist 目录寻找文件 (css, js, icons) + # 尝试在 web_dist 目录寻找文件 (css, js, icons) file_path = os.path.join(app.static_folder, path) if os.path.exists(file_path): return send_from_directory(app.static_folder, path) @@ -323,5 +323,5 @@ if __name__ == "__main__": scheduler.start() # Host='0.0.0.0' 允许外部IP访问 # Port=5000 (确保 Windows 防火墙开放了此端口) - print("应用正在启动... 请确保 dist 文件夹与脚本/exe 同级或已被打包") + print("应用正在启动... 请确保 web_dist 文件夹与脚本/exe 同级或已被打包") app.run(host='0.0.0.0', port=5000, debug=False, use_reloader=False) diff --git a/1.1/test1.py b/1.1/test1.py index dbed975..a74bd83 100644 --- a/1.1/test1.py +++ b/1.1/test1.py @@ -26,17 +26,17 @@ def get_base_path(): def get_static_path(): - """获取 Vue 静态资源 dist 的路径""" + """获取 Vue 静态资源 web_dist 的路径""" if getattr(sys, 'frozen', False): # PyInstaller 打包时,资源文件会被解压到 sys._MEIPASS 临时目录 - # 我们需要在打包命令中指定 --add-data "dist;dist" - return os.path.join(sys._MEIPASS, 'dist') + # 我们需要在打包命令中指定 --add-data "web_dist;web_dist" + return os.path.join(sys._MEIPASS, 'web_dist') # 开发环境 - return os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dist') + return os.path.join(os.path.dirname(os.path.abspath(__file__)), 'web_dist') # --- Flask 初始化 --- -# static_folder 指向 Vue 打包后的 dist 目录 +# static_folder 指向 Vue 打包后的 web_dist 目录 # static_url_path='' 表示静态文件不需要 /static 前缀 dist_folder = get_static_path() app = Flask(__name__, static_folder=dist_folder, static_url_path='') @@ -303,7 +303,7 @@ def serve_index(): @app.route('/') def serve_static_files(path): - # 尝试在 dist 目录寻找文件 (css, js, icons) + # 尝试在 web_dist 目录寻找文件 (css, js, icons) file_path = os.path.join(app.static_folder, path) if os.path.exists(file_path): return send_from_directory(app.static_folder, path) @@ -323,5 +323,5 @@ if __name__ == "__main__": scheduler.start() # Host='0.0.0.0' 允许外部IP访问 # Port=5000 (确保 Windows 防火墙开放了此端口) - print("应用正在启动... 请确保 dist 文件夹与脚本/exe 同级或已被打包") + print("应用正在启动... 请确保 web_dist 文件夹与脚本/exe 同级或已被打包") app.run(host='0.0.0.0', port=5000, debug=False, use_reloader=False) diff --git a/2_1banben/app.py b/2_1banben/app.py index 270028f..7bcfcbc 100644 --- a/2_1banben/app.py +++ b/2_1banben/app.py @@ -2,7 +2,10 @@ import os import sys import json import mimetypes +import logging from datetime import datetime +import pytz # ✅ 必须引入:用于强制指定北京时间 + from flask import Flask, send_from_directory, jsonify from flask_cors import CORS from flask_apscheduler import APScheduler @@ -11,171 +14,161 @@ from flask_apscheduler import APScheduler # ✅ 1. 核心模块引用 # ============================================================================== try: - # 导入配置类 from config import Config - - # 数据库实例 from extensions import db - - # 数据模型 from models import Device, DeviceHistory - - # 核心业务逻辑 (爬虫) from services.core import execute_monitor_task - # 核心业务逻辑 (IoT) - 用于定时任务 - from services.iot_api import sync_iot_data_service + # from services.iot_api import sync_iot_data_service # 如果不需要IoT可以注释 - # 路由蓝图 try: from routes.api import api_bp as device_bp - # 导入保存逻辑,供定时任务复用 - from routes.api import save_iot_cards_to_db, calculate_offset + from routes.api import calculate_offset except ImportError: - from routes.api import device_bp, save_iot_cards_to_db, calculate_offset + from routes.api import device_bp, calculate_offset except ImportError as e: - print(f"❌ 严重错误: 模块导入失败。请检查文件名和变量名。详细信息: {e}") + print(f"❌ 严重错误: 模块导入失败。详细信息: {e}") sys.exit(1) # ============================================================================== -# 2. 路径计算 (核心修复:区分资源路径和数据路径) +# 2. 路径配置 # ============================================================================== - def get_paths(): - """ - 计算关键路径: - 1. resource_base: 用于存放 web_dist (打包后在临时目录) - 2. data_base: 用于存放数据库 (打包后在 exe 旁边) - """ if getattr(sys, 'frozen', False): - # --- 打包环境 (PyInstaller) --- - - # 资源文件在临时解压目录 (sys._MEIPASS) resource_base = sys._MEIPASS - - # 数据文件(数据库)在 exe 所在目录 (sys.executable 的父目录) data_base = os.path.dirname(sys.executable) else: - # --- 开发环境 --- base = os.path.abspath(os.path.dirname(__file__)) resource_base = base data_base = base - return resource_base, data_base -# 获取路径 RESOURCE_BASE, DATA_BASE = get_paths() - -# 定义具体文件夹路径 STATIC_FOLDER = os.path.join(RESOURCE_BASE, 'web_dist') -# ⚠️ 关键:强制将 instance 文件夹定位到数据目录 (exe旁边),而不是临时目录 INSTANCE_PATH = os.path.join(DATA_BASE, 'instance') -# 修复 Windows MIME 类型 mimetypes.add_type('application/javascript', '.js') mimetypes.add_type('text/css', '.css') # ============================================================================== -# 3. 定时任务逻辑 (保持不变) +# 3. 核心定时任务逻辑 (加强版) # ============================================================================== def auto_monitor_job(app): - """定时任务具体执行逻辑""" + """ + 每天 12:00 触发的爬虫任务 + """ + # ✅ 强制使用应用上下文,确保数据库连接有效 with app.app_context(): - print(f"⏰ [定时任务] 启动时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + # 获取当前北京时间用于日志 + tz = pytz.timezone('Asia/Shanghai') + now_str = datetime.now(tz).strftime('%Y-%m-%d %H:%M:%S') - # --- 任务 A: 爬虫更新 --- - if execute_monitor_task: - try: - task_result = execute_monitor_task() - if task_result: - scraped_list = task_result.get('device_list', []) - current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - count = 0 - for item in scraped_list: - d_name = item.get('name') - if not d_name: continue + print(f"\n{'=' * 50}") + print(f"⏰ [定时任务触发] 北京时间: {now_str}") + print(f"🚀 正在开始执行爬虫逻辑...") - device = Device.query.filter_by(name=d_name).first() - if not device: - device = Device(name=d_name, source=item.get('source'), install_site="") - db.session.add(device) - db.session.flush() + if not execute_monitor_task: + print("❌ 错误: 未找到爬虫执行函数 (execute_monitor_task)") + return - device.status = item.get('status') - device.current_value = item.get('value') - device.latest_time = item.get('target_time') - device.check_time = current_time + try: + # 1. 执行爬取 + task_result = execute_monitor_task() - # =========== ✅ 核心修复开始:防止丢失 bound_iccid =========== + if not task_result: + print("⚠️ [警告] 爬虫执行完毕,但返回空数据 (None)") + return - # 1. 准备容器:先读取数据库里现有的 JSON 数据 - old_json = {} - try: - if device.json_data: - old_json = json.loads(device.json_data) - except Exception: - old_json = {} + scraped_list = task_result.get('device_list', []) + print(f"📦 [数据获取] 爬虫返回了 {len(scraped_list)} 条设备数据") - # 2. 获取爬虫新数据 - new_json = item.get('raw_json', {}) + current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + success_count = 0 - # 3. 合并数据:只更新新爬取到的字段,保留 old_json 里的 bound_iccid - if isinstance(new_json, dict): - old_json.update(new_json) + # 2. 遍历入库 + for item in scraped_list: + d_name = item.get('name') + if not d_name: continue - # 4. 存回数据库 - device.json_data = json.dumps(old_json, ensure_ascii=False) + # 查找或新建设备 + device = Device.query.filter_by(name=d_name).first() + if not device: + print(f"🆕 发现新设备: {d_name},正在创建...") + device = Device(name=d_name, source=item.get('source'), install_site="") + db.session.add(device) + db.session.flush() # 立即获取 ID - # =========== ✅ 核心修复结束 =========== + # 更新设备状态表 + device.status = item.get('status') + device.current_value = item.get('value') + device.latest_time = item.get('target_time') + device.check_time = current_time # 更新检查时间证明爬过了 - device.offset = calculate_offset(item.get('target_time')) + f_count = item.get('num_files', 0) + device.file_count = f_count - db.session.add(DeviceHistory( - device_id=device.id, - status=device.status, - result_data=device.current_value, - data_time=item.get('target_time'), - json_data=device.json_data - )) - count += 1 - print(f"✅ [定时任务-爬虫] 更新 {count} 台") - else: - print("⚠️ [定时任务-爬虫] 未获取到数据") - except Exception as e: - print(f"❌ [定时任务-爬虫] 异常: {e}") - import traceback - traceback.print_exc() + # 计算 offset + device.offset = calculate_offset(item.get('target_time')) + + # JSON 字段合并逻辑 + old_json = {} + try: + if device.json_data: + old_json = json.loads(device.json_data) + except: + old_json = {} + + new_json = item.get('raw_json', {}) + if isinstance(new_json, dict): + old_json.update(new_json) + device.json_data = json.dumps(old_json, ensure_ascii=False) + + # ✅ 3. 写入历史记录 (这是数据留存的关键) + history_entry = DeviceHistory( + device_id=device.id, + status=device.status, + result_data=device.current_value, + data_time=item.get('target_time'), # 文件的时间 + json_data=device.json_data, + file_count=f_count, + create_time=datetime.now() # 记录入库时的系统时间 + ) + db.session.add(history_entry) + success_count += 1 + + # ✅ 4. 显式提交事务 + print(f"💾 正在提交事务到数据库...") + db.session.commit() + print(f"✅ [成功] 已更新 {success_count} 台设备,并写入历史记录。") + print(f"{'=' * 50}\n") + + except Exception as e: + db.session.rollback() # 出错回滚 + print(f"❌ [严重异常] 定时任务执行失败: {e}") + import traceback + traceback.print_exc() # ============================================================================== # 4. Flask 应用工厂 # ============================================================================== def create_app(): - # ⚠️ 关键修改:显式传入 instance_path,告诉 Flask 去哪里找/存 数据库文件 app = Flask(__name__, static_folder=STATIC_FOLDER, instance_path=INSTANCE_PATH) CORS(app) - # 1. 确保 instance 目录存在 (在 exe 旁边创建文件夹) + # 数据库路径配置 if not os.path.exists(app.instance_path): - try: - os.makedirs(app.instance_path, exist_ok=True) - print(f"📁 已创建数据目录: {app.instance_path}") - except OSError as e: - print(f"❌ 无法创建数据目录 {app.instance_path}: {e}") + os.makedirs(app.instance_path, exist_ok=True) - # 2. 加载配置 app.config.from_object(Config) - # ⚠️ 关键修改:强制重写数据库 URI,确保使用绝对路径 - # 即使 Config 里写了,这里也要确保它指向我们刚才计算出的 INSTANCE_PATH - db_name = 'monitor_data.db' # 你的数据库文件名 + db_name = 'monitor_data.db' db_path = os.path.join(app.instance_path, db_name) - # Windows下路径分隔符处理,防止报错 if sys.platform.startswith('win'): app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}' else: @@ -183,36 +176,43 @@ def create_app(): app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False - print(f"💾 数据库路径锁定为: {db_path}") + # ✅ APScheduler 配置 + app.config['SCHEDULER_API_ENABLED'] = True + app.config['SCHEDULER_TIMEZONE'] = "Asia/Shanghai" # 全局时区设置 - # 3. 初始化扩展 db.init_app(app) scheduler = APScheduler() scheduler.init_app(app) scheduler.start() - # 4. 添加定时任务 (每天 12:00) + # ✅ 添加定时任务 (针对常开机环境的最稳配置) scheduler.add_job( id='daily_monitor_task', func=auto_monitor_job, args=[app], trigger='cron', - hour=12, - minute=0 + hour=12, # 每天 12 点 + minute=0, + second=0, + misfire_grace_time=3600, # 允许延迟1小时执行 + timezone=pytz.timezone('Asia/Shanghai') # 再次强制指定时区 ) - # 5. 注册路由蓝图 + # 打印一下确认任务已添加 + print(f"📅 定时任务已锁定: 每天北京时间 12:00 执行") + app.register_blueprint(device_bp) - # ------------------------------------------------- - # 前端路由支持 - # ------------------------------------------------- + # 手动触发测试接口 (保留以备不时之需) + @app.route('/api/force_run') + def force_run_task(): + auto_monitor_job(app) + return jsonify({'code': 200, 'msg': '手动触发成功'}) + + # 前端路由 @app.route('/') def serve_index(): - index_path = os.path.join(app.static_folder, 'index.html') - if not os.path.exists(index_path): - return f"❌ 错误: 前端文件丢失 ({index_path})", 404 return send_from_directory(app.static_folder, 'index.html') @app.route('/') @@ -220,14 +220,11 @@ def create_app(): file_path = os.path.join(app.static_folder, path) if os.path.exists(file_path): return send_from_directory(app.static_folder, path) - - if path.startswith('api') or path.startswith('static'): + if path.startswith('api'): return jsonify({'code': 404, 'message': 'Not Found'}), 404 - return send_from_directory(app.static_folder, 'index.html') with app.app_context(): - # 自动创建表结构 db.create_all() return app @@ -236,6 +233,9 @@ def create_app(): if __name__ == '__main__': app = create_app() debug_mode = not getattr(sys, 'frozen', False) - print("🚀 服务启动中...") - # use_reloader=False 防止定时任务执行两次 + + print("🚀 服务启动中 (24小时常驻模式)...") + + # ✅ 关键设置: use_reloader=False + # 防止 Flask 的热重载功能启动两个进程,导致定时任务跑两遍或者被意外杀掉 app.run(host='0.0.0.0', port=5000, debug=debug_mode, use_reloader=False) \ No newline at end of file diff --git a/2_1banben/config.py b/2_1banben/config.py index f10e48f..a996cd6 100644 --- a/2_1banben/config.py +++ b/2_1banben/config.py @@ -10,10 +10,10 @@ def get_base_path(): def get_static_path(): - """获取 dist 静态资源路径""" + """获取 web_dist 静态资源路径""" if getattr(sys, 'frozen', False): - return os.path.join(sys._MEIPASS, 'dist') - return os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dist') + return os.path.join(sys._MEIPASS, 'web_dist') + return os.path.join(os.path.dirname(os.path.abspath(__file__)), 'web_dist') class Config: diff --git a/2_1banben/models.py b/2_1banben/models.py index 37695ee..8b25284 100644 --- a/2_1banben/models.py +++ b/2_1banben/models.py @@ -1,7 +1,8 @@ +# models.py from datetime import datetime -import json from extensions import db + class Device(db.Model): __tablename__ = 'devices' @@ -18,11 +19,17 @@ class Device(db.Model): reason = db.Column(db.String(255)) offset = db.Column(db.String(50)) + # ✅ 新增字段:文件数量 + file_count = db.Column(db.Integer, default=0) + # 手动录入字段(受保护,run_monitor 不主动覆盖) install_site = db.Column(db.String(100), default="") is_maintaining = db.Column(db.Boolean, default=False) is_hidden = db.Column(db.Boolean, default=False) + # 白名单字段 (根据上下文可能存在,补全以防万一) + is_whitelist = db.Column(db.Boolean, default=False) + def to_dict(self): # 统一状态映射逻辑 api_status = 'offline' if self.status in ['离线', '异常', '已离线'] else 'online' @@ -38,9 +45,12 @@ class Device(db.Model): 'install_site': self.install_site or '', 'is_maintaining': self.is_maintaining, 'is_hidden': self.is_hidden, - 'offset': self.offset + 'is_whitelist': self.is_whitelist, + 'offset': self.offset, + 'file_count': self.file_count # ✅ 返回给前端 } + class DeviceHistory(db.Model): __tablename__ = 'device_history' @@ -53,8 +63,12 @@ class DeviceHistory(db.Model): json_data = db.Column(db.Text) file_path = db.Column(db.String(255)) + # ✅ 新增字段:历史记录文件数量 + file_count = db.Column(db.Integer, default=0) + recorded_at = db.Column(db.DateTime, default=datetime.now) + class MaintenanceLog(db.Model): __tablename__ = 'maintenance_logs' id = db.Column(db.Integer, primary_key=True) diff --git a/2_1banben/routes/api.py b/2_1banben/routes/api.py index d4fbcbf..4e6d256 100644 --- a/2_1banben/routes/api.py +++ b/2_1banben/routes/api.py @@ -255,6 +255,7 @@ def devices_overview(): data_list = [] for d in devices: + # 关键:d.to_dict() 在 models.py 中应包含 file_count item = d.to_dict() # 强制格式化时间 @@ -422,6 +423,10 @@ def run_monitor(): device.latest_time = target_time device.check_time = current_time + # ✅ [核心修改] 获取爬虫返回的文件数量并保存 + f_count = item.get('num_files', 0) + device.file_count = f_count + old_json = {} try: if device.json_data: @@ -436,12 +441,14 @@ def run_monitor(): device.json_data = json.dumps(old_json, ensure_ascii=False) device.offset = calculate_offset(device.latest_time) + # ✅ [核心修改] 写入历史记录时包含 file_count new_history = DeviceHistory( device_id=device.id, status=item.get('status'), result_data=item.get('value'), data_time=target_time, - json_data=device.json_data + json_data=device.json_data, + file_count=f_count # 确保历史数据也记录文件数 ) db.session.add(new_history) count_crawler += 1 @@ -636,4 +643,54 @@ def delete_log_entry(): db.session.delete(log) db.session.commit() return jsonify({'code': 200}) - return jsonify({'code': 404}) \ No newline at end of file + return jsonify({'code': 404}) + + +@api_bp.route('/device_history_list', methods=['GET']) +def get_device_history_list(): + try: + name = request.args.get('name') + page = int(request.args.get('page', 1)) + limit = int(request.args.get('limit', 10)) + + if not name: + return jsonify({'code': 400, 'message': '缺少设备名称'}) + + # 1. 找到设备ID + device = Device.query.filter_by(name=name).first() + if not device: + return jsonify({'code': 404, 'message': '设备不存在'}) + + # 2. 查询历史记录 (按时间倒序) + query = DeviceHistory.query.filter_by(device_id=device.id).order_by(desc(DeviceHistory.data_time)) + + # 3. 获取总数 + total = query.count() + + # 4. 分页切片 + history_list = query.offset((page - 1) * limit).limit(limit).all() + + # 5. 格式化返回数据 + data = [] + for h in history_list: + # 简单处理日期格式,只取日期部分,或者保留完整时间视需求而定 + # 这里假设 data_time 格式为 "YYYY-MM-DD HH:MM:SS" 或 "YYYY_MM_DD..." + date_str = h.data_time + if not date_str: + date_str = h.recorded_at.strftime("%Y-%m-%d %H:%M:%S") if h.recorded_at else "未知" + + data.append({ + 'date': date_str, + 'count': h.file_count or 0 + }) + + return jsonify({ + 'code': 200, + 'data': data, + 'total': total, + 'page': page, + 'limit': limit + }) + + except Exception as e: + return jsonify({'code': 500, 'message': str(e)}) \ No newline at end of file diff --git a/2_1banben/routes/web.py b/2_1banben/routes/web.py index 1b7b4cb..b5b42f4 100644 --- a/2_1banben/routes/web.py +++ b/2_1banben/routes/web.py @@ -7,11 +7,11 @@ web_bp = Blueprint('web', __name__) @web_bp.route('/') def index(): - """访问根路径时,返回 dist/index.html""" + """访问根路径时,返回 web_dist/index.html""" try: return send_from_directory(get_static_path(), 'index.html') except Exception as e: - return f"前端资源未找到,请确认 dist 文件夹是否存在。错误信息: {e}", 404 + return f"前端资源未找到,请确认 web_dist 文件夹是否存在。错误信息: {e}", 404 @web_bp.route('/') def static_files(path): diff --git a/2_1banben/services/core.py b/2_1banben/services/core.py index a4fda5d..8eeae7e 100644 --- a/2_1banben/services/core.py +++ b/2_1banben/services/core.py @@ -1,8 +1,27 @@ # services/core.py import logging import threading -from .crawler_106 import run_106_logic -from .crawler_82 import run_82_logic +import traceback +from datetime import datetime + +# 动态导入,防止文件缺失导致整个程序启动失败 +try: + from .crawler_106 import run_106_logic +except ImportError: + print("⚠️ 警告: 未找到 crawler_106 模块") + + + def run_106_logic(): + return [] + +try: + from .crawler_82 import run_82_logic +except ImportError: + print("⚠️ 警告: 未找到 crawler_82 模块") + + + def run_82_logic(): + return [] task_lock = threading.Lock() @@ -12,26 +31,65 @@ def execute_monitor_task(): 执行所有爬虫,返回一个大列表: {'device_list': [item1, item2...], 'target_time': '...'} """ + # 1. 锁机制:防止任务重复运行 if task_lock.locked(): logging.warning(">>> 任务正在运行中,跳过") + print(">>> ⚠️ 任务正在运行中,本次请求跳过") return None with task_lock: logging.info(">>> 开始执行监控任务...") + print(f"--- [任务开始] {datetime.now().strftime('%H:%M:%S')} ---") - # 1. 获取 106 数据列表 - list_106 = run_106_logic() + all_results = [] - # 2. 获取 82 数据列表 - list_82 = run_82_logic() + # ========================== + # 2. 执行 106 爬虫 + # ========================== + try: + list_106 = run_106_logic() + if list_106: + count = len(list_106) + print(f"✅ 106爬虫获取数据: {count} 条") - # 3. 合并 - combined_list = list_106 + list_82 + # 🔍 [调试] 打印第一条数据,确认 num_files 是否存在 + if count > 0: + first = list_106[0] + print(f" [调试检查] 106样本: {first.get('name')} | num_files={first.get('num_files')}") - logging.info(f">>> 任务完成,共获取 {len(combined_list)} 条数据") + all_results.extend(list_106) + else: + print("⚠️ 106爬虫未返回数据") + except Exception as e: + print(f"❌ 106爬虫执行失败: {e}") + traceback.print_exc() + + # ========================== + # 3. 执行 82 爬虫 + # ========================== + try: + list_82 = run_82_logic() + if list_82: + print(f"✅ 82爬虫获取数据: {len(list_82)} 条") + + # 🛠️ [补全] 82爬虫没有文件数概念,手动补0,防止入库报错 + for item in list_82: + if 'num_files' not in item: + item['num_files'] = 0 + + all_results.extend(list_82) + except Exception as e: + print(f"❌ 82爬虫执行失败: {e}") + traceback.print_exc() + + # ========================== + # 4. 汇总返回 + # ========================== + logging.info(f">>> 任务完成,共获取 {len(all_results)} 条数据") + print(f"--- [任务结束] 总计获取: {len(all_results)} 台设备 ---") return { - 'device_list': combined_list, - 'target_time': None, # 具体时间已在 item 里 - 'temp_file_path': None # 废弃旧逻辑,文件路径已在 item 里 + 'device_list': all_results, + 'target_time': None, # 具体时间已在 item['target_time'] 里 + 'temp_file_path': None # 废弃旧逻辑,文件路径已在 item['temp_file'] 里 } \ No newline at end of file diff --git a/2_1banben/services/crawler_106.py b/2_1banben/services/crawler_106.py index 4a7ac90..de81da9 100644 --- a/2_1banben/services/crawler_106.py +++ b/2_1banben/services/crawler_106.py @@ -52,7 +52,7 @@ def run_106_logic(): """返回 result_list, 每个元素是一个字典""" results = [] print(">>> [106爬虫] 启动...") - today_str = datetime.now().strftime("%Y_%m_%d") + # today_str = datetime.now().strftime("%Y_%m_%d") # ❌ 移除严格的“今天”判断 main_headers = {"Authorization": CONFIG["primary_auth"], "User-Agent": "Mozilla/5.0"} try: @@ -75,7 +75,8 @@ def run_106_logic(): 'value': '', 'target_time': datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 'raw_json': {}, - 'temp_file': None + 'temp_file': None, + 'num_files': 0 # ✅ 默认值 } if str(item.get('status')).lower() != 'online': @@ -96,29 +97,40 @@ def run_106_logic(): headers = {"Authorization": CONFIG["primary_auth"], "x-auth": token} api_root = "/api/resources/Data/" if is_tower_underscore else "/api/resources/data/" + # 1. 获取日期列表 res1 = requests.get(f"http://106.75.72.40:{port}{api_root}", headers=headers, timeout=10) best_date = find_closest_item(res1.json().get('items', []), True) - if not best_date or best_date[2] != today_str: - data_packet['value'] = "未找到今日文件夹" - data_packet['target_time'] = best_date[2] if best_date else "N/A" + # ✅ 修改点:如果找不到任何日期文件夹,才报错。否则,即使是旧日期也继续往下走。 + if not best_date: + data_packet['value'] = "未找到任何日期文件夹" results.append(data_packet) continue - data_packet['target_time'] = best_date[2] # 实际数据时间 + data_packet['target_time'] = best_date[2] # 记录找到的那个日期 (比如 2026_02_02) date_path = f"{api_root}{best_date[2]}/" + + # 2. 请求具体日期的文件夹内容 (这一步能获取 numFiles) res2 = requests.get(f"http://106.75.72.40:{port}{date_path}", headers=headers, timeout=10) - best_file = find_closest_item(res2.json().get('items', []), False) + folder_data = res2.json() # 获取完整JSON + + # ✅ 核心:提取 numFiles (只要请求成功,这里一定能拿到) + file_count = folder_data.get('numFiles', 0) + data_packet['num_files'] = file_count + print(f" -> {name}: 找到日期 {best_date[2]}, 文件数: {file_count}") + + # 3. 找该文件夹里最新的文件 + best_file = find_closest_item(folder_data.get('items', []), False) if not best_file: - data_packet['value'] = "今日文件夹为空" + data_packet['value'] = "文件夹为空" # 这种情况下 numFiles 应该是 0 results.append(data_packet) continue file_item = best_file[1] full_path = file_item.get('path') or f"{date_path}{file_item.get('name')}" - # 核心逻辑:获取内容 + # 4. 下载/读取内容逻辑 if is_tower_i: # 下载二进制文件 download_url = f"http://106.75.72.40:{port}/api/raw{full_path}" @@ -129,9 +141,9 @@ def run_106_logic(): with open(temp_path, 'wb') as f: f.write(res3.content) - data_packet['temp_file'] = temp_path # 🔥 传递给API + data_packet['temp_file'] = temp_path data_packet['value'] = f"Binary Downloaded: {len(res3.content)} bytes" - data_packet['raw_json'] = file_item # 用文件属性充当RawData + data_packet['raw_json'] = file_item # 借用 file_item 充当 raw_json else: data_packet['status'] = '异常' data_packet['value'] = f"下载失败: {res3.status_code}" @@ -141,7 +153,7 @@ def run_106_logic(): res3 = requests.get(file_api_url, headers=headers, timeout=20) try: json_content = res3.json() - data_packet['raw_json'] = json_content # 🔥 完整保存 + data_packet['raw_json'] = json_content data_packet['value'] = json_content.get('content', '') except: data_packet['value'] = "JSON解析失败" diff --git a/zhandianxinxi/光谱数据监控/src/App.vue b/zhandianxinxi/光谱数据监控/src/App.vue index 410e183..b340276 100644 --- a/zhandianxinxi/光谱数据监控/src/App.vue +++ b/zhandianxinxi/光谱数据监控/src/App.vue @@ -5,7 +5,7 @@
- 2.4版本 © 2026 Device Monitor + 2.5版本(加入每日数据个数) © 2026 Device Monitor
diff --git a/zhandianxinxi/光谱数据监控/src/views/Dashboard.vue b/zhandianxinxi/光谱数据监控/src/views/Dashboard.vue index 6d0dd95..8984720 100644 --- a/zhandianxinxi/光谱数据监控/src/views/Dashboard.vue +++ b/zhandianxinxi/光谱数据监控/src/views/Dashboard.vue @@ -103,6 +103,25 @@ + + + +