打包上传云端版本

This commit is contained in:
YueL1331
2026-01-07 17:36:33 +08:00
parent 3099427eb6
commit af4b4a28c3
17 changed files with 59581 additions and 0 deletions

View File

@ -0,0 +1,38 @@
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['test1.py'],
pathex=[],
binaries=[],
datas=[('dist', 'dist')],
hiddenimports=['flask_sqlalchemy', 'lxml'],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='MonitorSystem',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -0,0 +1,131 @@
This file lists modules PyInstaller was not able to find. This does not
necessarily mean this module is required for running your program. Python and
Python 3rd-party packages include a lot of conditional or optional modules. For
example the module 'ntpath' only exists on Windows, whereas the module
'posixpath' only exists on Posix systems.
Types if import:
* top-level: imported at the top-level - look at these first
* conditional: imported within an if-statement
* delayed: imported within a function
* optional: imported within a try-except-statement
IMPORTANT: Do NOT post this list to the issue-tracker. Use it as a basis for
tracking down the missing module yourself. Thanks!
missing module named pyimod02_importers - imported by D:\Git\Learn-Web-spider\venv\Lib\site-packages\PyInstaller\hooks\rthooks\pyi_rth_pkgutil.py (delayed), D:\Git\Learn-Web-spider\venv\Lib\site-packages\PyInstaller\hooks\rthooks\pyi_rth_pkgres.py (delayed)
missing module named pwd - imported by posixpath (delayed, conditional, optional), shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional), netrc (delayed, conditional), getpass (delayed), http.server (delayed, optional), webbrowser (delayed), distutils.util (delayed, conditional, optional), setuptools._vendor.backports.tarfile (optional), distutils.archive_util (optional), setuptools._distutils.util (delayed, conditional, optional), setuptools._distutils.archive_util (optional)
missing module named grp - imported by shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional), setuptools._vendor.backports.tarfile (optional), distutils.archive_util (optional), setuptools._distutils.archive_util (optional)
missing module named _posixsubprocess - imported by subprocess (conditional), multiprocessing.util (delayed)
missing module named fcntl - imported by subprocess (optional), tornado.platform.posix (top-level)
missing module named org - imported by copy (optional)
missing module named urllib.urlopen - imported by urllib (delayed, optional), lxml.html (delayed, optional)
missing module named urllib.urlencode - imported by urllib (delayed, optional), lxml.html (delayed, optional)
missing module named posix - imported by os (conditional, optional), posixpath (optional), shutil (conditional), importlib._bootstrap_external (conditional)
missing module named resource - imported by posix (top-level)
missing module named _manylinux - imported by packaging._manylinux (delayed, optional), setuptools._vendor.packaging._manylinux (delayed, optional), wheel.vendored.packaging._manylinux (delayed, optional)
missing module named '_typeshed.importlib' - imported by pkg_resources (conditional)
missing module named _typeshed - imported by werkzeug._internal (conditional), pkg_resources (conditional), setuptools.glob (conditional), setuptools.compat.py311 (conditional), setuptools._vendor.jaraco.collections (conditional)
missing module named jnius - imported by setuptools._vendor.platformdirs.android (delayed, conditional, optional)
missing module named android - imported by setuptools._vendor.platformdirs.android (delayed, conditional, optional)
missing module named _frozen_importlib_external - imported by importlib._bootstrap (delayed), importlib (optional), importlib.abc (optional), zipimport (top-level)
missing module named 'distutils._modified' - imported by setuptools._distutils.file_util (delayed)
missing module named 'distutils._log' - imported by setuptools._distutils.command.bdist_dumb (top-level), setuptools._distutils.command.bdist_rpm (top-level), setuptools._distutils.command.build_clib (top-level), setuptools._distutils.command.build_ext (top-level), setuptools._distutils.command.build_py (top-level), setuptools._distutils.command.build_scripts (top-level), setuptools._distutils.command.clean (top-level), setuptools._distutils.command.config (top-level), setuptools._distutils.command.install (top-level), setuptools._distutils.command.install_scripts (top-level), setuptools._distutils.command.sdist (top-level)
missing module named usercustomize - imported by site (delayed, optional)
missing module named sitecustomize - imported by site (delayed, optional)
missing module named readline - imported by code (delayed, conditional, optional), flask.cli (delayed, conditional, optional), rlcompleter (optional), cmd (delayed, conditional, optional), pdb (delayed, optional), site (delayed, optional)
missing module named _scproxy - imported by urllib.request (conditional)
missing module named termios - imported by getpass (optional), tty (top-level), werkzeug._reloader (delayed, optional), click._termui_impl (conditional)
missing module named _posixshmem - imported by multiprocessing.resource_tracker (conditional), multiprocessing.shared_memory (conditional)
missing module named multiprocessing.set_start_method - imported by multiprocessing (top-level), multiprocessing.spawn (top-level)
missing module named multiprocessing.get_start_method - imported by multiprocessing (top-level), multiprocessing.spawn (top-level)
missing module named multiprocessing.get_context - imported by multiprocessing (top-level), multiprocessing.pool (top-level), multiprocessing.managers (top-level), multiprocessing.sharedctypes (top-level)
missing module named multiprocessing.TimeoutError - imported by multiprocessing (top-level), multiprocessing.pool (top-level)
missing module named 'org.python' - imported by pickle (optional), xml.sax (delayed, conditional)
missing module named 'java.lang' - imported by platform (delayed, optional), xml.sax._exceptions (conditional)
missing module named multiprocessing.BufferTooShort - imported by multiprocessing (top-level), multiprocessing.connection (top-level)
missing module named multiprocessing.AuthenticationError - imported by multiprocessing (top-level), multiprocessing.connection (top-level)
missing module named asyncio.DefaultEventLoopPolicy - imported by asyncio (delayed, conditional), asyncio.events (delayed, conditional)
missing module named trove_classifiers - imported by setuptools.config._validate_pyproject.formats (optional)
missing module named importlib_resources - imported by setuptools._vendor.jaraco.text (optional)
excluded module named _frozen_importlib - imported by importlib (optional), importlib.abc (optional), zipimport (top-level)
missing module named vms_lib - imported by platform (delayed, optional)
missing module named java - imported by platform (delayed)
missing module named _winreg - imported by platform (delayed, optional), tzlocal.win32 (optional)
missing module named collections.MutableMapping - imported by collections (optional), html5lib.treebuilders.dom (optional), html5lib.treebuilders.etree_lxml (optional)
missing module named collections.Mapping - imported by collections (optional), html5lib._utils (optional), html5lib._trie._base (optional)
missing module named collections.Callable - imported by collections (optional), socks (optional), bs4.element (optional), bs4.builder._lxml (optional)
missing module named htmlentitydefs - imported by tornado.escape (conditional), lxml.html.soupparser (optional)
missing module named BeautifulSoup - imported by lxml.html.soupparser (optional)
missing module named cchardet - imported by bs4.dammit (optional)
missing module named bs4.builder.HTMLParserTreeBuilder - imported by bs4.builder (top-level), bs4 (top-level)
missing module named StringIO - imported by six (conditional)
missing module named html5lib.treebuilders._base - imported by html5lib.treebuilders (optional), bs4.builder._html5lib (optional), lxml.html._html5builder (top-level)
missing module named html5lib.XHTMLParser - imported by html5lib (optional), lxml.html.html5parser (optional)
runtime module named six.moves - imported by dateutil.tz.tz (top-level), dateutil.tz._factories (top-level), dateutil.tz.win (top-level), dateutil.rrule (top-level), html5lib._inputstream (top-level), six.moves.urllib (top-level), html5lib.filters.sanitizer (top-level)
missing module named six.moves.range - imported by six.moves (top-level), dateutil.rrule (top-level)
missing module named 'genshi.core' - imported by html5lib.treewalkers.genshi (top-level)
missing module named genshi - imported by html5lib.treewalkers.genshi (top-level)
missing module named urlparse - imported by tornado.escape (conditional), lxml.ElementInclude (optional), lxml.html.html5parser (optional)
missing module named urllib2 - imported by lxml.ElementInclude (optional), lxml.html.html5parser (optional)
missing module named lxml_html_clean - imported by lxml.html.clean (optional)
missing module named twisted - imported by apscheduler.schedulers.twisted (optional)
missing module named trollius - imported by tornado.platform.asyncio (optional)
missing module named __builtin__ - imported by tornado.gen (conditional)
missing module named backports_abc - imported by tornado.gen (optional)
missing module named singledispatch - imported by tornado.gen (optional)
missing module named thread - imported by tornado.ioloop (conditional)
missing module named 'tornado.speedups' - imported by tornado.util (conditional, optional)
missing module named monotonic - imported by tornado.platform.auto (optional)
missing module named monotime - imported by tornado.platform.auto (optional)
missing module named _curses - imported by curses (top-level), curses.has_key (top-level)
missing module named win32evtlog - imported by logging.handlers (delayed, optional)
missing module named win32evtlogutil - imported by logging.handlers (delayed, optional)
missing module named 'gevent.lock' - imported by apscheduler.schedulers.gevent (optional)
missing module named 'gevent.event' - imported by apscheduler.schedulers.gevent (optional)
missing module named gevent - imported by apscheduler.executors.gevent (optional), apscheduler.schedulers.gevent (optional)
missing module named 'kazoo.client' - imported by apscheduler.jobstores.zookeeper (optional)
missing module named kazoo - imported by apscheduler.jobstores.zookeeper (top-level)
missing module named annotationlib - imported by sqlalchemy.util.langhelpers (conditional)
missing module named pysqlcipher3 - imported by sqlalchemy.dialects.sqlite.pysqlcipher (delayed)
missing module named sqlcipher3 - imported by sqlalchemy.dialects.sqlite.pysqlcipher (delayed, optional)
missing module named psycopg2 - imported by sqlalchemy.dialects.postgresql.psycopg2 (delayed)
missing module named 'psycopg.pq' - imported by sqlalchemy.dialects.postgresql.psycopg (delayed)
missing module named 'psycopg.types' - imported by sqlalchemy.dialects.postgresql.psycopg (delayed, conditional)
missing module named 'psycopg.adapt' - imported by sqlalchemy.dialects.postgresql.psycopg (delayed, conditional)
missing module named psycopg - imported by sqlalchemy.dialects.postgresql.psycopg (delayed, conditional)
missing module named asyncpg - imported by sqlalchemy.dialects.postgresql.asyncpg (delayed)
missing module named oracledb - imported by sqlalchemy.dialects.oracle.oracledb (delayed, conditional)
missing module named cx_Oracle - imported by sqlalchemy.dialects.oracle.cx_oracle (delayed)
missing module named mysql - imported by sqlalchemy.dialects.mysql.mysqlconnector (delayed, conditional, optional)
missing module named asyncmy - imported by sqlalchemy.dialects.mysql.asyncmy (delayed)
missing module named nacl - imported by pymysql._auth (delayed, optional)
missing module named 'cryptography.hazmat' - imported by werkzeug.serving (delayed, conditional, optional), redis.ocsp (top-level), pymysql._auth (optional)
missing module named rethinkdb - imported by apscheduler.jobstores.rethinkdb (optional)
missing module named cryptography - imported by urllib3.contrib.pyopenssl (top-level), requests (conditional, optional), werkzeug.serving (delayed, optional), flask.cli (delayed, conditional, optional), redis.utils (optional), redis.ocsp (top-level)
missing module named hiredis - imported by redis.utils (optional), redis.connection (conditional), redis._parsers.hiredis (delayed)
missing module named 'cryptography.x509' - imported by urllib3.contrib.pyopenssl (delayed, optional), werkzeug.serving (delayed, conditional, optional), redis.ocsp (top-level)
missing module named 'cryptography.exceptions' - imported by redis.ocsp (top-level)
missing module named OpenSSL - imported by urllib3.contrib.pyopenssl (top-level), redis.connection (delayed, conditional)
missing module named 'pymongo.errors' - imported by apscheduler.jobstores.mongodb (optional)
missing module named pymongo - imported by apscheduler.jobstores.mongodb (optional)
missing module named bson - imported by apscheduler.jobstores.mongodb (optional)
missing module named etcd3 - imported by apscheduler.jobstores.etcd (optional)
missing module named 'setuptools._vendor.backports.zoneinfo' - imported by apscheduler.util (conditional)
missing module named dateutil.tz.tzfile - imported by dateutil.tz (top-level), dateutil.zoneinfo (top-level)
missing module named asgiref - imported by flask.app (delayed, optional)
missing module named '_typeshed.wsgi' - imported by werkzeug.exceptions (conditional), werkzeug.http (conditional), werkzeug.wsgi (conditional), werkzeug.utils (conditional), werkzeug.wrappers.response (conditional), werkzeug.test (conditional), werkzeug.formparser (conditional), werkzeug.wrappers.request (conditional), werkzeug.serving (conditional), werkzeug.debug (conditional), werkzeug.middleware.shared_data (conditional), werkzeug.local (conditional), werkzeug.routing.exceptions (conditional), werkzeug.routing.map (conditional), flask.typing (conditional), flask.ctx (conditional), flask.testing (conditional), flask.cli (conditional), flask.app (conditional)
missing module named dotenv - imported by flask.cli (delayed, optional)
missing module named 'watchdog.observers' - imported by werkzeug._reloader (delayed)
missing module named 'watchdog.events' - imported by werkzeug._reloader (delayed)
missing module named watchdog - imported by werkzeug._reloader (delayed)
missing module named simplejson - imported by requests.compat (conditional, optional)
missing module named dummy_threading - imported by requests.cookies (optional)
missing module named zstandard - imported by urllib3.util.request (optional), urllib3.response (optional)
missing module named brotli - imported by urllib3.util.request (optional), urllib3.response (optional)
missing module named brotlicffi - imported by urllib3.util.request (optional), urllib3.response (optional)
missing module named win_inet_pton - imported by socks (conditional, optional)
missing module named 'OpenSSL.crypto' - imported by urllib3.contrib.pyopenssl (delayed, conditional)
missing module named pyodide - imported by urllib3.contrib.emscripten.fetch (top-level)
missing module named js - imported by urllib3.contrib.emscripten.fetch (top-level)

