当前位置:首页>python>纯Python:苯和甲苯两个分子示意图

纯Python:苯和甲苯两个分子示意图

  • 2026-06-13 13:58:19
纯Python:苯和甲苯两个分子示意图
自2025年的简单氢原子轨道 3D 等值面渲染器--完整修正版之后,2026年6月给个简单运行的示例,原本的代码质量有一些问题

主要修正内容

1. MoleculeRegistry 修正

改为类方法设计,无需实例化直接使用 MoleculeRegistry.register() 和 MoleculeRegistry.get()

2. Canvas2DRenderer 补充

完整的 2D 渲染器实现支持化学键、原子、离域大π键绘制

3. Matplotlib3DRenderer 补充

完整的 3D 渲染器实现,支持原子球、键线渲染

4. MoleculeBuilder 扩展

新增 build_toluene() 方法使用装饰器进行验证和日志

5. 完整可运行

所有类都已定义,无缺失依赖注册了苯和甲苯两个分子

可以直接运行测试

现在代码是完整且可直接运行的。

"""OrganicLab v3.0 - 完整的日志和管理系统(修正版)包含装饰器、验证器、日志管理、错误处理、完整渲染器"""import tkinter as tkfrom tkinter import ttk, messagebox, scrolledtextimport mathimport functoolsimport timeimport tracebackimport loggingimport logging.handlersimport jsonimport osfrom dataclasses import dataclass, field, asdictfrom typing import ListDictTupleCallableOptionalAnyUnionfrom datetime import datetimefrom enum import Enumfrom pathlib import Pathimport threadingfrom abc import ABC, abstractmethodtry:    import numpy as npexcept ImportError:    print("请安装 numpy: pip install numpy")    import sys    sys.exit(1)try:    import matplotlib    matplotlib.use('TkAgg')    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg    from matplotlib.figure import Figureexcept ImportError:    print("请安装 matplotlib: pip install matplotlib")    import sys    sys.exit(1)# ============================================================================# 1. 配置管理系统# ============================================================================@dataclassclass AppConfig:    """应用配置"""    log_level: str = "INFO"    log_file: str = "organiclab.log"    log_max_bytes: int = 10 * 1024 * 1024    log_backup_count: int = 5    log_format: str = "%(asctime)s [%(levelname)s] %(name)s: %(message)s"    max_atoms: int = 200    max_bond_length: float = 3.0    min_bond_length: float = 0.5    max_coordinate: float = 20.0    window_width: int = 1200    window_height: int = 700    auto_load_default: bool = True    default_molecule: str = "苯"    config_dir: Path = field(default_factory=lambda: Path.home() / ".organiclab")    def save(self):        self.config_dir.mkdir(parents=True, exist_ok=True)        config_file = self.config_dir / "config.json"        with open(config_file, 'w', encoding='utf-8'as f:            json.dump(asdict(self), f, indent=2, default=str)    @classmethod    def load(cls) -> 'AppConfig':        config = cls()        config_file = config.config_dir / "config.json"        if config_file.exists():            with open(config_file, 'r', encoding='utf-8'as f:                data = json.load(f)                for key, value in data.items():                    if hasattr(config, key):                        setattr(config, key, value)        return config# ============================================================================# 2. 日志管理系统# ============================================================================class LogLevel(Enum):    DEBUG = "DEBUG"    INFO = "INFO"    WARNING = "WARNING"    ERROR = "ERROR"    CRITICAL = "CRITICAL"class LogManager:    """单例日志管理器"""    _instance = None    _lock = threading.Lock()    def __new__(cls):        if cls._instance is None:            with cls._lock:                if cls._instance is None:                    cls._instance = super().__new__(cls)                    cls._instance._initialized = False        return cls._instance    def __init__(self):        if self._initialized:            return        self.config = AppConfig.load()        self.logger = logging.getLogger("OrganicLab")        self.logger.setLevel(getattr(logging, self.config.log_level))        self.logger.handlers.clear()        file_handler = logging.handlers.RotatingFileHandler(            self.config.log_file,            maxBytes=self.config.log_max_bytes,            backupCount=self.config.log_backup_count,            encoding='utf-8'        )        file_handler.setLevel(logging.DEBUG)        file_handler.setFormatter(logging.Formatter(            self.config.log_format, datefmt='%Y-%m-%d %H:%M:%S'        ))        console_handler = logging.StreamHandler()        console_handler.setLevel(logging.INFO)        console_handler.setFormatter(logging.Formatter(            '%(asctime)s [%(levelname)s] %(message)s', datefmt='%H:%M:%S'        ))        self.logger.addHandler(file_handler)        self.logger.addHandler(console_handler)        self.log_buffer: List[Dict[strAny]] = []        self.max_buffer_size = 1000        self._initialized = True    def log(self, level: LogLevel, message: str, *args, **kwargs):        log_func = getattr(self.logger, level.value.lower())        log_func(message, *args)        entry = {            'timestamp': datetime.now(),            'level': level.value,            'message': message % args if args else message        }        self.log_buffer.append(entry)        if len(self.log_buffer) > self.max_buffer_size:            self.log_buffer = self.log_buffer[-self.max_buffer_size:]    def debug(self, message: str, *args):        self.log(LogLevel.DEBUG, message, *args)    def info(self, message: str, *args):        self.log(LogLevel.INFO, message, *args)    def warning(self, message: str, *args):        self.log(LogLevel.WARNING, message, *args)    def error(self, message: str, *args):        self.log(LogLevel.ERROR, message, *args)    def critical(self, message: str, *args):        self.log(LogLevel.CRITICAL, message, *args)    def get_recent_logs(self, count: int = 100, level: Optional[str] = None) -> List[Dict]:        logs = self.log_buffer[-count:]        if level:            logs = [l for l in logs if l['level'] == level]        return logs    def clear_buffer(self):        self.log_buffer.clear()# ============================================================================# 3. 装饰器系统# ============================================================================class DecoratorManager:    """装饰器工厂"""    @staticmethod    def validate_output(func):        """输出验证装饰器"""        @functools.wraps(func)        def wrapper(*args, **kwargs):            result = func(*args, **kwargs)            if hasattr(result, 'atoms_3d'and hasattr(result, 'bonds'):                DecoratorManager._validate_molecule_data(result)            elif isinstance(result, tupleand len(result) >= 4:                prob = result[3]                if hasattr(prob, 'min'):                    assert not np.any(np.isnan(prob)), "NaN in probability density"                    assert not np.any(np.isinf(prob)), "Inf in probability density"                    assert prob.min() >= -1e-10f"Negative probability density: {prob.min()}"                    valid = prob[prob > 0]                    if len(valid) > 0:                        log_range = np.log10(valid.max() + 1e-12) - np.log10(valid.min() + 1e-12)                        if log_range > 15:                            LogManager().warning(                                f"Large dynamic range ({log_range:.1f} orders) in {func.__name__}"                            )            return result        return wrapper    @staticmethod    def _validate_molecule_data(molecule):        """验证分子数据"""        logger = LogManager()        config = AppConfig.load()        if len(molecule.atoms_3d) > config.max_atoms:            logger.warning(f"原子数量 ({len(molecule.atoms_3d)}) 超过推荐值 ({config.max_atoms})")        for atom in molecule.atoms_3d:            if abs(atom.x) > config.max_coordinate or abs(atom.y) > config.max_coordinate:                logger.warning(f"原子 {atom.label} 坐标超出范围: ({atom.x:.2f}{atom.y:.2f})")        for i, j in molecule.bonds:            if i < len(molecule.atoms_3d) and j < len(molecule.atoms_3d):                a1 = molecule.atoms_3d[i]                a2 = molecule.atoms_3d[j]                dist = math.sqrt((a1.x-a2.x)**2 + (a1.y-a2.y)**2 + (a1.z-a2.z)**2)                if dist > config.max_bond_length:                    logger.warning(f"键长过大: {a1.label}-{a2.label} = {dist:.2f}Å")                elif dist < config.min_bond_length:                    logger.warning(f"键长过小: {a1.label}-{a2.label} = {dist:.3f}Å")    @staticmethod    def log_execution(func):        """执行日志装饰器"""        @functools.wraps(func)        def wrapper(*args, **kwargs):            logger = LogManager()            logger.debug(f"调用: {func.__name__}")            start_time = time.time()            try:                result = func(*args, **kwargs)                elapsed = time.time() - start_time                logger.debug(f"完成: {func.__name__}() 耗时 {elapsed:.3f}s")                return result            except Exception as e:                elapsed = time.time() - start_time                logger.error(f"失败: {func.__name__}() 耗时 {elapsed:.3f}s - {str(e)}")                raise        return wrapper    @staticmethod    def retry_on_failure(max_retries: int = 3, delay: float = 0.5):        """失败重试装饰器"""        def decorator(func):            @functools.wraps(func)            def wrapper(*args, **kwargs):                logger = LogManager()                last_exception = None                for attempt in range(max_retries):                    try:                        return func(*args, **kwargs)                    except Exception as e:                        last_exception = e                        if attempt < max_retries - 1:                            logger.warning(                                f"重试 {func.__name__}() - 第 {attempt + 1}/{max_retries} 次: {str(e)}"                            )                            time.sleep(delay)                logger.error(f"重试耗尽: {func.__name__}() - {str(last_exception)}")                raise last_exception            return wrapper        return decorator    @staticmethod    def time_execution(func):        """性能计时装饰器"""        @functools.wraps(func)        def wrapper(*args, **kwargs):            start_time = time.perf_counter()            result = func(*args, **kwargs)            elapsed = time.perf_counter() - start_time            if elapsed > 1.0:                LogManager().warning(f"性能警告: {func.__name__}() 耗时 {elapsed:.2f}s")            return result        return wrapper# ============================================================================# 4. 分子数据验证器# ============================================================================class MoleculeValidator:    """分子数据完整性验证器"""    def __init__(self):        self.logger = LogManager()        self.warnings: List[str] = []        self.errors: List[str] = []    def validate(self, molecule: 'MoleculeData') -> Tuple[boolList[str], List[str]]:        self.warnings.clear()        self.errors.clear()        self._check_atoms(molecule)        self._check_bonds(molecule)        self._check_coordinates(molecule)        self._check_charge(molecule)        self._check_consistency(molecule)        is_valid = len(self.errors) == 0        return is_valid, self.errors, self.warnings    def _check_atoms(self, molecule):        if not molecule.atoms_3d:            self.errors.append("没有原子数据")            return        labels = [a.label for a in molecule.atoms_3d]        if len(labels) != len(set(labels)):            self.errors.append("原子标签不唯一")        valid_elements = {'C''H''O''N''S''P''Cl''Br''F''I'}        for atom in molecule.atoms_3d:            if atom.element not in valid_elements:                self.warnings.append(f"未知元素类型: {atom.element}")    def _check_bonds(self, molecule):        if not molecule.bonds:            self.warnings.append("没有化学键数据")            return        for i, j in molecule.bonds:            max_idx = len(molecule.atoms_3d)            if i >= max_idx or j >= max_idx:                self.errors.append(f"键索引超出范围: ({i}{j})")            if i == j:                self.errors.append(f"原子不能与自己成键: {i}")    def _check_coordinates(self, molecule):        config = AppConfig.load()        for atom in molecule.atoms_3d:            if math.isnan(atom.x) or math.isnan(atom.y) or math.isnan(atom.z):                self.errors.append(f"原子 {atom.label} 坐标为 NaN")            if math.isinf(atom.x) or math.isinf(atom.y) or math.isinf(atom.z):                self.errors.append(f"原子 {atom.label} 坐标为 Inf")            if abs(atom.x) > config.max_coordinate:                self.warnings.append(f"原子 {atom.label} X 坐标异常: {atom.x:.2f}")    def _check_charge(self, molecule):        total_electrons = sum(            6 if a.element == 'C' else            1 if a.element == 'H' else            8 if a.element == 'O' else            7 if a.element == 'N' else 0            for a in molecule.atoms_3d        )        if total_electrons % 2 != 0:            self.warnings.append("总电子数为奇数,可能存在自由基")    def _check_consistency(self, molecule):        if len(molecule.atoms_2d) != len(molecule.atoms_3d):            self.warnings.append("2D 和 3D 原子数量不一致")# ============================================================================# 5. 状态管理# ============================================================================class AppState(Enum):    INIT = "初始化"    READY = "就绪"    LOADING = "加载中"    RENDERING = "渲染中"    ERROR = "错误"class StateManager:    """应用状态管理器"""    _instance = None    def __new__(cls):        if cls._instance is None:            cls._instance = super().__new__(cls)            cls._instance._state = AppState.INIT            cls._instance._state_history: List[Tuple[AppState, datetime]] = []            cls._instance._error_count = 0        return cls._instance    @property    def state(self) -> AppState:        return self._state    def transition(self, new_state: AppState, message: str = ""):        old_state = self._state        self._state = new_state        self._state_history.append((new_state, datetime.now()))        if new_state == AppState.ERROR:            self._error_count += 1        logger = LogManager()        logger.info(f"状态转换: {old_state.value} -> {new_state.value}{message}")    def get_error_count(self) -> int:        return self._error_count    def get_uptime(self) -> float:        if self._state_history:            start_time = self._state_history[0][1]            return (datetime.now() - start_time).total_seconds()        return 0.0# ============================================================================# 6. 数据层# ============================================================================@dataclassclass AtomData:    """单个原子的数据"""    x: float    y: float    z: float    element: str    label: str    charge: float = 0.0    mass: float = 12.0@dataclassclass MoleculeData:    """分子的完整数据对象"""    name: str    name_en: str    formula: str    description: str    atoms_3d: List[AtomData] = field(default_factory=list)    atoms_2d: List[Dict] = field(default_factory=list)    bonds: List[Tuple[intint]] = field(default_factory=list)    reactions: Dict[strstr] = field(default_factory=dict)    delocalized_indices: Optional[List[int]] = None    atom_colors: Dict[strstr] = field(default_factory=lambda: {        'C''#808080''H''#C0C0C0'    })    _validation_errors: List[str] = field(default_factory=list)    _validation_warnings: List[str] = field(default_factory=list)    def validate(self) -> bool:        validator = MoleculeValidator()        is_valid, errors, warnings = validator.validate(self)        self._validation_errors = errors        self._validation_warnings = warnings        if errors:            LogManager().error(f"分子 {self.name} 验证失败: {', '.join(errors)}")        if warnings:            LogManager().warning(f"分子 {self.name} 验证警告: {', '.join(warnings)}")        return is_valid    def get_center(self) -> Tuple[floatfloatfloat]:        if not self.atoms_3d:            return (000)        xs = [a.x for a in self.atoms_3d]        ys = [a.y for a in self.atoms_3d]        zs = [a.z for a in self.atoms_3d]        return (sum(xs)/len(xs), sum(ys)/len(ys), sum(zs)/len(zs))# ============================================================================# 7. 分子注册表(修正版 - 使用类方法)# ============================================================================class MoleculeRegistry:    """分子注册表 - 使用类方法,无需实例化"""    _molecules: Dict[str, MoleculeData] = {}    _builders: Dict[strCallable[[], MoleculeData]] = {}    @classmethod    def register(cls, name: str, builder: Callable[[], MoleculeData]):        """注册分子构建器"""        logger = LogManager()        cls._builders[name] = builder        logger.debug(f"注册分子: {name}")    @classmethod    def get(cls, name: str) -> Optional[MoleculeData]:        """获取分子数据(延迟构建)"""        if name not in cls._molecules:            if name in cls._builders:                logger = LogManager()                logger.info(f"首次加载分子: {name}")                cls._molecules[name] = cls._builders[name]()            else:                return None        return cls._molecules[name]    @classmethod    def get_all_names(cls) -> List[str]:        """获取所有注册的分子名称"""        return list(cls._builders.keys())    @classmethod    def clear_cache(cls):        """清除缓存的分子数据"""        cls._molecules.clear()        LogManager().info("分子缓存已清除")# ============================================================================# 8. 分子构建器# ============================================================================class MoleculeBuilder:    """分子构建器"""    @staticmethod    @DecoratorManager.validate_output    @DecoratorManager.log_execution    @DecoratorManager.time_execution    def build_benzene() -> MoleculeData:        """构建苯环"""        logger = LogManager()        logger.info("开始构建苯环分子")        RING_RADIUS = 1.40        CH_BOND = 1.09        atoms_3d = []        atoms_2d = []        # 6个碳原子        for i in range(6):            angle = i * math.pi / 3            x = RING_RADIUS * math.cos(angle)            y = RING_RADIUS * math.sin(angle)            atoms_3d.append(AtomData(x, y, 0.0'C'f'C{i+1}'))            atoms_2d.append({'x': x, 'y': y, 'element''C''label'f'C{i+1}'})        # 6个氢原子        for i in range(6):            c_atom = atoms_3d[i]            angle = i * math.pi / 3            hx = c_atom.x + CH_BOND * math.cos(angle)            hy = c_atom.y + CH_BOND * math.sin(angle)            atoms_3d.append(AtomData(hx, hy, 0.0'H'f'H{i+1}'))            atoms_2d.append({'x': hx, 'y': hy, 'element''H''label'f'H{i+1}'})        # 键连关系        bonds = []        for i in range(6):            bonds.append((i, (i+1) % 6))  # C-C        for i in range(6):            bonds.append((i, i+6))         # C-H        molecule = MoleculeData(            name="苯", name_en="Benzene", formula="C₆H₆",            description="平面六元环,具有芳香性",            atoms_3d=atoms_3d, atoms_2d=atoms_2d,            bonds=bonds, delocalized_indices=list(range(6)),            reactions={                "卤化反应""C₆H₆ + X₂ → C₆H₅X + HX (催化剂: FeX₃)",                "硝化反应""C₆H₆ + HNO₃ → C₆H₅NO₂ + H₂O (催化剂: H₂SO₄)",                "磺化反应""C₆H₆ + H₂SO₄ → C₆H₅SO₃H + H₂O",            }        )        molecule.validate()        logger.info(f"苯环构建完成,原子数: {len(atoms_3d)}, 键数: {len(bonds)}")        return molecule    @staticmethod    @DecoratorManager.validate_output    @DecoratorManager.log_execution    @DecoratorManager.time_execution    def build_toluene() -> MoleculeData:        """构建甲苯"""        logger = LogManager()        logger.info("开始构建甲苯分子")        benzene = MoleculeBuilder.build_benzene()        # 用甲基替换一个氢        target_idx = 6  # 第一个氢的索引        c1 = benzene.atoms_3d[0]        h1 = benzene.atoms_3d[6]        # 甲基碳位置(沿 C1-H1 方向延伸)        dx = c1.x - h1.x        dy = c1.y - h1.y        dist = math.hypot(dx, dy)        if dist > 0:            dx /= dist            dy /= dist        ch3_x = c1.x + dx * 1.54        ch3_y = c1.y + dy * 1.54        # 替换氢为甲基碳        benzene.atoms_3d[6] = AtomData(ch3_x, ch3_y, 0.0'C''CH₃')        benzene.atoms_2d[6] = {'x': ch3_x, 'y': ch3_y, 'element''C''label''CH₃'}        # 更新键连关系        benzene.bonds = [b for b in benzene.bonds if b != (06)]        benzene.bonds.append((06))        benzene.name = "甲苯"        benzene.name_en = "Toluene"        benzene.formula = "C₆H₅-CH₃"        benzene.description = "苯环上有一个甲基取代"        benzene.delocalized_indices = list(range(6))        benzene.reactions = {            "侧链氯化""C₆H₅CH₃ + Cl₂ → C₆H₅CH₂Cl + HCl (光照)",            "氧化反应""C₆H₅CH₃ + KMnO₄ → C₆H₅COOH",        }        benzene.validate()        logger.info(f"甲苯构建完成")        return benzene# ============================================================================# 9. 渲染器# ============================================================================class BaseRenderer(ABC):    """渲染器抽象基类"""    @abstractmethod    def render(self, molecule: MoleculeData):        passclass Canvas2DRenderer(BaseRenderer):    """通用的 2D Canvas 渲染器"""    def __init__(self, canvas: tk.Canvas):        self.canvas = canvas        self.scale = 50        self.atom_radius = 15    def render(self, molecule: MoleculeData):        """通用渲染方法"""        self.canvas.delete("all")        if not molecule.atoms_2d:            return        # 计算画布中心        canvas_w = self.canvas.winfo_width() or 400        canvas_h = self.canvas.winfo_height() or 400        cx, cy = canvas_w // 2, canvas_h // 2        # 绘制化学键        for i, j in molecule.bonds:            if i < len(molecule.atoms_2d) and j < len(molecule.atoms_2d):                a1 = molecule.atoms_2d[i]                a2 = molecule.atoms_2d[j]                x1 = cx + a1['x'] * self.scale                y1 = cy - a1['y'] * self.scale                x2 = cx + a2['x'] * self.scale                y2 = cy - a2['y'] * self.scale                self.canvas.create_line(x1, y1, x2, y2, width=3, fill='#333')        # 绘制离域大π键        if molecule.delocalized_indices:            self._draw_delocalized(cx, cy, molecule)        # 绘制原子        for atom in molecule.atoms_2d:            x = cx + atom['x'] * self.scale            y = cy - atom['y'] * self.scale            color = molecule.atom_colors.get(atom['element'], '#808080')            self.canvas.create_oval(                x - self.atom_radius, y - self.atom_radius,                x + self.atom_radius, y + self.atom_radius,                fill=color, outline='#333', width=2            )            self.canvas.create_text(                x, y, text=atom['label'],                font=('Arial'10'bold'), fill='white'            )        # 标题        self.canvas.create_text(            cx, 30,            text=f"{molecule.name} ({molecule.name_en}) - {molecule.formula}",            font=('Arial'14'bold')        )    def _draw_delocalized(self, cx: int, cy: int, molecule: MoleculeData):        """绘制离域大π键标记"""        if not molecule.delocalized_indices:            return        points = []        for idx in molecule.delocalized_indices:            if idx < len(molecule.atoms_2d):                atom = molecule.atoms_2d[idx]                points.append((                    cx + atom['x'] * self.scale,                    cy - atom['y'] * self.scale                ))        if len(points) >= 3:            avg_x = sum(p[0for p in points) / len(points)            avg_y = sum(p[1for p in points) / len(points)            r = math.hypot(points[0][0] - avg_x, points[0][1] - avg_y)            self.canvas.create_oval(                avg_x - r, avg_y - r, avg_x + r, avg_y + r,                width=2, outline='#666', dash=(55)            )class Matplotlib3DRenderer(BaseRenderer):    """通用的 3D 渲染器"""    def __init__(self, parent_frame):        self.figure = Figure(figsize=(55), dpi=90, facecolor='#1a1a2e')        self.ax = self.figure.add_subplot(111, projection='3d')        self.canvas = FigureCanvasTkAgg(self.figure, master=parent_frame)        self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)        self.ax.set_facecolor('#1a1a2e')        self.ax.grid(True, alpha=0.3, color='white')    def render(self, molecule: MoleculeData):        """通用渲染方法"""        self.ax.clear()        self.ax.set_facecolor('#1a1a2e')        # 绘制化学键        for i, j in molecule.bonds:            if i < len(molecule.atoms_3d) and j < len(molecule.atoms_3d):                a1 = molecule.atoms_3d[i]                a2 = molecule.atoms_3d[j]                self.ax.plot(                    [a1.x, a2.x], [a1.y, a2.y], [a1.z, a2.z],                    'w-', linewidth=2, alpha=0.6                )        # 绘制原子        for atom in molecule.atoms_3d:            color = molecule.atom_colors.get(atom.element, '#808080')            size = 300 if atom.element == 'C' else 200            self.ax.scatter(                atom.x, atom.y, atom.z,                s=size, c=color, edgecolors='black', linewidth=1,                alpha=0.9, depthshade=True            )            self.ax.text(                atom.x, atom.y, atom.z + 0.15, atom.element,                color='white', fontsize=10, ha='center', fontweight='bold'            )        # 设置视图范围        center = molecule.get_center()        all_atoms = molecule.atoms_3d        max_range = max(            max(abs(a.x - center[0]) for a in all_atoms),            max(abs(a.y - center[1]) for a in all_atoms),            max(abs(a.z - center[2]) for a in all_atoms)        ) * 1.8        self.ax.set_xlim(center[0] - max_range, center[0] + max_range)        self.ax.set_ylim(center[1] - max_range, center[1] + max_range)        self.ax.set_zlim(center[2] - max_range, center[2] + max_range)        title = f"{molecule.name} ({molecule.name_en})\n{molecule.formula}"        self.ax.set_title(title, color='white', fontsize=12, pad=20)        self.ax.set_xlabel('X (Å)', color='white')        self.ax.set_ylabel('Y (Å)', color='white')        self.ax.set_zlabel('Z (Å)', color='white')        self.ax.tick_params(colors='white')        self.canvas.draw()# ============================================================================# 10. GUI 组件# ============================================================================class LogViewerWidget(ttk.Frame):    """日志查看器组件"""    def __init__(self, parent, *args, **kwargs):        super().__init__(parent, *args, **kwargs)        self.logger = LogManager()        self._build_ui()        self._update_display()    def _build_ui(self):        control_frame = ttk.Frame(self)        control_frame.pack(fill=tk.X, pady=2)        ttk.Label(control_frame, text="📋 操作日志").pack(side=tk.LEFT)        self.level_var = tk.StringVar(value="ALL")        level_combo = ttk.Combobox(            control_frame, textvariable=self.level_var,            values=["ALL""DEBUG""INFO""WARNING""ERROR""CRITICAL"],            state='readonly', width=10        )        level_combo.pack(side=tk.RIGHT, padx=2)        level_combo.bind('<<ComboboxSelected>>'lambda e: self._update_display())        ttk.Button(            control_frame, text="清除",            command=self._clear_logs        ).pack(side=tk.RIGHT, padx=2)        self.log_text = scrolledtext.ScrolledText(            self, height=8,            bg='#1e1e2e', fg='#00ff00',            font=('Consolas'9),            wrap=tk.WORD        )        self.log_text.pack(fill=tk.BOTH, expand=True)        self.log_text.tag_config('DEBUG', foreground='#888888')        self.log_text.tag_config('INFO', foreground='#00ff00')        self.log_text.tag_config('WARNING', foreground='#ffaa00')        self.log_text.tag_config('ERROR', foreground='#ff4444')        self.log_text.tag_config('CRITICAL', foreground='#ff0000', font=('Consolas'9'bold'))        self._schedule_update()    def _update_display(self):        level_filter = self.level_var.get()        if level_filter == "ALL":            level_filter = None        logs = self.logger.get_recent_logs(100, level_filter)        self.log_text.config(state='normal')        self.log_text.delete(1.0, tk.END)        for entry in logs:            timestamp = entry['timestamp'].strftime('%H:%M:%S')            line = f"[{timestamp}{entry['level']:8}{entry['message']}\n"            self.log_text.insert(tk.END, line, entry['level'])        self.log_text.see(tk.END)        self.log_text.config(state='disabled')    def _clear_logs(self):        self.logger.clear_buffer()        self._update_display()    def _schedule_update(self):        self._update_display()        self.after(2000self._schedule_update)class StatusPanel(ttk.Frame):    """状态监控面板"""    def __init__(self, parent, *args, **kwargs):        super().__init__(parent, *args, **kwargs)        self.state_manager = StateManager()        self.logger = LogManager()        self._build_ui()        self._update_status()    def _build_ui(self):        ttk.Label(self, text="📊 系统状态", font=('Arial'10'bold')).pack(anchor='w', pady=5)        self.status_frame = ttk.Frame(self)        self.status_frame.pack(fill=tk.X, pady=5)        self.status_labels = {}        status_items = [            ("应用状态:", AppState.READY.value),            ("运行时间:""0.0s"),            ("错误计数:""0"),            ("加载分子:""无"),            ("内存日志:""0条"),        ]        for i, (label, value) in enumerate(status_items):            ttk.Label(self.status_frame, text=label).grid(row=i, column=0, sticky='w', padx=5)            value_label = ttk.Label(self.status_frame, text=value)            value_label.grid(row=i, column=1, sticky='w', padx=5)            self.status_labels[label] = value_label    def _update_status(self):        try:            self.status_labels["应用状态:"].config(                text=self.state_manager.state.value,                foreground=self._get_state_color(self.state_manager.state)            )            uptime = self.state_manager.get_uptime()            self.status_labels["运行时间:"].config(text=f"{uptime:.1f}s")            self.status_labels["错误计数:"].config(                text=str(self.state_manager.get_error_count())            )            log_count = len(self.logger.log_buffer)            self.status_labels["内存日志:"].config(text=f"{log_count}条")        except Exception:            pass        self.after(1000self._update_status)    def _get_state_color(self, state: AppState) -> str:        colors = {            AppState.INIT: 'gray',            AppState.READY: 'green',            AppState.LOADING: 'blue',            AppState.RENDERING: 'orange',            AppState.ERROR: 'red',        }        return colors.get(state, 'black')    def update_molecule(self, name: str):        self.status_labels["加载分子:"].config(text=name)# ============================================================================# 11. 主应用# ============================================================================class OrganicLabApp:    """应用程序主控制器"""    def __init__(self, root: tk.Tk):        self.root = root        self.config = AppConfig.load()        self.logger = LogManager()        self.state_manager = StateManager()        self.current_molecule: Optional[MoleculeData] = None        self.state_manager.transition(AppState.INIT, "应用启动")        self._setup_registry()        self._build_ui()        self.state_manager.transition(AppState.READY, "界面初始化完成")        if self.config.auto_load_default:            self._load_molecule(self.config.default_molecule)    def _setup_registry(self):        """设置分子注册表"""        MoleculeRegistry.register("苯", MoleculeBuilder.build_benzene)        MoleculeRegistry.register("甲苯", MoleculeBuilder.build_toluene)        self.logger.info(f"已注册分子: {', '.join(MoleculeRegistry.get_all_names())}")    def _build_ui(self):        """构建用户界面"""        self.root.title('OrganicLab v3.0 - 完整管理系统')        self.root.geometry(f"{self.config.window_width}x{self.config.window_height}")        # 主容器        main_paned = ttk.PanedWindow(self.root, orient=tk.HORIZONTAL)        main_paned.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)        # 左侧控制面板        left_frame = ttk.Frame(main_paned)        main_paned.add(left_frame, weight=0)        self._build_control_panel(left_frame)        # 右侧视图面板        right_paned = ttk.PanedWindow(main_paned, orient=tk.VERTICAL)        main_paned.add(right_paned, weight=1)        # 2D 视图        self.canvas_2d = tk.Canvas(right_paned, bg='white', height=300)        right_paned.add(self.canvas_2d, weight=1)        self.renderer_2d = Canvas2DRenderer(self.canvas_2d)        # 3D 视图        self.frame_3d = ttk.Frame(right_paned)        right_paned.add(self.frame_3d, weight=2)        # 底部日志面板        self.log_viewer = LogViewerWidget(self.root)        self.log_viewer.pack(side=tk.BOTTOM, fill=tk.X, padx=5, pady=5)    def _build_control_panel(self, parent):        """构建控制面板"""        # 标题        ttk.Label(            parent, text="🧪 OrganicLab v3.0",            font=('Arial'14'bold')        ).pack(pady=10)        # 分子选择        mol_frame = ttk.LabelFrame(parent, text="分子选择", padding=10)        mol_frame.pack(fill=tk.X, padx=5, pady=5)        self.mol_var = tk.StringVar(value=self.config.default_molecule)        mol_list = MoleculeRegistry.get_all_names()        self.mol_combo = ttk.Combobox(            mol_frame, textvariable=self.mol_var,            values=mol_list, state='readonly'        )        self.mol_combo.pack(fill=tk.X, pady=5)        ttk.Button(            mol_frame, text="🔬 加载分子",            command=self._on_load        ).pack(fill=tk.X, pady=5)        # 反应方程式        rxn_frame = ttk.LabelFrame(parent, text="反应方程式", padding=10)        rxn_frame.pack(fill=tk.X, padx=5, pady=5)        self.reaction_var = tk.StringVar()        self.reaction_combo = ttk.Combobox(            rxn_frame, textvariable=self.reaction_var,            state='readonly'        )        self.reaction_combo.pack(fill=tk.X, pady=5)        self.reaction_combo.bind('<<ComboboxSelected>>'self._on_reaction_select)        ttk.Button(            rxn_frame, text="⚡ 查看方程式",            command=self._show_reaction        ).pack(fill=tk.X, pady=5)        # 分子信息        info_frame = ttk.LabelFrame(parent, text="📊 分子信息", padding=10)        info_frame.pack(fill=tk.X, padx=5, pady=5)        self.info_text = tk.Text(info_frame, height=10, width=30,                                 font=('Arial'9), wrap=tk.WORD)        self.info_text.pack(fill=tk.BOTH, expand=True)        # 状态面板        self.status_panel = StatusPanel(parent)        self.status_panel.pack(fill=tk.X, padx=5, pady=5)    @DecoratorManager.log_execution    @DecoratorManager.time_execution    def _load_molecule(self, name: str):        """加载分子"""        self.state_manager.transition(AppState.LOADING, f"加载 {name}")        try:            molecule = MoleculeRegistry.get(name)            if molecule is None:                raise ValueError(f"未找到分子: {name}")            if not molecule.validate():                self.logger.warning(f"{name} 数据验证有警告,继续加载")            self.current_molecule = molecule            # 更新视图            self.state_manager.transition(AppState.RENDERING, "渲染 2D")            self.renderer_2d.render(molecule)            self.state_manager.transition(AppState.RENDERING, "渲染 3D")            self._update_3d_view(molecule)            # 更新 UI            self._update_reactions(molecule)            self._update_info(molecule)            self.status_panel.update_molecule(name)            self.state_manager.transition(AppState.READY, f"{name} 加载完成")        except Exception as e:            self.logger.error(f"加载失败: {str(e)}\n{traceback.format_exc()}")            self.state_manager.transition(AppState.ERROR, str(e))            messagebox.showerror("加载错误"f"无法加载 {name}:\n{str(e)}")    @DecoratorManager.retry_on_failure(max_retries=2)    def _update_3d_view(self, molecule):        """更新 3D 视图"""        for widget in self.frame_3d.winfo_children():            widget.destroy()        self.renderer_3d = Matplotlib3DRenderer(self.frame_3d)        self.renderer_3d.render(molecule)    def _on_load(self):        """处理加载按钮点击"""        name = self.mol_var.get()        if name:            self._load_molecule(name)    def _on_reaction_select(self, event):        """处理反应选择事件"""        self._show_reaction()    def _show_reaction(self):        """显示反应方程式"""        if not self.current_molecule:            return        rxn_name = self.reaction_var.get()        equation = self.current_molecule.reactions.get(rxn_name, "")        if equation:            self.logger.info(f"查看反应: {rxn_name}")            messagebox.showinfo(                f"{self.current_molecule.name} - {rxn_name}",                equation            )    def _update_reactions(self, molecule):        """更新反应列表"""        reaction_names = list(molecule.reactions.keys())        self.reaction_combo['values'] = reaction_names        if reaction_names:            self.reaction_combo.set(reaction_names[0])    def _update_info(self, molecule):        """更新信息面板"""        self.info_text.config(state='normal')        self.info_text.delete(1.0, tk.END)        info = [            f"名称: {molecule.name} ({molecule.name_en})",            f"分子式: {molecule.formula}",            f"描述: {molecule.description}",            f"原子数: {len(molecule.atoms_3d)}",            f"化学键数: {len(molecule.bonds)}",            "",            "验证结果:",        ]        if molecule._validation_errors:            info.append(f"  ❌ 错误: {', '.join(molecule._validation_errors)}")        else:            info.append("  ✅ 数据验证通过")        if molecule._validation_warnings:            info.append(f"  ⚠️ 警告: {', '.join(molecule._validation_warnings)}")        info.extend(["""反应类型:"])        for rxn_name in molecule.reactions:            info.append(f"  • {rxn_name}")        self.info_text.insert(1.0'\n'.join(info))        self.info_text.config(state='disabled')# ============================================================================# 12. 程序入口# ============================================================================def setup_exception_handler():    """设置全局异常处理器"""    def global_handler(exc_type, exc_value, exc_tb):        logger = LogManager()        logger.critical(            f"未捕获的异常: {exc_type.__name__}{exc_value}\n"            f"{''.join(traceback.format_tb(exc_tb))}"        )        AppConfig.load().save()    import sys    sys.excepthook = global_handlerdef main():    """程序主入口"""    setup_exception_handler()    logger = LogManager()    logger.info("=" * 50)    logger.info("OrganicLab v3.0 - 启动")    logger.info("完整日志和管理系统已初始化")    logger.info("=" * 50)    try:        root = tk.Tk()        app = OrganicLabApp(root)        def on_closing():            logger.info("应用关闭中...")            AppConfig.load().save()            root.destroy()        root.protocol("WM_DELETE_WINDOW", on_closing)        root.mainloop()    except Exception as e:        logger.critical(f"应用启动失败: {str(e)}\n{traceback.format_exc()}")        raiseif __name__ == '__main__':    main()
谢谢 梁锦麟 2026年6月2日

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-07-03 03:49:26 HTTP/2.0 GET : https://f.mffb.com.cn/a/497041.html
  2. 运行时间 : 0.104208s [ 吞吐率:9.60req/s ] 内存消耗:4,781.76kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=198bc092a9e61e7aa3f286d76811fd42
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000600s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000899s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000342s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000275s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000485s ]
  6. SELECT * FROM `set` [ RunTime:0.000204s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000489s ]
  8. SELECT * FROM `article` WHERE `id` = 497041 LIMIT 1 [ RunTime:0.004251s ]
  9. UPDATE `article` SET `lasttime` = 1783021766 WHERE `id` = 497041 [ RunTime:0.011599s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 66 LIMIT 1 [ RunTime:0.000316s ]
  11. SELECT * FROM `article` WHERE `id` < 497041 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000613s ]
  12. SELECT * FROM `article` WHERE `id` > 497041 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.000388s ]
  13. SELECT * FROM `article` WHERE `id` < 497041 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.005239s ]
  14. SELECT * FROM `article` WHERE `id` < 497041 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.000937s ]
  15. SELECT * FROM `article` WHERE `id` < 497041 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.008845s ]
0.105865s