当前位置:首页>python>Python 设计模式:备忘录模式——给对象拍一张"快照"

Python 设计模式:备忘录模式——给对象拍一张"快照"

  • 2026-06-30 01:00:01
Python 设计模式:备忘录模式——给对象拍一张"快照"

你有没有用过 Git 的版本回退?或者游戏的存档/读档功能?这些看似不同的操作,背后其实都是同一个设计思想——备忘录模式(Memento Pattern)

想象一下:你正在开发一个文本编辑器,用户希望随时撤销(Undo)操作。如果每次编辑都直接修改原始数据,撤销将变得极其困难。备忘录模式就是解决这类问题的利器——它让你在不破坏封装性的前提下,捕获对象的内部状态,并在之后恢复到这个状态。

今天我们就来深入理解这个"时光倒流"的设计模式。


一、什么是备忘录模式

备忘录模式(Memento Pattern) 是一种行为型设计模式,它允许在不暴露对象实现细节的情况下,保存和恢复对象的内部状态。

为什么需要备忘录模式

假设你在开发一个画板应用,用户可以在画布上绘制各种图形:

class Canvas:    def __init__(self):        self.shapes = []    def add_shape(self, shape):        self.shapes.append(shape)    def clear(self):        self.shapes = []

用户画了三个矩形,然后点击"清空"。这时候他后悔了,想把之前的图形恢复回来。怎么办?

一种简单的做法是直接保存整个对象的副本:

# 保存状态backup = canvas.shapes.copy()# 清空画布canvas.clear()# 恢复状态canvas.shapes = backup

这种方式看似可行,但存在严重问题:

  • 破坏封装性:直接访问和修改对象的内部属性,违反了面向对象的封装原则
  • 难以扩展:如果 Canvas 新增了 background_colorgrid_visible 等属性,你必须记得更新所有保存/恢复逻辑
  • 职责混乱:Canvas 既要管理绘图,又要管理状态保存,违反了单一职责原则

备忘录模式通过引入一个独立的"备忘录"对象,优雅地解决了这些问题。

备忘录模式的三个角色

备忘录模式包含三个核心角色:

  1. Originator(原发器):需要保存状态的对象,它创建备忘录来记录当前内部状态,也可以使用备忘录来恢复状态
  2. Memento(备忘录):存储 Originator 内部状态的对象,它对其他对象不透明,只暴露必要的接口
  3. Caretaker(负责者):负责保存备忘录的对象,但从不操作或检查备忘录的内容

三者的关系如下:

  • Originator 创建 Memento → Caretaker 存储 Memento
  • 需要恢复时 → Caretaker 将 Memento 返还给 Originator → Originator 用 Memento 恢复自身状态

二、Python 实现备忘录模式

2.1 经典实现:三角色结构

下面是备忘录模式的经典 Python 实现:

class CanvasMemento:    """备忘录——存储 Canvas 的状态"""    def __init__(self, shapes, background_color):        # 使用私有属性,外部无法直接访问        self._shapes = [s.copy() for s in shapes]        self._background_color = background_colorclass Canvas:    """原发器——需要保存状态的对象"""    def __init__(self):        self.shapes = []        self.background_color = "#FFFFFF"    def add_shape(self, shape):        self.shapes.append(shape)    def set_background(self, color):        self.background_color = color    def clear(self):        self.shapes = []    def display(self):        print(f"背景色: {self.background_color}, 图形: {self.shapes}")    def save(self):        """创建备忘录——保存当前状态"""        return CanvasMemento(self.shapes, self.background_color)    def restore(self, memento):        """从备忘录恢复状态"""        self.shapes = memento._shapes        self.background_color = memento._background_colorclass CanvasHistory:    """负责者——管理备忘录的保存和恢复"""    def __init__(self):        self._history = []    def push(self, memento):        """保存一个备忘录"""        self._history.append(memento)    def pop(self):        """取出最近的备忘录"""        if not self._history:            return None        return self._history.pop()