File diff suppressed because it is too large Load Diff

BIN
1.1/dabao/dist/MonitorSystem.exe vendored Normal file

Binary file not shown.

155
1.1/dabao/dist/assets/index-0f069df0.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

15
1.1/dabao/dist/index.html vendored Normal file
View File

@ -0,0 +1,15 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>my-vue-app</title>
<script type="module" crossorigin src="./assets/index-0f069df0.js"></script>
<link rel="stylesheet" href="./assets/index-c85ab497.css">
</head>
<body>
<div id="app"></div>
</body>
</html>

1
1.1/dabao/dist/vite.svg vendored Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
1.1/dabao/requirements.txt Normal file

Binary file not shown.

327
1.1/dabao/test1.py Normal file
View File

@ -0,0 +1,327 @@
import os
import sys
import json
import threading
import requests
import logging
from datetime import datetime
from flask import Flask, jsonify, send_from_directory
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
from flask_apscheduler import APScheduler
from lxml import etree
# --- 配置日志 ---
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# --- 关键路径处理函数 (适配 PyInstaller) ---
def get_base_path():
"""获取运行时及其所在目录适配开发环境和打包后的EXE环境"""
if getattr(sys, 'frozen', False):
# 如果是打包后的 exesys.executable 是 exe 的路径
return os.path.dirname(sys.executable)
# 开发环境下,是当前脚本的路径
return os.path.dirname(os.path.abspath(__file__))
def get_static_path():
"""获取 Vue 静态资源 dist 的路径"""
if getattr(sys, 'frozen', False):
# PyInstaller 打包时,资源文件会被解压到 sys._MEIPASS 临时目录
# 我们需要在打包命令中指定 --add-data "dist;dist"
return os.path.join(sys._MEIPASS, 'dist')
# 开发环境
return os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dist')
# --- Flask 初始化 ---
# static_folder 指向 Vue 打包后的 dist 目录
# static_url_path='' 表示静态文件不需要 /static 前缀
dist_folder = get_static_path()
app = Flask(__name__, static_folder=dist_folder, static_url_path='')
CORS(app)
# --- 数据库配置 ---
# 确保数据库生成在 exe 同级目录下,而不是临时文件夹中
db_path = os.path.join(get_base_path(), 'monitor_data.db')
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SCHEDULER_API_ENABLED'] = True
db = SQLAlchemy(app)
scheduler = APScheduler()
# --- 模型定义 (保持不变) ---
class MonitorRecord(db.Model):
id = db.Column(db.Integer, primary_key=True)
source = db.Column(db.String(50))
name = db.Column(db.String(100))
status = db.Column(db.String(50))
reason = db.Column(db.String(255))
offset = db.Column(db.String(50))
latest_time = db.Column(db.String(50))
check_time = db.Column(db.String(50))
content = db.Column(db.Text, nullable=True)
with app.app_context():
db.create_all()
# --- 爬虫配置 (保持不变) ---
CONFIG = {
"106": {
"base_url": "http://106.75.72.40:7500/api/proxy/tcp",
"primary_auth": "Basic YWRtaW46bGljYWhr",
"login_payload": {"username": "admin", "password": "licahk", "recaptcha": ""}
},
"82": {
"base_url": "http://82.156.1.111/weather/php",
"login": {'username': 'renlixin', 'password': 'licahk', 'login': '123'}
}
}
is_running = False
# --- 核心辅助函数 (保持不变) ---
def calculate_offset(latest_time_str):
if not latest_time_str or latest_time_str == "N/A":
return "从未同步"
try:
clean_date_str = str(latest_time_str).split()[0].replace('_', '-')
target_date = datetime.strptime(clean_date_str, "%Y-%m-%d").date()
diff = (datetime.now().date() - target_date).days
if diff == 0: return "当天已同步"
return f"滞后 {diff}"
except:
return "时间解析失败"
def save_record(source, name, status, reason, latest_time="N/A", content=None):
record = MonitorRecord.query.filter_by(source=source, name=name).first()
now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
current_offset = calculate_offset(latest_time)
if record:
if content is not None: record.content = content
if latest_time != "N/A": record.latest_time = latest_time
record.status = status
record.reason = reason
record.check_time = now_str
time_base = latest_time if latest_time != "N/A" else record.latest_time
record.offset = calculate_offset(time_base)
else:
new_record = MonitorRecord(
source=source, name=name, status=status, reason=reason,
offset=current_offset, latest_time=latest_time,
check_time=now_str, content=content
)
db.session.add(new_record)
try:
db.session.commit()
except Exception as e:
db.session.rollback()
logging.error(f"DB Error: {e}")
return f"{source}_{name}"
# --- 业务逻辑函数 (保持不变) ---
def get_106_dynamic_token(port):
try:
login_url = f"http://106.75.72.40:{port}/api/login"
resp = requests.post(login_url, json=CONFIG["106"]["login_payload"], timeout=10)
return resp.text.strip().replace('"', '') if resp.status_code == 200 else None
except:
return None
def find_closest_item(items, is_date_level=True):
if not items or not isinstance(items, list): return None
today = datetime.now()
scored_items = []
for item in items:
name_val = item.get('name', '')
path_val = item.get('path', '')
target_str = name_val if name_val else path_val.split('/')[-1]
try:
if is_date_level:
current_date = datetime.strptime(target_str, "%Y_%m_%d")
else:
mod_str = item.get('modified', '')
current_date = datetime.fromisoformat(mod_str.replace('Z', '+00:00'))
diff = abs((today - current_date.replace(tzinfo=None)).total_seconds())
scored_items.append((diff, item, target_str))
except:
continue
if not scored_items: return None
scored_items.sort(key=lambda x: x[0])
return scored_items[0]
def run_106_logic(active_set):
# (保持原样,省略以节省空间,直接用你原本的逻辑即可)
c = CONFIG["106"]
today_str = datetime.now().strftime("%Y_%m_%d")
main_headers = {"Authorization": c["primary_auth"], "User-Agent": "Mozilla/5.0"}
try:
resp = requests.get(c["base_url"], headers=main_headers, timeout=20)
proxies = resp.json().get('proxies', [])
for item in proxies:
name = item.get('name', '')
if not name.lower().endswith('_data'): continue
if "TOWER" not in name.upper(): continue
if str(item.get('status')).lower() != 'online':
key = save_record("106网站", name, "离线", f"设备状态: {item.get('status')}")
active_set.add(key)
continue
try:
port = item.get('conf', {}).get('remote_port')
token = get_106_dynamic_token(port)
if not token:
key = save_record("106网站", name, "异常", "Token获取失败")
active_set.add(key)
continue
headers = {"Authorization": c["primary_auth"], "x-auth": token}
api_root = "/api/resources/Data/" if "TOWER_" in name.upper() else "/api/resources/data/"
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:
key = save_record("106网站", name, "正常", "未找到今日文件夹",
latest_time=best_date[2] if best_date else "N/A")
active_set.add(key)
continue
date_path = f"{api_root}{best_date[2]}/"
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)
if not best_file:
key = save_record("106网站", name, "正常", "今日文件夹为空", latest_time=today_str)
active_set.add(key)
continue
file_item = best_file[1]
full_path = file_item.get('path') or f"{date_path}{file_item.get('name')}"
is_tower_i = "TOWER" in name.upper() and "TOWER_" not in name.upper()
if is_tower_i:
download_url = f"http://106.75.72.40:{port}/api/raw{full_path}"
res3 = requests.get(download_url, headers=headers, timeout=20)
final_content = f"Binary Data Size: {len(res3.content)}"
else:
file_api_url = f"http://106.75.72.40:{port}/api/resources{full_path}"
res3 = requests.get(file_api_url, headers=headers, timeout=20)
final_content = res3.json().get('content', '')
key = save_record("106网站", name, "正常", "同步成功", latest_time=today_str, content=final_content)
active_set.add(key)
except Exception as e:
key = save_record("106网站", name, "异常", f"采集错误: {str(e)[:50]}")
active_set.add(key)
except Exception as e:
logging.error(f"106 Global Error: {e}")
def run_82_logic(active_set):
# (保持原样,直接用你原本的逻辑即可)
c = CONFIG["82"]
session = requests.Session()
try:
session.post(f"{c['base_url']}/login.php", data=c["login"], timeout=10)
resp = session.post(f"{c['base_url']}/GetStationList.php", timeout=10)
stations = etree.HTML(resp.content).xpath('//option/@value')
for sid in [s for s in stations if s]:
try:
r = session.post(f"{c['base_url']}/getLastWeatherData.php", data=str(sid),
headers={'Content-Type': 'text/plain'}, timeout=10)
data = r.json()
if data:
d_list = data.get('date', [])
latest = str(d_list[-1]) if d_list else "N/A"
key = save_record("82网站", sid, "正常", "同步成功", latest_time=latest,
content=json.dumps(data, ensure_ascii=False))
active_set.add(key)
else:
key = save_record("82网站", sid, "异常", "返回空数据")
active_set.add(key)
except:
key = save_record("82网站", sid, "异常", "单个采集失败")
active_set.add(key)
except Exception as e:
logging.error(f"82 Global Error: {e}")
def execute_monitor_task():
global is_running
if is_running: return
is_running = True
logging.info("Starting monitor task...")
with app.app_context():
active_set = set()
run_106_logic(active_set)
run_82_logic(active_set)
all_records = MonitorRecord.query.all()
now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
for record in all_records:
if f"{record.source}_{record.name}" not in active_set:
record.status = "已离线"
record.reason = "设备本次未出现"
record.check_time = now_str
record.offset = calculate_offset(record.latest_time)
try:
db.session.commit()
except:
db.session.rollback()
is_running = False
logging.info("Monitor task finished.")
# --- API 路由 (保持不变) ---
@app.route('/api/run', methods=['POST'])
def manual_start():
if is_running: return jsonify({"status": "busy"}), 400
threading.Thread(target=execute_monitor_task).start()
return jsonify({"status": "started"})
@app.route('/api/status')
def status(): return jsonify({"is_running": is_running})
@app.route('/api/logs')
def logs():
data = MonitorRecord.query.all()
return jsonify([{
"source": l.source, "name": l.name, "status": l.status,
"reason": l.reason, "offset": l.offset, "latest_time": l.latest_time,
"check_time": l.check_time, "content": l.content
} for l in data])
# --- 新增: 前端页面托管路由 ---
@app.route('/')
def serve_index():
return send_from_directory(app.static_folder, 'index.html')
@app.route('/<path:path>')
def serve_static_files(path):
# 尝试在 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)
# 如果找不到文件例如刷新页面时的路由返回index.html让Vue Router处理
return send_from_directory(app.static_folder, 'index.html')
# --- 调度器与启动 ---
@scheduler.task('cron', id='daily_job', hour=10, minute=0)
def auto_run_task():
with app.app_context():
threading.Thread(target=execute_monitor_task).start()
if __name__ == "__main__":
scheduler.init_app(app)
scheduler.start()
# Host='0.0.0.0' 允许外部IP访问
# Port=5000 (确保 Windows 防火墙开放了此端口)
print("应用正在启动... 请确保 dist 文件夹与脚本/exe 同级或已被打包")
app.run(host='0.0.0.0', port=5000, debug=False, use_reloader=False)