站点配置(百世密码/韵达账密/安能路径)从 config.yaml 移至 state.db
- state_store:加 site_settings(site,key,value) 表 + get_setting/set_setting/get_site_settings
- runtime:seed_legacy_config 启动一次性从 config.yaml 灌入(已存在不覆盖);安能 app_path 改读 state_store
- site_baishi:导出密码改读 get_setting("百世","password")
- site_yunda:登录账密改读 get_setting("韵达",...)
- server:/config PUT 接受 settings;新增 GET /config/{site}/settings(不进 5s 轮询)
debug 仍保留在 config.yaml;站点配置现由前端配置弹窗管理(state.db 为准)。
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
40
runtime.py
40
runtime.py
@@ -206,24 +206,44 @@ class RuntimeContext:
|
||||
# ============================ 启动 + 就绪 + 弹窗 ============================
|
||||
|
||||
|
||||
def seed_legacy_config():
|
||||
"""一次性:把 config.yaml 里的站点配置(百世密码 / 韵达账密 / 安能 exe 路径)
|
||||
灌入 state.db。已存在的值不覆盖(前端改过的不动)。"""
|
||||
if not os.path.exists(CONFIG_PATH):
|
||||
return
|
||||
try:
|
||||
with open(CONFIG_PATH, "r", encoding="utf-8") as f:
|
||||
cfg = yaml.safe_load(f) or {}
|
||||
except Exception as e:
|
||||
print(f"⚠️ 读取 config.yaml 做 seed 失败: {e}")
|
||||
return
|
||||
items = [
|
||||
("百世", "password", (cfg.get("baishi", {}) or {}).get("password", "")),
|
||||
("韵达", "username", (cfg.get("yunda", {}) or {}).get("username", "")),
|
||||
("韵达", "password", (cfg.get("yunda", {}) or {}).get("password", "")),
|
||||
("安能", "app_path", (cfg.get("anneng", {}) or {}).get("app_path", "")),
|
||||
]
|
||||
seeded = []
|
||||
for site, key, val in items:
|
||||
if val and not state_store.get_setting(site, key):
|
||||
state_store.set_setting(site, key, str(val))
|
||||
seeded.append(f"{site}.{key}")
|
||||
if seeded:
|
||||
print(f">> [seed] 从 config.yaml 灌入站点配置: {', '.join(seeded)}")
|
||||
|
||||
|
||||
def launch_and_prepare(debug_mode=False, debug_target=""):
|
||||
"""启动 Playwright + 各站 page + 就绪轮询 + 弹窗清理 + 心跳初值,返回 RuntimeContext。
|
||||
|
||||
必须在"持有 Playwright 的线程"调用(交互模式主线程 / 服务模式 worker 线程)。
|
||||
阻塞至所有站点登录就绪才返回。
|
||||
"""
|
||||
# 0. 状态库建表/迁移(须在 reset_login_states 等读写新字段的调用之前)
|
||||
# 0. 状态库建表/迁移 + 从 config.yaml 灌入站点配置(须在 reset_login_states 等之前)
|
||||
state_store.init_db()
|
||||
seed_legacy_config()
|
||||
|
||||
# 1. 读 config(anneng.app_path)
|
||||
anneng_app_path = ""
|
||||
try:
|
||||
if os.path.exists(CONFIG_PATH):
|
||||
with open(CONFIG_PATH, "r", encoding="utf-8") as f:
|
||||
config = yaml.safe_load(f) or {}
|
||||
anneng_app_path = (config.get("anneng", {}) or {}).get("app_path", "")
|
||||
except Exception as e:
|
||||
print(f"⚠️ 读取 config.yaml 异常: {e}")
|
||||
# 1. 安能 Electron 应用路径(state.db,前端站点配置;seed 已灌入)
|
||||
anneng_app_path = state_store.get_setting("安能", "app_path")
|
||||
|
||||
# 2. 确定要挂载的网页站点 + 安能标记
|
||||
anneng_active = False
|
||||
|
||||
19
server.py
19
server.py
@@ -17,7 +17,7 @@ import queue
|
||||
import threading
|
||||
import time
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import Optional
|
||||
from typing import Dict, Optional
|
||||
|
||||
import uvicorn
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
@@ -190,6 +190,7 @@ class ConfigRequest(BaseModel):
|
||||
actual_offset: Optional[int] = None
|
||||
schedule_enabled: Optional[bool] = None
|
||||
schedule_time: Optional[str] = None
|
||||
settings: Optional[Dict[str, str]] = None
|
||||
|
||||
|
||||
def _default_cfg():
|
||||
@@ -236,7 +237,21 @@ def set_config(site: str, req: ConfigRequest):
|
||||
)
|
||||
state_store.set_schedule(site, enabled, time_str)
|
||||
_reschedule_site(site)
|
||||
return state_store.get_all_config().get(site, _default_cfg())
|
||||
# 站点专属配置(密码/账号/路径…)
|
||||
if req.settings:
|
||||
for k, v in req.settings.items():
|
||||
state_store.set_setting(site, k, v)
|
||||
cfg = state_store.get_all_config().get(site, _default_cfg())
|
||||
cfg["settings"] = state_store.get_site_settings(site)
|
||||
return cfg
|
||||
|
||||
|
||||
@app.get("/config/{site}/settings")
|
||||
def get_site_settings_api(site: str):
|
||||
"""单站专属配置(密码/账号/路径…;不进 5s 轮询)。"""
|
||||
if site not in ALL_SITES:
|
||||
raise HTTPException(status_code=400, detail=f"未知站点: {site}")
|
||||
return state_store.get_site_settings(site)
|
||||
|
||||
|
||||
REPORT_FILE = "应到未到数据.xlsx"
|
||||
|
||||
@@ -5,6 +5,7 @@ import yaml
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
from paths import DOWNLOAD_DIR, CONFIG_PATH
|
||||
import state_store
|
||||
|
||||
|
||||
def with_retry(site_name, label, flow, reset, max_attempts=3):
|
||||
@@ -143,21 +144,11 @@ def baishi_download_undelivered_data_impl(page):
|
||||
# 验证模态框弹出
|
||||
page.locator(".ant-modal-title", has_text="导出设置").wait_for(state="visible")
|
||||
|
||||
# 4. 加载 YAML 配置并输入密码
|
||||
# 4. 读取百世导出密码(存 state.db,由前端站点配置)
|
||||
print(">> 正在读取配置并填写密码...")
|
||||
password = ""
|
||||
try:
|
||||
with open(CONFIG_PATH, "r", encoding="utf-8") as f:
|
||||
config = yaml.safe_load(f)
|
||||
password = config.get("baishi", {}).get("password", "")
|
||||
|
||||
if not password:
|
||||
print(
|
||||
" ⚠️ 警告:在 config.yaml 中未找到百世的登录密码,可能导致导出失败。"
|
||||
)
|
||||
except Exception as e:
|
||||
print(f" ⚠️ 读取 config.yaml 失败,请确保文件存在且格式正确: {e}")
|
||||
return False
|
||||
password = state_store.get_setting("百世", "password")
|
||||
if not password:
|
||||
print(" ⚠️ 警告:未设置百世导出密码(前端站点配置),可能导致导出失败。")
|
||||
|
||||
page.get_by_placeholder("请输入登录密码").fill(password)
|
||||
|
||||
|
||||
@@ -73,15 +73,9 @@ def yunda_login(page):
|
||||
switch_btn.click()
|
||||
page.wait_for_timeout(500)
|
||||
|
||||
# 从配置读取凭证(默认空串;真实凭据仅存于被忽略的 config.yaml)
|
||||
username = ""
|
||||
password = ""
|
||||
if os.path.exists(CONFIG_PATH):
|
||||
with open(CONFIG_PATH, "r", encoding="utf-8") as f:
|
||||
config = yaml.safe_load(f) or {}
|
||||
yd_cfg = config.get("yunda", {})
|
||||
username = str(yd_cfg.get("username", ""))
|
||||
password = str(yd_cfg.get("password", ""))
|
||||
# 凭证存 state.db(由前端站点配置)
|
||||
username = state_store.get_setting("韵达", "username")
|
||||
password = state_store.get_setting("韵达", "password")
|
||||
|
||||
print(f" -> 正在填充登录表单 (账号: {username})...")
|
||||
page.locator("#username").fill(username)
|
||||
|
||||
@@ -96,6 +96,14 @@ def init_db():
|
||||
)
|
||||
except sqlite3.OperationalError:
|
||||
pass
|
||||
conn.execute("""
|
||||
CREATE TABLE IF NOT EXISTS site_settings (
|
||||
site TEXT,
|
||||
key TEXT,
|
||||
value TEXT,
|
||||
PRIMARY KEY (site, key)
|
||||
)
|
||||
""")
|
||||
conn.commit()
|
||||
|
||||
|
||||
@@ -282,6 +290,43 @@ def get_all_config():
|
||||
}
|
||||
|
||||
|
||||
# ============================ 站点键值配置(site_settings)============================
|
||||
# 各站专属配置(百世密码、韵达账密、安能 exe 路径…),由前端配置弹窗设置。
|
||||
|
||||
|
||||
def get_setting(site, key):
|
||||
"""读取单站某个配置值;未设置返回 ''。"""
|
||||
if not os.path.exists(STATE_DB_PATH):
|
||||
return ""
|
||||
with sqlite3.connect(STATE_DB_PATH) as conn:
|
||||
row = conn.execute(
|
||||
"SELECT value FROM site_settings WHERE site=? AND key=?", (site, key)
|
||||
).fetchone()
|
||||
return row[0] if row else ""
|
||||
|
||||
|
||||
def set_setting(site, key, value):
|
||||
"""设置单站某个配置值(upsert)。"""
|
||||
with sqlite3.connect(STATE_DB_PATH) as conn:
|
||||
conn.execute(
|
||||
"INSERT INTO site_settings (site, key, value) VALUES (?, ?, ?) "
|
||||
"ON CONFLICT(site, key) DO UPDATE SET value=excluded.value",
|
||||
(site, key, value if value is not None else ""),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
|
||||
def get_site_settings(site):
|
||||
"""返回单站全部配置 {key: value}。"""
|
||||
if not os.path.exists(STATE_DB_PATH):
|
||||
return {}
|
||||
with sqlite3.connect(STATE_DB_PATH) as conn:
|
||||
rows = conn.execute(
|
||||
"SELECT key, value FROM site_settings WHERE site=?", (site,)
|
||||
).fetchall()
|
||||
return {r[0]: r[1] for r in rows}
|
||||
|
||||
|
||||
# ============================ 任务历史 ============================
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user