Files
ZDXX/2_1banben/app.py
2026-01-13 14:50:23 +08:00

206 lines
7.5 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
from datetime import datetime
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
# [新增] 核心业务逻辑 (IoT) - 用于定时任务
from services.iot_api import sync_iot_data_service
# 路由蓝图
try:
from routes.api import api_bp as device_bp
# [新增] 导入保存逻辑,供定时任务复用
from routes.api import save_iot_cards_to_db, calculate_offset
except ImportError:
from routes.api import device_bp, save_iot_cards_to_db, calculate_offset
except ImportError as e:
print(f"❌ 严重错误: 模块导入失败。请检查文件名和变量名。详细信息: {e}")
sys.exit(1)
# ==============================================================================
# 2. 路径计算 (辅助静态文件服务)
# ==============================================================================
# 注意Config 类中已经处理了数据库路径,这里主要处理 web_dist 静态资源路径
def get_base_path():
if getattr(sys, 'frozen', False):
if hasattr(sys, '_MEIPASS'):
return sys._MEIPASS
else:
return os.path.dirname(os.path.abspath(sys.executable))
else:
return os.path.abspath(os.path.dirname(__file__))
BASE_DIR = get_base_path()
STATIC_FOLDER = os.path.join(BASE_DIR, 'web_dist')
INSTANCE_FOLDER = os.path.join(BASE_DIR, 'instance')
# 修复 Windows MIME 类型
mimetypes.add_type('application/javascript', '.js')
mimetypes.add_type('text/css', '.css')
# ==============================================================================
# 3. 定时任务逻辑 (同时运行 爬虫 + IoT同步)
# ==============================================================================
def auto_monitor_job(app):
"""定时任务具体执行逻辑"""
with app.app_context():
print(f"⏰ [定时任务] 启动时间: {datetime.now().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
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()
device.status = item.get('status')
device.current_value = item.get('value')
device.latest_time = item.get('target_time')
device.check_time = current_time
device.json_data = json.dumps(item.get('raw_json', {}), ensure_ascii=False)
device.offset = calculate_offset(item.get('target_time'))
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}")
# --- 任务 B: IoT 同步 (新增) ---
if sync_iot_data_service:
try:
# 1. 获取数据
iot_list = sync_iot_data_service()
# 2. 保存入库 (复用 api.py 中的逻辑)
count_iot, err = save_iot_cards_to_db(iot_list)
if err:
print(f"❌ [定时任务-IoT] 错误: {err}")
else:
print(f"✅ [定时任务-IoT] 更新 {count_iot}")
except Exception as e:
print(f"❌ [定时任务-IoT] 异常: {e}")
# 统一提交事务
try:
db.session.commit()
print("💾 [定时任务] 数据库事务提交完成")
except Exception as e:
db.session.rollback()
print(f"❌ [定时任务] 提交失败: {e}")
# ==============================================================================
# 4. Flask 应用工厂
# ==============================================================================
def create_app():
# 指定静态文件夹
app = Flask(__name__, static_folder=STATIC_FOLDER)
CORS(app)
# 1. 确保 instance 目录存在
if not os.path.exists(INSTANCE_FOLDER):
os.makedirs(INSTANCE_FOLDER, exist_ok=True)
# ==========================================================
# ✅ 2. 核心修复:加载 config.py 中的配置
# ==========================================================
app.config.from_object(Config)
# 打印一下关键配置,确保 IoT 配置已加载 (调试用)
# print(f"DEBUG Config Loaded: IOT_APP_ID={app.config.get('IOT_APP_ID')}")
# 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
)
# 5. 注册路由蓝图
app.register_blueprint(device_bp)
# -------------------------------------------------
# 前端路由支持
# -------------------------------------------------
@app.route('/')
def serve_index():
if not os.path.exists(os.path.join(app.static_folder, 'index.html')):
return "❌ 错误: 前端文件丢失 (web_dist/index.html)", 404
return send_from_directory(app.static_folder, 'index.html')
@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') or path.startswith('static'):
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
if __name__ == '__main__':
app = create_app()
debug_mode = not getattr(sys, 'frozen', False)
print("🚀 服务启动中...")
# 注意use_reloader=False 防止定时任务执行两次
app.run(host='0.0.0.0', port=5000, debug=debug_mode, use_reloader=False)