使用方式:

canvas = Canvas()history = CanvasHistory()# 初始状态canvas.add_shape("矩形A")canvas.add_shape("圆形B")canvas.display()# 输出: 背景色: #FFFFFF, 图形: ['矩形A', '圆形B']# 保存当前状态history.push(canvas.save())# 修改状态canvas.add_shape("三角形C")canvas.set_background("#F0F0F0")canvas.display()# 输出: 背景色: #F0F0F0, 图形: ['矩形A', '圆形B', '三角形C']# 撤销——恢复到之前的状态memento = history.pop()canvas.restore(memento)canvas.display()# 输出: 背景色: #FFFFFF, 图形: ['矩形A', '圆形B']

关键设计要点

  • CanvasMemento
     的属性使用下划线前缀(_shapes_background_color),虽然 Python 没有真正的私有属性,但这是一种约定,表明外部不应该直接访问
  • Canvas
     负责"打包"和"解包"状态,CanvasHistory 只负责存储,完全不关心备忘录里的内容
  • 撤销操作非常自然:history.pop() 取出最近的备忘录,交给 canvas.restore() 恢复

2.2 使用 dataclass 简化备忘录

Python 的 dataclass 可以让备忘录的定义更加简洁:

from dataclasses import dataclass, fieldfrom copy import deepcopy@dataclassclass CanvasMemento:    """备忘录——使用 dataclass 自动生成 __init__"""    shapes: list = field(default_factory=list)    background_color: str = "#FFFFFF"class Canvas:    def __init__(self):        self.shapes = []        self.background_color = "#FFFFFF"    def add_shape(self, shape):        self.shapes.append(shape)    def set_background(self, color):        self.background_color = color    def clear(self):        self.shapes = []    def save(self):        """创建备忘录时深拷贝,确保状态独立"""        return CanvasMemento(            shapes=deepcopy(self.shapes),            background_color=self.background_color        )    def restore(self, memento):        """恢复时深拷贝,避免备忘录和当前对象共享引用"""        self.shapes = deepcopy(memento.shapes)        self.background_color = memento.background_color

使用 dataclass 的好处:

  • 自动生成 __init____repr__ 等方法,减少样板代码
  • 类型注解让备忘录的字段一目了然
  • 配合 deepcopy(),确保保存和恢复时状态完全独立

三、实战案例:文本编辑器的撤销功能

让我们用一个更贴近实际的案例来深入理解备忘录模式——实现一个支持多步撤销的文本编辑器:

from dataclasses import dataclassfrom copy import deepcopy@dataclassclass EditorMemento:    """备忘录——保存编辑器的完整状态"""    content: str    cursor_position: int    selection_start: int    selection_end: intclass TextEditor:    """原发器——文本编辑器"""    def __init__(self):        self.content = ""        self.cursor_position = 0        self.selection_start = 0        self.selection_end = 0    def type_text(self, text):        """在光标位置输入文本"""        self.content = (            self.content[: self.cursor_position]            + text            + self.content[self.cursor_position :]        )        self.cursor_position += len(text)    def delete(self):        """删除光标前的一个字符"""        if self.cursor_position > 0:            self.content = (                self.content[: self.cursor_position - 1]                + self.content[self.cursor_position :]            )            self.cursor_position -= 1    def move_cursor(self, position):        """移动光标"""        self.cursor_position = max(0min(position, len(self.content)))    def select(self, start, end):        """选择文本"""        self.selection_start = max(0, start)        self.selection_end = min(end, len(self.content))    def display(self):        """显示当前状态"""        cursor_line = " " * self.cursor_position + "^"        print(f"内容: [{self.content}]")        print(f"光标: {cursor_line} (位置 {self.cursor_position})")    def save(self):        """创建备忘录"""        return EditorMemento(            content=self.content,            cursor_position=self.cursor_position,            selection_start=self.selection_start,            selection_end=self.selection_end,        )    def restore(self, memento):        """从备忘录恢复"""        self.content = memento.content        self.cursor_position = memento.cursor_position        self.selection_start = memento.selection_start        self.selection_end = memento.selection_endclass UndoManager:    """负责者——管理撤销/重做历史"""    def __init__(self, max_history=50):        self._undo_stack = []        self._redo_stack = []        self._max_history = max_history    def save_state(self, memento):        """保存状态到撤销栈"""        self._undo_stack.append(memento)        # 每次新操作后清空重做栈        self._redo_stack.clear()        # 限制历史记录数量        if len(self._undo_stack) > self._max_history:            self._undo_stack.pop(0)    def undo(self):        """撤销:从撤销栈取出,压入重做栈"""        if len(self._undo_stack) <= 1:            print("没有更多可撤销的操作")            return None        memento = self._undo_stack.pop()        self._redo_stack.append(memento)        # 返回撤销后应该恢复的状态        return self._undo_stack[-1]    def redo(self):        """重做:从重做栈取出,压入撤销栈"""        if not self._redo_stack:            print("没有更多可重做的操作")            return None        memento = self._redo_stack.pop()        self._undo_stack.append(memento)        return memento    @property    def can_undo(self):        return len(self._undo_stack) > 1    @property    def can_redo(self):        return len(self._redo_stack) > 0

