站点配置(百世密码/韵达账密/安能路径)从 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:
Misaka
2026-07-18 08:37:15 +08:00
parent 96e7018146
commit 6d45a9f5e5
5 changed files with 100 additions and 35 deletions

View File

@@ -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. 读 configanneng.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

View File

@@ -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"

View File

@@ -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", "")
password = state_store.get_setting("百世", "password")
if not password:
print(
" ⚠️ 警告:在 config.yaml 中未找到百世的登录密码,可能导致导出失败。"
)
except Exception as e:
print(f" ⚠️ 读取 config.yaml 失败,请确保文件存在且格式正确: {e}")
return False
print(" ⚠️ 警告:未设置百世导出密码(前端站点配置),可能导致导出失败。")
page.get_by_placeholder("请输入登录密码").fill(password)

View File

@@ -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)

View File

@@ -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}
# ============================ 任务历史 ============================