Files
ZDXX/2_1banben/app.py

206 lines
7.5 KiB
Python
Raw Normal View History

2026-01-09 12:48:50 +08:00
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:
2026-01-13 14:50:23 +08:00
# [新增] 导入配置类
from config import Config
# 数据库实例
2026-01-09 12:48:50 +08:00
from extensions import db
2026-01-13 14:50:23 +08:00
# 数据模型
from models import Device, DeviceHistory
2026-01-09 12:48:50 +08:00
2026-01-13 14:50:23 +08:00
# 核心业务逻辑 (爬虫)
2026-01-09 12:48:50 +08:00
from services.core import execute_monitor_task
2026-01-13 14:50:23 +08:00
# [新增] 核心业务逻辑 (IoT) - 用于定时任务
from services.iot_api import sync_iot_data_service
# 路由蓝图
2026-01-09 12:48:50 +08:00
try:
from routes.api import api_bp as device_bp
2026-01-13 14:50:23 +08:00
# [新增] 导入保存逻辑,供定时任务复用
from routes.api import save_iot_cards_to_db, calculate_offset
2026-01-09 12:48:50 +08:00
except ImportError:
2026-01-13 14:50:23 +08:00
from routes.api import device_bp, save_iot_cards_to_db, calculate_offset
2026-01-09 12:48:50 +08:00
except ImportError as e:
print(f"❌ 严重错误: 模块导入失败。请检查文件名和变量名。详细信息: {e}")
sys.exit(1)
# ==============================================================================
2026-01-13 14:50:23 +08:00
# 2. 路径计算 (辅助静态文件服务)
2026-01-09 12:48:50 +08:00
# ==============================================================================
2026-01-13 14:50:23 +08:00
# 注意:Config 类中已经处理了数据库路径,这里主要处理 web_dist 静态资源路径
2026-01-09 12:48:50 +08:00
def get_base_path():
if getattr(sys, 'frozen', False):
if hasattr(sys, '_MEIPASS'):
2026-01-13 14:50:23 +08:00
return sys._MEIPASS
2026-01-09 12:48:50 +08:00
else:
2026-01-13 14:50:23 +08:00
return os.path.dirname(os.path.abspath(sys.executable))
2026-01-09 12:48:50 +08:00
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')
2026-01-13 14:50:23 +08:00
# 修复 Windows MIME 类型
2026-01-09 12:48:50 +08:00
mimetypes.add_type('application/javascript', '.js')
mimetypes.add_type('text/css', '.css')
# ==============================================================================
2026-01-13 14:50:23 +08:00
# 3. 定时任务逻辑 (同时运行 爬虫 + IoT同步)
2026-01-09 12:48:50 +08:00
# ==============================================================================
def auto_monitor_job(app):
"""定时任务具体执行逻辑"""
with app.app_context():
print(f"⏰ [定时任务] 启动时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
2026-01-13 14:50:23 +08:00
# --- 任务 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}")
# 统一提交事务
2026-01-09 12:48:50 +08:00
try:
db.session.commit()
2026-01-13 14:50:23 +08:00
print("💾 [定时任务] 数据库事务提交完成")
2026-01-09 12:48:50 +08:00
except Exception as e:
db.session.rollback()
2026-01-13 14:50:23 +08:00
print(f"❌ [定时任务] 提交失败: {e}")
2026-01-09 12:48:50 +08:00
# ==============================================================================
# 4. Flask 应用工厂
# ==============================================================================
def create_app():
2026-01-13 14:50:23 +08:00
# 指定静态文件夹
2026-01-09 12:48:50 +08:00
app = Flask(__name__, static_folder=STATIC_FOLDER)
CORS(app)
2026-01-13 14:50:23 +08:00
# 1. 确保 instance 目录存在
2026-01-09 12:48:50 +08:00
if not os.path.exists(INSTANCE_FOLDER):
os.makedirs(INSTANCE_FOLDER, exist_ok=True)
2026-01-13 14:50:23 +08:00
# ==========================================================
# ✅ 2. 核心修复:加载 config.py 中的配置
# ==========================================================
app.config.from_object(Config)
# 打印一下关键配置,确保 IoT 配置已加载 (调试用)
# print(f"DEBUG Config Loaded: IOT_APP_ID={app.config.get('IOT_APP_ID')}")
2026-01-09 12:48:50 +08:00
2026-01-13 14:50:23 +08:00
# 3. 初始化扩展
2026-01-09 12:48:50 +08:00
db.init_app(app)
scheduler = APScheduler()
scheduler.init_app(app)
scheduler.start()
2026-01-13 14:50:23 +08:00
# 4. 添加定时任务 (每天 12:00)
2026-01-09 12:48:50 +08:00
scheduler.add_job(
id='daily_monitor_task',
func=auto_monitor_job,
args=[app],
trigger='cron',
2026-01-12 15:57:34 +08:00
hour=12,
2026-01-09 12:48:50 +08:00
minute=0
)
2026-01-13 14:50:23 +08:00
# 5. 注册路由蓝图
2026-01-09 12:48:50 +08:00
app.register_blueprint(device_bp)
# -------------------------------------------------
2026-01-13 14:50:23 +08:00
# 前端路由支持
2026-01-09 12:48:50 +08:00
# -------------------------------------------------
@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("🚀 服务启动中...")
2026-01-13 14:50:23 +08:00
# 注意:use_reloader=False 防止定时任务执行两次
2026-01-09 12:48:50 +08:00
app.run(host='0.0.0.0', port=5000, debug=debug_mode, use_reloader=False)