使用方式:

editor = TextEditor()undo_manager = UndoManager()# 初始状态undo_manager.save_state(editor.save())# 输入 "Hello"editor.type_text("Hello")undo_manager.save_state(editor.save())# 输入 " World"editor.type_text(" World")undo_manager.save_state(editor.save())# 删除一个字符editor.delete()undo_manager.save_state(editor.save())editor.display()# 输出: 内容: [Hello Worl]#       光标:           ^ (位置 10)# 撤销删除state = undo_manager.undo()if state:    editor.restore(state)editor.display()# 输出: 内容: [Hello World]#       光标:            ^ (位置 11)# 撤销 " World" 的输入state = undo_manager.undo()if state:    editor.restore(state)editor.display()# 输出: 内容: [Hello]#       光标:       ^ (位置 5)# 重做state = undo_manager.redo()if state:    editor.restore(state)editor.display()# 输出: 内容: [Hello World]#       光标:            ^ (位置 11)

这个例子展示了备忘录模式的完整应用:

  • EditorMemento:备忘录,封装了编辑器的所有状态
  • TextEditor:原发器,负责创建和恢复备忘录
  • UndoManager:负责者,管理撤销/重做栈,不关心备忘录内部内容

四、Pythonic 的备忘录实现方式

经典的三角色结构虽然清晰,但在 Python 中,我们可以用更适合语言特性的方式来实现。

4.1 使用 __dict__ 实现通用备忘录

如果你只需要保存对象的所有属性状态,Python 的 __dict__ 提供了一种极简的实现方式:

from copy import deepcopyclass UniversalMemento:    """通用备忘录——保存任意对象的 __dict__"""    def __init__(self, state_dict):        self._state = state_dictclass UniversalOriginator:    """通用原发器混入类"""    def save(self):        """保存当前状态"""        return UniversalMemento(deepcopy(self.__dict__))    def restore(self, memento):        """恢复状态"""        self.__dict__ = deepcopy(memento._state)# 任何类都可以继承这个混入类来获得备忘录功能class GameCharacter(UniversalOriginator):    def __init__(self, name, hp, mp, position):        self.name = name        self.hp = hp        self.mp = mp        self.position = position    def __repr__(self):        return f"GameCharacter({self.name}, HP={self.hp}, MP={self.mp}, Pos={self.position})"# 使用hero = GameCharacter("勇者"10050, (00))save_point = hero.save()hero.hp = 30hero.position = (510)print(hero)  # GameCharacter(勇者, HP=30, MP=50, Pos=(5, 10))hero.restore(save_point)print(hero)  # GameCharacter(勇者, HP=100, MP=50, Pos=(0, 0))

