Files
ZDXX/2_1banben/app.py

241 lines
8.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
# ==============================================================================
# ✅ 1. 核心模块引用
# ==============================================================================
try:
from config import Config
from extensions import db
from models import Device, DeviceHistory
from services.core import execute_monitor_task
try:
from services.iot_api import sync_iot_data_service
except ImportError:
sync_iot_data_service = None
try:
from routes.api import api_bp as device_bp
from routes.api import calculate_offset
except ImportError:
from routes.api import device_bp, calculate_offset
except ImportError as e:
print(f"❌ 严重错误: 模块导入失败。详细信息: {e}")
sys.exit(1)
# ==============================================================================
# 2. 智能路径配置 (适配 PyInstaller 的 _internal 和 _MEIPASS)
# ==============================================================================
RESOURCE_BASE = Config.BASE_DIR
INSTANCE_PATH = Config.INSTANCE_DIR
def find_static_folder(base_path):
"""
全能路径搜寻逻辑,按优先级查找 web_dist
"""
# 1. PyInstaller 打包后的特殊路径
if getattr(sys, 'frozen', False):
if hasattr(sys, '_MEIPASS'):
mei_path = os.path.join(sys._MEIPASS, 'web_dist')
if os.path.exists(os.path.join(mei_path, 'index.html')):
return mei_path
internal_path = os.path.join(base_path, '_internal', 'web_dist')
if os.path.exists(os.path.join(internal_path, 'index.html')):
return internal_path
# 2. 当前目录 (exe 同级)
path = os.path.join(base_path, 'web_dist')
if os.path.exists(os.path.join(path, 'index.html')):
return path
# 3. 开发环境上一级
parent_path = os.path.join(os.path.dirname(base_path), 'web_dist')
if os.path.exists(os.path.join(parent_path, 'index.html')):
return parent_path
return path
STATIC_FOLDER = find_static_folder(RESOURCE_BASE)
mimetypes.add_type('application/javascript', '.js')
mimetypes.add_type('text/css', '.css')
# ==============================================================================
# 3. 核心定时任务逻辑
# ==============================================================================
def auto_monitor_job(app):
"""
每天 17:00 触发的爬虫任务。
修复:移除不匹配的 create_time 字段,并确保 Session 清理。
"""
with app.app_context():
tz = pytz.timezone('Asia/Shanghai')
now_str = datetime.now(tz).strftime('%Y-%m-%d %H:%M:%S')
print(f"\n{'=' * 50}")
print(f"⏰ [定时任务触发] 北京时间: {now_str}")
if not execute_monitor_task:
return
try:
task_result = execute_monitor_task()
if not task_result:
print("⚠️ [警告] 爬虫执行完毕,但返回空数据")
return
scraped_list = task_result.get('device_list', [])
print(f"📦 [数据获取] 爬取到 {len(scraped_list)} 条设备数据")
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
stats = {'new_device': 0, 'history_added': 0}
for item in scraped_list:
d_name = item.get('name')
if not d_name: continue
f_count = item.get('num_files', 0)
target_date = item.get('target_time')
# A. 更新 Device 表
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()
stats['new_device'] += 1
device.status = item.get('status')
device.current_value = item.get('value')
device.latest_time = target_date
device.check_time = current_time
device.file_count = f_count
device.offset = calculate_offset(target_date)
# 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)
# B. 新增 History 记录
# [修复点] 移除了 create_time 参数,防止报错
history_entry = DeviceHistory(
device_id=device.id,
status=item.get('status'),
result_data=item.get('value'),
data_time=target_date,
json_data=device.json_data,
file_count=f_count
# create_time=datetime.now() # 已删除:你的 models.py 中没有定义这个字段
)
db.session.add(history_entry)
stats['history_added'] += 1
db.session.flush()
db.session.commit()
print(f"✅ [入库成功] 新增设备: {stats['new_device']} | 新增历史: {stats['history_added']}")
except Exception as e:
db.session.rollback()
print(f"❌ [严重异常] 数据写入失败: {e}")
finally:
db.session.remove()
print(f"{'=' * 50}\n")
# ==============================================================================
# 4. Flask 应用工厂
# ==============================================================================
def create_app():
# 调试路径
print(f"🔍 [前端路径锁定] {STATIC_FOLDER}")
if not os.path.exists(os.path.join(STATIC_FOLDER, 'index.html')):
print(f"❌ [严重警告] 仍然无法找到 index.html请检查 PyInstaller 是否将 web_dist 打包进了 _internal 目录。")
app = Flask(__name__, static_folder=STATIC_FOLDER, instance_path=INSTANCE_PATH)
CORS(app)
if not os.path.exists(app.instance_path):
os.makedirs(app.instance_path, exist_ok=True)
app.config.from_object(Config)
db.init_app(app)
scheduler = APScheduler()
scheduler.init_app(app)
scheduler.start()
scheduler.add_job(
id='daily_monitor_task',
func=auto_monitor_job,
args=[app],
trigger='cron',
hour=17,
minute=0,
second=0,
misfire_grace_time=3600,
timezone=pytz.timezone('Asia/Shanghai')
)
print(f"📅 定时任务已锁定: 每天北京时间 17: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():
try:
return send_from_directory(app.static_folder, 'index.html')
except Exception:
return "<h1>错误:找不到前端文件</h1>", 404
@app.route('/<path:path>')
def serve_static(path):
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'):
return jsonify({'code': 404, 'message': 'API endpoint not found'}), 404
try:
return send_from_directory(app.static_folder, 'index.html')
except Exception:
return "Frontend not found", 404
with app.app_context():
db.create_all()
return app
if __name__ == '__main__':
app = create_app()
debug_mode = not getattr(sys, 'frozen', False)
print(f"🚀 服务启动中... 数据库: {app.config['SQLALCHEMY_DATABASE_URI']}")
app.run(host='0.0.0.0', port=5000, debug=debug_mode, use_reloader=False)