#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 配置管理模块 - 精简版 使用JSON本地存储,不搞数据库那些花里胡哨的东西 """ import os from dataclasses import dataclass, field from typing import List, Optional from pathlib import Path # 项目根目录 BASE_DIR = Path(__file__).parent.absolute() DATA_DIR = BASE_DIR / "data" COOKIES_DIR = DATA_DIR / "cookies" SCREENSHOTS_DIR = DATA_DIR / "screenshots" # 确保目录存在 DATA_DIR.mkdir(exist_ok=True) COOKIES_DIR.mkdir(exist_ok=True) SCREENSHOTS_DIR.mkdir(exist_ok=True) # 配置文件路径 CONFIG_FILE = DATA_DIR / "config.json" ENCRYPTION_KEY_FILE = DATA_DIR / "encryption_key.bin" ENCRYPTION_SALT_FILE = DATA_DIR / "encryption_salt.bin" KDOCS_LOGIN_STATE_FILE = DATA_DIR / "kdocs_login_state.json" @dataclass class AccountConfig: """账号配置""" username: str password: str # 加密存储 remark: str = "" enabled: bool = True @dataclass class KDocsConfig: """金山文档配置""" enabled: bool = False doc_url: str = "https://kdocs.cn/l/cpwEOo5ynKX4" sheet_name: str = "Sheet1" sheet_index: int = 0 unit_column: str = "A" image_column: str = "D" unit: str = "" # 县区名 name_column: str = "C" # 姓名列 row_start: int = 0 # 有效行起始(0表示不限制) row_end: int = 0 # 有效行结束(0表示不限制) @dataclass class ScreenshotConfig: """截图配置""" dir: str = str(SCREENSHOTS_DIR) quality: int = 95 width: int = 1920 height: int = 1080 js_delay_ms: int = 3000 timeout_seconds: int = 60 wkhtmltoimage_path: str = "" # 自定义路径,空则自动查找 @dataclass class ProxyConfig: """代理配置""" enabled: bool = False server: str = "" # http://host:port @dataclass class ZSGLConfig: """知识管理平台配置""" base_url: str = "https://postoa.aidunsoft.com" login_url: str = "https://postoa.aidunsoft.com/admin/login.aspx" index_url_pattern: str = "index.aspx" @dataclass class TaskConfig: """浏览任务配置""" browse_type: str = "应读" # 浏览类型 auto_screenshot: bool = True # 浏览后自动截图 auto_upload: bool = False # 截图后自动上传 @dataclass class AppConfig: """应用总配置""" accounts: List[AccountConfig] = field(default_factory=list) kdocs: KDocsConfig = field(default_factory=KDocsConfig) screenshot: ScreenshotConfig = field(default_factory=ScreenshotConfig) proxy: ProxyConfig = field(default_factory=ProxyConfig) zsgl: ZSGLConfig = field(default_factory=ZSGLConfig) task: TaskConfig = field(default_factory=TaskConfig) theme: str = "light" # light/dark def to_dict(self) -> dict: """转换为字典""" return { "accounts": [ { "username": a.username, "password": a.password, "remark": a.remark, "enabled": a.enabled } for a in self.accounts ], "kdocs": { "enabled": self.kdocs.enabled, "doc_url": self.kdocs.doc_url, "sheet_name": self.kdocs.sheet_name, "sheet_index": self.kdocs.sheet_index, "unit_column": self.kdocs.unit_column, "image_column": self.kdocs.image_column, "unit": self.kdocs.unit, "name_column": self.kdocs.name_column, "row_start": self.kdocs.row_start, "row_end": self.kdocs.row_end, }, "screenshot": { "dir": self.screenshot.dir, "quality": self.screenshot.quality, "width": self.screenshot.width, "height": self.screenshot.height, "js_delay_ms": self.screenshot.js_delay_ms, "timeout_seconds": self.screenshot.timeout_seconds, "wkhtmltoimage_path": self.screenshot.wkhtmltoimage_path, }, "proxy": { "enabled": self.proxy.enabled, "server": self.proxy.server, }, "zsgl": { "base_url": self.zsgl.base_url, "login_url": self.zsgl.login_url, "index_url_pattern": self.zsgl.index_url_pattern, }, "task": { "browse_type": self.task.browse_type, "auto_screenshot": self.task.auto_screenshot, "auto_upload": self.task.auto_upload, }, "theme": self.theme, } @classmethod def from_dict(cls, data: dict) -> "AppConfig": """从字典创建配置""" config = cls() # 加载账号 accounts_data = data.get("accounts", []) config.accounts = [ AccountConfig( username=a.get("username", ""), password=a.get("password", ""), remark=a.get("remark", ""), enabled=a.get("enabled", True) ) for a in accounts_data ] # 加载金山文档配置 kdocs_data = data.get("kdocs", {}) config.kdocs = KDocsConfig( enabled=kdocs_data.get("enabled", False), doc_url=kdocs_data.get("doc_url", "https://kdocs.cn/l/cpwEOo5ynKX4"), sheet_name=kdocs_data.get("sheet_name", "Sheet1"), sheet_index=kdocs_data.get("sheet_index", 0), unit_column=kdocs_data.get("unit_column", "A"), image_column=kdocs_data.get("image_column", "D"), unit=kdocs_data.get("unit", ""), name_column=kdocs_data.get("name_column", "C"), row_start=kdocs_data.get("row_start", 0), row_end=kdocs_data.get("row_end", 0), ) # 加载截图配置 screenshot_data = data.get("screenshot", {}) config.screenshot = ScreenshotConfig( dir=screenshot_data.get("dir", str(SCREENSHOTS_DIR)), quality=screenshot_data.get("quality", 95), width=screenshot_data.get("width", 1920), height=screenshot_data.get("height", 1080), js_delay_ms=screenshot_data.get("js_delay_ms", 3000), timeout_seconds=screenshot_data.get("timeout_seconds", 60), wkhtmltoimage_path=screenshot_data.get("wkhtmltoimage_path", ""), ) # 加载代理配置 proxy_data = data.get("proxy", {}) config.proxy = ProxyConfig( enabled=proxy_data.get("enabled", False), server=proxy_data.get("server", ""), ) # 加载知识管理平台配置 zsgl_data = data.get("zsgl", {}) config.zsgl = ZSGLConfig( base_url=zsgl_data.get("base_url", "https://postoa.aidunsoft.com"), login_url=zsgl_data.get("login_url", "https://postoa.aidunsoft.com/admin/login.aspx"), index_url_pattern=zsgl_data.get("index_url_pattern", "index.aspx"), ) # 加载任务配置 task_data = data.get("task", {}) config.task = TaskConfig( browse_type=task_data.get("browse_type", "应读"), auto_screenshot=task_data.get("auto_screenshot", True), auto_upload=task_data.get("auto_upload", False), ) # 主题 config.theme = data.get("theme", "light") return config # 全局配置实例 _config: Optional[AppConfig] = None def get_config() -> AppConfig: """获取配置实例(懒加载)""" global _config if _config is None: from utils.storage import load_config _config = load_config() return _config def save_config(config: AppConfig = None): """保存配置""" global _config if config: _config = config if _config: from utils.storage import save_config as _save _save(_config) def reload_config(): """重新加载配置""" global _config from utils.storage import load_config _config = load_config() return _config