这种方式的优点是极度简洁——任何类只需继承 UniversalOriginator,就能立即获得状态保存和恢复的能力。

但它的局限性也很明显:

  • 保存了所有属性,无法选择性保存(可能有些属性不需要保存,如缓存、临时状态)
  • 使用了 deepcopy,对于复杂对象嵌套较深时性能可能不佳
  • 备忘录对原发器的内部结构完全透明,没有实现信息隐藏

4.2 使用 __getstate__ 和 __setstate__

Python 的 pickle 模块使用了 __getstate__ 和 __setstate__ 协议来控制序列化行为。我们可以借用这个机制实现备忘录:

import copyclass GameState:    """游戏状态——支持选择性状态保存"""    def __init__(self):        self.level = 1        self.score = 0        self.player_hp = 100        self.player_position = (00)        self._cache = {}  # 临时缓存,不需要保存        self._dirty = True  # 脏标记,不需要保存    def __getstate__(self):        """定义哪些状态需要保存"""        state = self.__dict__.copy()        # 排除不需要保存的属性        state.pop("_cache"None)        state.pop("_dirty"None)        return state    def __setstate__(self, state):        """恢复状态时补充默认值"""        self.__dict__.update(state)        self._cache = {}        self._dirty = True    def save(self):        """创建备忘录"""        return copy.deepcopy(self.__getstate__())    def restore(self, memento):        """从备忘录恢复"""        self.__setstate__(copy.deepcopy(memento))# 使用game = GameState()game.level = 5game.score = 3000game.player_hp = 60game._cache = {"enemies": ["goblin""orc"]}  # 临时数据save_state = game.save()# 游戏继续进行game.level = 6game.score = 5000game.player_hp = 10  # 快死了!# 恢复到之前的存档game.restore(save_state)print(f"等级: {game.level}, 分数: {game.score}, 血量: {game.player_hp}")# 输出: 等级: 5, 分数: 3000, 血量: 60print(f"缓存: {game._cache}")  # 缓存被重置为空字典

这种方式的优势:

  • 选择性保存:通过 __getstate__ 精确控制哪些属性需要保存
  • 恢复时补充默认值:__setstate__ 可以在恢复时为非持久化属性设置合理默认值
  • 与 pickle 协议兼容:直接支持 Python 标准的序列化接口

4.3 使用装饰器实现状态快照

如果你想更细粒度地标记哪些方法需要保存状态,可以用装饰器模式配合备忘录模式:

from functools import wrapsfrom copy import deepcopydef snapshotable(method):    """装饰器:在方法执行前自动保存状态快照"""    @wraps(method)    def wrapper(self, *args, **kwargs):        # 执行前保存状态        if hasattr(self"_snapshot_history"):            self._snapshot_history.append(deepcopy(self.__dict__))        result = method(self, *args, **kwargs)        return result    return wrapperclass SmartDocument:    """智能文档——自动快照"""    def __init__(self):        self.content = ""        self.font_size = 14        self._snapshot_history = []    @snapshotable    def type_text(self, text):        self.content += text    @snapshotable    def set_font_size(self, size):        self.font_size = size    @snapshotable    def clear(self):        self.content = ""    def undo(self):        if self._snapshot_history:            self.__dict__.update(self._snapshot_history.pop())        else:            print("没有更多可撤销的操作")    def display(self):        print(f"内容: [{self.content}], 字号: {self.font_size}")# 使用doc = SmartDocument()doc.type_text("你好")doc.type_text(",世界")doc.set_font_size(16)doc.display()  # 内容: [你好,世界], 字号: 16doc.undo()  # 撤销字号修改doc.display()  # 内容: [你好,世界], 字号: 14doc.undo()  # 撤销",世界"doc.display()  # 内容: [你好], 字号: 14

装饰器方式让代码更声明式——哪些方法会触发快照一目了然。


五、备忘录模式的应用场景

备忘录模式在实际开发中有广泛的应用,以下是几个典型场景:

场景 1:游戏存档/读档

这是最经典的应用场景。玩家可以在关键节点保存游戏进度,死亡后从存档点重新开始:

# 游戏角色状态保存hero = GameCharacter("勇者"10050, (1020))save_file = hero.save()  # 存档hero.hp = 0  # 角色阵亡hero.restore(save_file)  # 读档,满血复活

场景 2:配置系统的版本管理

应用的配置经常需要回滚到之前的版本:

# 保存配置快照config_snapshot = app_config.save()# 修改配置(可能出问题)app_config.set("timeout"5)app_config.set("retry"0)# 发现问题,回滚到之前的配置app_config.restore(config_snapshot)

场景 3:事务回滚

数据库事务的回滚机制本质上就是备忘录模式的应用:

# 开启事务前保存状态before = db_state.save()try:    # 执行一系列操作    db.execute("UPDATE accounts SET balance = balance - 100")    db.execute("UPDATE accounts SET balance = balance + 100")    db.commit()except Exception:    # 出错时回滚    db_state.restore(before)

场景 4:工作流状态回退

审批流程中,可能需要将流程回退到上一步:

# 保存流程状态approval_snapshot = workflow.save_state()# 推进到下一步workflow.approve()# 发现问题,回退到之前的状态workflow.restore_state(approval_snapshot)

六、备忘录模式 vs 其他模式的对比

6.1 备忘录 vs 命令模式

命令模式和备忘录模式都可以实现撤销功能,但思路完全不同:

  • 备忘录模式:保存对象的完整状态快照,恢复时直接替换。简单粗暴,但可能占用较多内存
  • 命令模式:保存操作本身(执行+反向操作),撤销时执行反向操作。更精细,但需要为每个操作实现对应的逆操作
# 备忘录方式撤销:直接恢复状态memento = editor.save()editor.type_text("Hello")editor.restore(memento)  # 状态直接回到之前# 命令方式撤销:执行逆操作class TypeTextCommand:    def execute(self):        editor.type_text(self.text)    def undo(self):        editor.delete(len(self.text))  # 需要实现逆操作

选择建议

  • 如果状态简单、操作复杂 → 用备忘录模式
  • 如果状态庞大、操作可逆 → 用命令模式
  • 两者也可以结合使用:用备忘录保存关键节点的状态,用命令模式处理中间步骤

6.2 备忘录 vs 原型模式

原型模式通过克隆创建新对象,和备忘录的 deepcopy 看起来很像:

  • 原型模式:目的是创建新对象,克隆后继续独立使用
  • 备忘录模式:目的是保存和恢复状态,备忘录是临时存储,不是用来创建新对象的
# 原型模式:克隆一个新对象来使用prototype = Document("模板")doc1 = prototype.clone()doc1.content = "文档1"# 备忘录模式:保存状态以便恢复memento = editor.save()  # 保存状态editor.type_text("新内容")  # 修改editor.restore(memento)  # 恢复到之前的状态

七、最佳实践与注意事项

7.1 控制备忘录的内存消耗

备忘录模式最大的风险是内存消耗。每次保存状态都会创建一份深拷贝,如果对象很大或保存频率很高,内存会迅速增长。

解决方案:限制历史记录数量,并采用增量保存。

class BoundedHistory:    """有界历史——限制备忘录数量"""    def __init__(self, max_size=20):        self._history = []        self._max_size = max_size    def push(self, memento):        self._history.append(memento)        if len(self._history) > self._max_size:            self._history.pop(0)  # 移除最旧的记录    def pop(self):        if self._history:            return self._history.pop()        return None    def clear(self):        self._history.clear()

7.2 何时使用深拷贝 vs 浅拷贝

  • 深拷贝(deepcopy):当对象包含可变嵌套结构(列表、字典、自定义对象)时,必须使用深拷贝,否则备忘录和原对象会共享引用
  • 浅拷贝(copy):当对象只包含不可变数据(字符串、数字、元组)时,浅拷贝就足够了,性能也更好
  • 选择性拷贝:只为需要保存的属性执行深拷贝,跳过临时数据和缓存
import copyclass SelectiveMemento:    """选择性深拷贝备忘录"""    def __init__(self, content, metadata):        # content 是可变字符串列表,需要深拷贝        self.content = copy.deepcopy(content)        # metadata 是不可变元组,直接赋值即可        self.metadata = metadata

7.3 备忘录的不可变性

备忘录一旦创建,就不应该被修改。只有 Originator 才有权读取备忘录的内容。在 Python 中,可以通过以下方式增强备忘录的不可变性:

class ImmutableMemento:    """不可变备忘录"""    __slots__ = ("_content""_cursor_position")    def __init__(self, content, cursor_position):        object.__setattr__(self"_content", content)        object.__setattr__(self"_cursor_position", cursor_position)    def __setattr__(self, name, value):        raise AttributeError("备忘录是不可变的,不允许修改")    @property    def content(self):        return self._content    @property    def cursor_position(self):        return self._cursor_position

7.4 备忘录与性能

在高频操作场景下(比如文本编辑器每次按键都保存),保存完整的备忘录可能影响性能。这时可以考虑以下优化:

  • 操作间隔保存:不是每次操作都保存,而是按时间间隔或操作次数保存
  • 增量备忘录:只保存变化的属性,而非完整状态
  • 延迟深拷贝:使用写时复制(Copy-on-Write)策略,只在修改时才真正执行拷贝

八、总结

备忘录模式是"时光机"般的设计模式——它让我们可以随时保存对象的状态快照,并在需要时恢复到任意历史时刻。

回顾本文要点:

  1. 备忘录模式的核心思想:在不破坏封装性的前提下,捕获并保存对象的内部状态,以便之后恢复
  2. 三个角色:Originator(原发器)创建和恢复备忘录,Memento(备忘录)存储状态,Caretaker(负责者)管理备忘录的生命周期
  3. Pythonic 实现:从经典三角色到 __dict__ 快照、__getstate__/__setstate__、装饰器快照,Python 提供了多种灵活的实现方式
  4. 典型应用:编辑器撤销/重做、游戏存档、配置回滚、事务回滚
  5. 与命令模式对比:备忘录保存完整状态,命令模式保存操作及其逆操作;两者可以结合使用
  6. 注意事项:警惕内存消耗,合理选择深拷贝/浅拷贝,保持备忘录的不可变性

掌握了备忘录模式,你就拥有了让对象"穿越时空"的能力——无论是撤销操作、回滚状态,还是存档读档,都可以优雅地实现。


如果这篇文章对你有帮助,欢迎点赞、在看、转发,你的支持是我持续创作的动力!

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-07-03 20:45:05 HTTP/2.0 GET : https://f.mffb.com.cn/a/492864.html
  2. 运行时间 : 0.217654s [ 吞吐率:4.59req/s ] 内存消耗:4,719.99kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=967d84109e5fb15b799a2cd9f7df9db4
  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.000973s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.001404s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.001530s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000737s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.001363s ]
  6. SELECT * FROM `set` [ RunTime:0.000571s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.001384s ]
  8. SELECT * FROM `article` WHERE `id` = 492864 LIMIT 1 [ RunTime:0.007567s ]
  9. UPDATE `article` SET `lasttime` = 1783082705 WHERE `id` = 492864 [ RunTime:0.006203s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 66 LIMIT 1 [ RunTime:0.000721s ]
  11. SELECT * FROM `article` WHERE `id` < 492864 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.001179s ]
  12. SELECT * FROM `article` WHERE `id` > 492864 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.000964s ]
  13. SELECT * FROM `article` WHERE `id` < 492864 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.002273s ]
  14. SELECT * FROM `article` WHERE `id` < 492864 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.015351s ]
  15. SELECT * FROM `article` WHERE `id` < 492864 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.015759s ]
0.221616s