乐于分享
好东西不私藏

AI编程助手在单体仓库中引发代码重复的实战记录与防护方法论

AI编程助手在单体仓库中引发代码重复的实战记录与防护方法论

A Practical Record of AI-Assisted Code Duplication in Monorepos and the Defense Methodology

作者: 林慕空日期: 2026年7月性质: 工作备忘录(以论文形式组织)


作者注: 本文不是传统学术论文,而是一份工作日志。它记录了我们在开发 ppt-bot-v2(一个10包单体仓库的PDF智能处理系统)过程中,与AI编程助手(Codex CLI)协作时遭遇的代码重复问题、排查过程、修复方案,以及最终建立的多层防护体系。所有代码示例、git提交记录和错误日志均来自真实项目。


摘要

大语言模型(LLM)驱动的AI编程助手在提升开发效率的同时,也引入了一个隐蔽但严重的问题:AI代理倾向于为每个子任务独立实现功能逻辑,而非复用项目中已有的共享组件。本文记录了一个真实案例——在一天内(2026年7月5日至7日),AI助手为一个10包单体仓库生成了超过13000行代码,其中包含了至少6类、约300行的跨包重复代码。我们通过三个阶段的排查和修复,最终建立了包含文档约束、行为规则、自动化检测和架构设计四个层次的防护体系,将跨包重复率从67%降至接近0%。

关键词: AI编程助手;代码重复;单体仓库;Codex;代码质量


1 问题是怎么发生的

1.1 项目背景

ppt-bot-v2 是一个PDF智能处理系统,核心功能是将中文PDF教材自动转换为PowerPoint演示文稿,并提取结构化知识(术语、关系、思维导图、学习路径等)。

2026年7月5日,我们决定将原本集中在 backend/apps/pipelines/services.py 中的1179行单体服务代码,拆分为10个独立的Python包:

1
2
3
4
5
6
7
8
9
10
pdf-chapter-splitter   # 章节检测与拆分pdf-image-extractor    # 图像提取ppt-generator          # PPT生成rag-indexer            # 语义搜索索引summary-generator      # 摘要生成knowledge-base         # 知识提取(术语、关系、卡片)knowledge-graph        # 知识图谱mindmap-generator      # 思维导图study-route            # 学习路径规划ppt-common             # 共享工具库

这个拆包过程由AI助手(Codex CLI)主导完成。git记录显示,单次提交 4d7c5b1 就新增了106个文件、13428行代码。

1.2 重复代码的种子

问题出在拆包的方式上。AI助手在处理每个包时,采用的是"自包含"策略——为每个包独立实现它所需的所有功能,而不是先检查其他包是否已经有了可以复用的组件。

这就像让一个人同时为10个房间装修,他没有先去看看客厅有没有现成的工具,而是为每个房间都买了一套新工具。

以下是git历史中可以清晰看到的重复模式:


2 六类重复代码的发现与修复

2.1 案例一:CJK字符检测——同一段代码出现了6次

发现过程:

在拆包完成后的第二天(7月6日),我们在测试PPT生成时发现中文字符显示为乱码。排查后发现是CJK字符检测函数出了问题——它没有正确识别CJK标点符号。

当我们去修复这个问题时,才发现这个函数在6个不同的包里被独立实现了6次,而且每个版本都不一样:

knowledge-base 的版本 (knowledge_base/extractor.py):

1
2
3
4
def _is_cjk_pair(a: str, b: str) -> bool:    def is_cjk(c: str) -> bool:        return '\u4e00' <= c <= '\u9fff' or c in ',。!?、;:""''()《》'    return is_cjk(a) or is_cjk(b)

rag-indexer 的版本 (rag_indexer/indexer.py):

1
2
3
4
5
def _is_cjk_pair(a: str, b: str) -> bool:    """Check if two adjacent characters are both CJK."""    def is_cjk(c: str) -> bool:        return '\u4e00' <= c <= '\u9fff' or c in ',。!?、;:""''()《》'    return is_cjk(a) or is_cjk(b)

mindmap-generator 的版本 (mindmap_generator/text_extractor.py):

1
2
3
4
def _is_cjk_pair(a: str, b: str) -> bool:    def is_cjk(c: str) -> bool:        return '\u4e00' <= c <= '\u9fff' or c in ',。!?、;:""''()《》'    return is_cjk(a) or is_cjk(b)

ppt-generator 的版本 (ppt_generator/text_analysis.py):

1
2
3
4
5
6
def _is_cjk(text: str) -> bool:    """Detect if text is predominantly CJK characters."""    cjk_count = sum(1 for c in text if '\u4e00' <= c <= '\u9fff'                    or '\u3040' <= c <= '\u30ff')    latin_count = sum(1 for c in text if c.isascii() and c.isalpha())    return cjk_count > latin_count

ppt-generator/llm_service 的版本:

1
2
3
4
5
6
@staticmethoddef _is_cjk(text: str) -> bool:    cjk = sum(1 for c in text if '\u4e00' <= c <= '\u9fff'              or '\u3040' <= c <= '\u30ff')    latin = sum(1 for c in text if c.isascii() and c.isalpha())    return cjk > latin

study-route 的版本 (study_route/llm_analyzer.py):

1
2
3
4
5
def _is_cjk(text: str) -> bool:    """Check if text is predominantly CJK."""    cjk = sum(1 for c in text if "\u4e00" <= c <= "\u9fff")    latin = sum(1 for c in text if c.isascii() and c.isalpha())    return cjk > latin

问题分析:

注意 study-route 的版本——它漏掉了 '\u3040' <= c <= '\u30ff'(日文平假名和片假名)的检测。这意味着如果教材中包含日文内容,study-route 会错误地将其判定为非CJK文本,从而生成英文而非中文的学习路径标题。

更隐蔽的问题是:这些函数虽然逻辑相似,但命名不同_is_cjk_is_cjk_pairis_cjk),参数签名不同(有的接受单字符,有的接受字符串),返回值含义不同(有的检测单个字符,有的检测文本整体)。这使得简单的全局搜索很难发现它们。

修复方案:

我们将所有CJK检测逻辑统一到 ppt-common 包中,提供三个层次的API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# ppt_common/text.pydef is_cjk_char(c: str) -> bool:    """单个字符是否是CJK字符(含标点、日文假名)"""    return (        ("\u4e00" <= c <= "\u9fff")       # CJK统一汉字        or c in _CJK_PUNCT                  # 全角CJK标点        or ("\u3040" <= c <= "\u30ff")      # 日文平假名+片假名    )def is_cjk(text: str) -> bool:    """文本是否以CJK字符为主"""    cjk = sum(1 for c in text              if ("\u4e00" <= c <= "\u9fff") or ("\u3040" <= c <= "\u30ff"))    latin = sum(1 for c in text if c.isascii() and c.isalpha())    return cjk > latindef is_cjk_pair(a: str, b: str) -> bool:    """相邻字符对中是否有CJK字符(用于PDF断行合并)"""    return is_cjk_char(a) or is_cjk_char(b)

然后各包改为导入:

1
from ppt_common.text import is_cjk, is_cjk_pair, is_cjk_char

git提交记录:e6aed61 — “refactor: eliminate code duplication across packages”


2.2 案例二:PDF断行合并——"same logic as rag-indexer"的注释暴露了一切

发现过程:

在 knowledge-base/knowledge_base/extractor.py 的原始代码中,有这样一行注释:

1
2
def _merge_lines(lines: list[str]) -> str:    """Merge PDF lines into proper paragraphs (same logic as rag-indexer)."""

这句注释直接告诉我们:这段代码是从 rag-indexer 复制过来的。AI助手在创建 knowledge-base 包时,发现它需要PDF断行合并功能,于是直接把 rag-indexer 中的实现复制了过来——甚至保留了"和rag-indexer一样的逻辑"这个注释。

以下是三个包中 _merge_lines() 的对比(它们几乎完全相同):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# knowledge-base/extractor.pydef _merge_lines(lines: list[str]) -> str:    """Merge PDF lines into proper paragraphs (same logic as rag-indexer)."""    paragraphs: list[str] = []    current = ""    for line in lines:        line = line.replace('\x08', '').replace('\u200b', '').strip()        if not line or _GARBAGE_RE.match(line):            if current:                paragraphs.append(current)                current = ""            continue        if _HEADER_RE.match(line):            if current:                paragraphs.append(current)            paragraphs.append(line)            current = ""            continue        if not current:            current = line            continue        prev_ends_sentence = current[-1] in '。!?.!?""))》'        if prev_ends_sentence:            paragraphs.append(current)            current = line        else:            if _is_cjk_pair(current[-1], line[0]):                current += line            else:                current += ' ' + line    if current:        paragraphs.append(current)    return '\n'.join(paragraphs)

这段约40行的函数在 knowledge-base、rag-indexer、mindmap-generator 中各出现了一次,总计约120行重复代码。

修复方案:

统一为 ppt_common.text.merge_pdf_lines(),各包直接导入使用。knowledge-base 的修复diff如下:

1
2
3
4
5
6
7
8
9
-from ppt_common.text import merge_pdf_lines+from ppt_common.text import merge_pdf_lines, HEADER_PATTERN, GARBAGE_PATTERN, is_cjk_pair-# Section header patterns-HEADER_PATTERN = re.compile(...)-GARBAGE_PATTERN = re.compile(...)-def _is_cjk_pair(a, b): ...-def _merge_lines(lines): ...-    # 约70行重复代码被删除

2.3 案例三:页面清洗——“修一个bug,制造更多重复”

发现过程:

这个案例特别有意思,因为它展示了修复bug的过程中如何产生新的重复

7月7日上午,我们发现 mindmap-generator 生成的思维导图包含了PDF提取的乱码文本(如 ԍ㑂䃽Ⱊ౜ 这类Unicode垃圾字符)。原因是 mindmap-generator 的 _merge_lines() 函数没有做页面清洗就直接合并文本。

AI助手的修复方案是:在 mindmap-generator 中添加一个 _clean_page_lines() 函数。但这个函数的实现,几乎是 ppt-generator 中 TextCleaner.clean_page() 方法的逐行复制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# mindmap-generator 中新增的 _clean_page_lines()def _clean_page_lines(lines, page_num, book_title="", chapter_title=""):    clean = []    for line in lines:        line = line.strip()        if not line:            continue        if re.match(r'^\d{1,4}$', line):        # 跳过页码            continue        if re.match(r'^[a-z]$', line):           # 跳过脚注标记            continue        if book_title and line == book_title:     # 跳过书名页眉            continue        if chapter_title and line == chapter_title:            continue        if _CJK_CHAPTER_PATTERN.match(line) and len(line) < 20:            continue        # ... 更多清洗逻辑,与 ppt-generator 完全相同

git提交 39c3033 的message写道:“Replace custom _merge_lines with ppt_common.text.merge_pdf_lines for proper CJK handling”——它确实用共享组件替换了 _merge_lines,但同时又新增了一个 _clean_page_lines,把重复从一处转移到了另一处。

这是一个典型的"打地鼠"问题:修了一个重复,又在另一个地方制造了新的重复。

修复方案:

在后续的提交 e6aed61 中,我们将 clean_page() 也提取到了 ppt-common

1
2
3
4
5
6
7
# ppt_common/text.pydef clean_page(text, page_num, book_title="", chapter_title=""):    """清洗PDF页面文本"""    # ... 统一的清洗逻辑# mindmap-generator 改为委托调用from ppt_common.text import clean_page

ppt-generator 中的 TextCleaner 也改为委托模式:

1
2
3
4
5
6
7
class TextCleaner:    def clean_page(self, text, page_num, book_title="", chapter_title=""):        """Delegates to ppt_common.text.clean_page."""        from ppt_common.text import clean_page as _clean_page        cleaned_text = _clean_page(text, page_num, book_title, chapter_title)        clean_lines = cleaned_text.split('\n') if cleaned_text else []        return CleanPage(page_num=page_num, lines=clean_lines, raw_text=text)

2.4 案例四:LLM客户端——4个包各自配置了一遍API

发现过程:

在排查PPT生成质量低下的问题时(见 output_report/PPT_QUALITY_ANALYSIS.md),我们发现了以下错误日志:

1
2
LLM analysis failed: cannot access local variable 'is_cjk'    where it is not associated with a value

这个错误的根因是 ppt-generator/ppt_generator/llm_service.py 中的 LLMContentAnalyzer 类有一个 _is_cjk 静态方法,但在某处代码中,一个局部变量 is_cjk 遮蔽了导入的函数。由于LLM分析失败,PPT生成器退化到了纯文本模式,生成的幻灯片质量大幅下降:

  • 标题变成了 “Section 4”、“Key Concept” 这样的通用标题
  • 内容变成了原始段落的直接复制
  • 没有定义卡片、引用卡片、对比布局等丰富结构

而这个LLM客户端的初始化代码,在4个包中各自实现了一遍:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# ppt-generator/llm_service.pyclass LLMContentAnalyzer:    def __init__(self, api_key="", base_url="", model=""):        import httpx        from openai import OpenAI        self.api_key = api_key or os.getenv("DASHSCOPE_API_KEY", "")        # 代理清除逻辑...        saved_proxy = {}        for key in ['ALL_PROXY', 'HTTPS_PROXY', 'HTTP_PROXY']:            if key in os.environ:                saved_proxy[key] = os.environ.pop(key)        try:            http_client = httpx.Client(base_url=self.base_url, ...)            self.client = OpenAI(api_key=self.api_key, ...)        finally:            os.environ.update(saved_proxy)
1
2
# study-route/llm_analyzer.py — 几乎一模一样的初始化代码# mindmap-generator/llm_enhancer.py — 又是一遍

甚至连JSON响应解析函数也重复了:

1
2
3
4
5
6
7
8
9
10
11
# study-route/llm_analyzer.pydef _parse_llm_response(raw: str) -> dict | None:    """Parse LLM JSON response, stripping code fences."""    text = raw.strip()    if text.startswith("```"):        text = text.split("\n", 1)[1] if "\n" in text else text[3:]    if text.endswith("```"):        text = text[:-3]    if text.startswith("json"):        text = text[4:]    # ...

修复方案:

统一为 ppt_common.llm.LLMClient 和 parse_llm_json()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# ppt_common/llm.pyclass LLMClient:    """懒初始化的OpenAI兼容客户端,含DashScope代理处理"""    def __init__(self, api_key="", base_url="", model="", timeout=60):        self._api_key = api_key or _first_env(_API_KEY_VARS)        self._client = None  # 懒初始化    @property    def client(self):        if self._client is not None:            return self._client        # ... 初始化逻辑(代理清除等)    def chat(self, user_prompt, *, system="", ...):        """发送聊天请求"""    def chat_json(self, user_prompt, *, system="", ...):        """发送请求并解析JSON响应"""

2.5 案例五:正则表达式——“长得一样但变量名不同”

发现过程:

在排查知识提取不准确的问题时,我们对比了各包中使用的正则表达式。发现以下两个正则在多个包中被重复定义,但变量名各不相同:

垃圾行过滤正则:

1
2
3
4
# knowledge-base:  GARBAGE_PATTERN# rag-indexer:     _GARBAGE_RE# mindmap-generator: _GARBAGE_RE# summary-generator: _GARBAGE_RE

章节标题检测正则:

1
2
3
# knowledge-base:  HEADER_PATTERN# rag-indexer:     _HEADER_RE# mindmap-generator: _SECTION_L1 + _SECTION_L2 + _SECTION_L3(拆成了三个)

更微妙的是,这些正则的内容也不完全一致。例如 knowledge-base 的 HEADER_PATTERN

1
2
3
4
5
6
HEADER_PATTERN = re.compile(    r'^(第[一二三四五六七八九十\d]+[章节篇部]|'    r'\d+\.\d+|'                          # ← 只有两级编号    r'[A-Z]\.\s|Chapter\s|Part\s|Section\s)',    re.I)

而 ppt-common 的统一版本:

1
2
3
4
5
6
7
8
9
10
_HEADER_RE = re.compile(    r'^('    r'第[一二三四五六七八九十百千零\d]+[章节篇部]'    r'|\d+\.\d+(?:\.\d+)?\s+\S'          # ← 支持三级编号    r'|[A-Z]\.\s'    r'|Chapter\s|Part\s|Section\s'    r'|([一二三四五六七八九十]+)'         # ← 额外的中文括号编号    r')',    re.I,)

统一版本比任何单个包的版本都更加完善——它支持三级编号(如 1.2.3 概念名)和中文括号编号(如 (一)传播的定义)。但由于这些正则分散在各包中,每个版本都只覆盖了部分场景。


2.6 案例六:main.py 中的"临时"清洗函数

发现过程:

在 ppt-generator/main.py 中,我们发现了一个约30行的 clean_text() 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def clean_text(text: str) -> str:    """Remove garbage characters and filter meaningless content."""    cleaned = re.sub(r'[^\x20-\x7E\u4e00-\u9fff\u3000-\u303f\uff00-\uffef\n\r\t]',                     '', text)    lines = cleaned.split('\n')    filtered_lines = []    for line in lines:        line = line.strip()        if not line:            continue        if len(line) < 3:            continue        non_alnum = sum(1 for c in line if not c.isalnum() and not c.isspace())        if len(line) > 5 and non_alnum / len(line) > 0.7:            continue        filtered_lines.append(line)    return '\n'.join(filtered_lines)

这个函数是AI助手在实现 main.py 的CLI入口时"随手"写的——它需要一个简单的文本清洗功能,于是直接内联了一个实现,而没有去检查项目中是否已经有 TextCleaner.clean_page() 可以使用。

同样的情况还出现在图像过滤的硬编码阈值中:

1
2
3
# main.py 中的硬编码if d['width'] < 100 or d['height'] < 100 or area < 10000:    # 过滤小图

而 pdf-image-extractor 包中已经有 ImageFilterConfig 类定义了这些阈值。

修复方案(commit 1986ecc):

1
2
3
4
5
6
7
8
9
10
11
12
-        def clean_text(text: str) -> str:-            """Remove garbage characters..."""-            # 30行代码...+        cleaner = TextCleaner()+        for page_num, page in enumerate(doc):+            clean_page = cleaner.clean_page(page.get_text(), page_num + 1)+            text_content.append("\n".join(clean_page.lines))-                if d['width'] < 100 or d['height'] < 100 or area < 10000:+                from pdf_image_extractor import ImageFilterConfig+                img_config = ImageFilterConfig()+                if d['width'] < img_config.min_width or d['height'] < img_config.min_height:

这次修复删除了154行重复代码。


3 重复代码导致的真实Bug

重复代码不仅仅是"代码不整洁"的问题——它直接导致了多个影响功能的Bug。

3.1 Bug 1:PPT质量断崖式下降

现象: PPT生成的幻灯片质量突然下降,标题变成 “Section 4”、“Key Concept” 这样的通用文本,内容变成原始段落的直接复制。

根因:ppt-generator/ppt_generator/llm_service.py 中的 LLMContentAnalyzer 类有一个 _is_cjk 静态方法。在某次重构中,一个局部变量 is_cjk 遮蔽了该方法,导致:

1
2
LLM analysis failed: cannot access local variable 'is_cjk'    where it is not associated with a value

LLM分析失败后,生成器退化到纯文本模式,失去了:

  • 智能标题提取
  • 关键点提炼
  • 定义卡片和引用卡片
  • 对比布局
  • 内容摘要

教训: 如果 _is_cjk 不是在各包中重复实现,而是统一从 ppt-common 导入,这个变量遮蔽问题就不会发生。

3.2 Bug 2:思维导图包含乱码

现象: mindmap-generator 生成的思维导图节点标题包含 ԍ㑂䃽Ⱊ౜ 等Unicode垃圾字符。

根因: mindmap-generator 的 _merge_lines() 函数没有做页面清洗就直接合并文本。PDF中的图表区域会产生大量非文本Unicode字符,这些字符被当成正文合并到了段落中。

修复过程:

  1. 第一次修复(39c3033):在 mindmap-generator 中新增 _clean_page_lines() —— 但这是从 ppt-generator 复制过来的,制造了新的重复
  2. 第二次修复(e6aed61):将 clean_page() 提取到 ppt-common,mindmap-generator 改为导入

3.3 Bug 3:章节内容错位

现象: PPT中"小结"幻灯片显示了其他章节的内容。

根因:ppt-generator/main.py 中使用 str.find() 进行子串匹配来定位章节内容:

1
2
start_idx = full_text.find(section_title)end_idx = full_text.find(next_title, start_idx + len(section_title))

当教材中有多个同名章节(如多个"小结")时,find() 总是返回第一个匹配,导致后面的章节内容被错误地分配到前面的章节。

修复(8f18ba4): 用前向游标(forward cursor)匹配替代 find()

1
2
3
4
5
6
7
8
cursor = 0for i, (section_title, _level) in enumerate(sections):    start = cursor    while cursor < len(paragraphs) and section_title not in paragraphs[cursor]:        cursor += 1    if cursor < len(paragraphs):        start = cursor + 1        cursor = start

3.4 Bug 4:study-route 的 Python 3.14 作用域问题

现象: study-route 在 Python 3.14 下运行时报错:

1
cannot access local variable 'is_cjk' where it is not associated with a value

根因:study_route/planner.py 中在条件块内部导入了 is_cjk

1
2
3
if terms:    from ppt_common.text import is_cjk    title = f"{term_name} 学习路线" if is_cjk(term_name) else ...

Python 3.14 的作用域规则变化导致这个延迟导入在某些路径下无法正确解析。


4 防护体系的建立

在经历了上述排查和修复之后,我们逐步建立了一个四层的防护体系。

4.1 第一层:文档约束——让AI"知道"有什么可以复用

我们在项目根目录创建了 AGENTS.md,其中包含一个共享组件查找表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 文本处理from ppt_common.text import (    clean_page,           # PDF页面清洗    merge_pdf_lines,      # PDF断行合并(CJK感知)    is_section_header,    # 章节标题检测    is_cjk,              # CJK文本检测    is_cjk_pair,         # CJK字符对检测    GARBAGE_PATTERN,     # 垃圾行过滤正则    HEADER_PATTERN,      # 章节标题检测正则)# LLM集成from ppt_common.llm import (    LLMClient,           # 统一OpenAI兼容客户端    parse_llm_json,      # LLM JSON响应解析)

以及一个四步决策清单

1
2
3
4
Step 1: 该功能是否在查找表中? YES → 导入  NO → Step 2Step 2: 这是通用的文本/PDF/LLM处理? YES → 加到ppt-common  NO → Step 3Step 3: 这是某个包的领域特有逻辑? YES → 在该包实现  NO → 应该在ppt-commonStep 4: 我是否即将从另一个包复制代码? YES → 停止!改为导入

4.2 第二层:行为规则——让AI"应该"去复用

我们为AI助手定义了行为规则,核心是一个门禁函数(Gate Function):

1
2
3
4
5
6
7
8
9
在实现任何新函数之前,必须执行:1. 识别:这个函数的用途是什么?2. 搜索:查找表中是否已有实现?3. 验证:运行重复检测器4. 决策:   - 找到了 → 导入   - 接近但不完全匹配 → 组合使用   - 确实是新的 → 加到共享包,然后导入5. 然后才能写代码

以及一组红旗标志——当AI助手的编码行为触发以下标志时,应立即停止:

  • 正在写 def is_cjk( 而没有先检查查找表
  • 在非共享包中创建 def clean_page(
  • 使用 '\u4e00' <= c <= '\u9fff' 实现CJK检测
  • 认为"这只是一个小辅助函数,直接写就好"

4.3 第三层:自动化检测——让重复无处藏身

我们编写了 check_duplication.py,一个基于正则表达式的重复检测器:

1
2
3
4
5
6
7
8
9
FORBIDDEN_PATTERNS = [    (r'def\s+_?is_cjk_pair\s*\(',    'use ppt_common.text.is_cjk_pair'),    (r'def\s+_?is_cjk_char\s*\(',    'use ppt_common.text.is_cjk_char'),    (r'def\s+_?is_cjk\s*\(',         'use ppt_common.text.is_cjk'),    (r'def\s+_?merge_pdf_lines\s*\(', 'use ppt_common.text.merge_pdf_lines'),    (r'def\s+_?clean_page\s*\(',     'use ppt_common.text.clean_page'),    (r'^_GARBAGE_RE\s*=\s*re\.compile', 'use ppt_common.text.GARBAGE_PATTERN'),    (r'["\']\u4e00["\'].*<=.*["\']\u9fff["\']', 'use ppt_common.text.is_cjk_char'),]

检测器还实现了误报过滤——如果文件中包含 "Delegates to ppt_common" 注释或正确的导入语句,则跳过相关的匹配:

1
2
3
4
# 检查文件是否包含委托模式has_delegation = "Delegates to ppt_common" in sourceif has_delegation and "clean_page" in description:    continue  # 合法的委托代码

实际运行效果:

1
2
3
4
5
6
7
8
9
$ python3 check_duplication.py🔍 Scanning for code duplication...📦 knowledge-base  ❌ knowledge-base/knowledge_base/terms.py     Line 166: CJK range check (use ppt_common.text.is_cjk_char)       if not all('\u4e00' <= c <= '\u9fff' for c in ngram):❌ Found 1 duplication(s)

即使在完成大规模重构后,检测器仍然发现了1处遗漏——knowledge-base/terms.py 中的CJK范围检查。这种细粒度的检测是人工代码审查很难做到的。

4.4 第四层:架构设计——从根源上减少重复

我们定义了共享包-领域包分离的架构:

1
2
3
4
5
6
7
8
9
10
ppt-common (共享工具包)  text.py  → CJK检测、PDF行合并、页面清洗、正则模式  llm.py   → 统一LLM客户端、JSON解析领域包 (各自只包含领域特有逻辑)  pdf-chapter-splitter → TOC检测、章节边界提取  pdf-image-extractor  → 图像过滤、图表提取  ppt-generator        → PowerPoint构建、幻灯片布局  rag-indexer          → 语义分块、FAISS索引  ...

核心原则:

  1. 单一实现原则: 每个功能在整个代码库中只有一个实现
  2. 领域纯净原则: 每个领域包只包含其领域特有的逻辑
  3. 共享下沉原则: 当多个包需要相同功能时,该功能应下沉到共享包

5 修复效果

5.1 量化指标

指标
修复前
修复后
跨包重复率(CPDR)
67%(10个包中约7个有重复)
~0%
重复代码行数
~300行
~5行(1处遗漏)
共享组件导入点
0
23
测试通过率
未测试
10/10包全部通过
总测试数
0
199个测试全部通过

5.2 git提交时间线

整个排查和修复过程发生在2026年7月7日一天内:

1
2
3
4
5
6
15:33  8f18ba4  fix: 修复章节内容错位 + 消除重复PDF解析15:49  1986ecc  refactor: 去重ppt-generator的文本/图像处理 (-154行)16:11  6d615fd  feat: 添加全包集成测试 (10包, 199个测试)16:29  39c3033  fix: 修复思维导图乱码 (但引入了新的重复!)16:57  e6aed61  refactor: 消除跨包重复 (-300行, 统一ppt-common)17:11  9fdb611  feat: 实现三层防护体系 (AGENTS.md + 检测器 + 行为规则)

5.3 一个有意思的发现

在修复过程中,我们发现AI助手有一个有趣的"自我纠正"模式:

  1. 在 commit 39c3033 中,AI助手修复了思维导图乱码问题,但方式是复制 ppt-generator 的 clean_page 逻辑到 mindmap-generator
  2. 在 commit e6aed61 中,同一个AI助手(在同一天的后续会话中)又将这个复制的代码提取到了 ppt-common

这说明AI助手并不是"不知道"代码复用的原则——它只是在单个任务的上下文中倾向于快速解决问题,而不是花时间去寻找已有的共享组件。当我们在后续会话中明确指出"请先检查 ppt-common"时,它就能够正确地使用共享组件。


6 经验总结

6.1 AI编程助手产生重复代码的根因

根因
表现
对策
局部视野
不知道其他包已有相同功能
查找表 + AGENTS.md
自包含倾向
倾向于为每个任务独立实现
门禁函数 + 行为规则
缺乏项目记忆
每次会话都从零开始
持久化文档 + 检测器
"小辅助函数"陷阱
认为小函数不值得复用
红旗标志 + 合理化防御
修bug时的紧迫感
快速修复优先于正确修复
自动化检测兜底

6.2 关键教训

教训一:重复代码的危害不仅仅是"不整洁"

在我们的案例中,重复代码直接导致了:

  • PPT质量断崖式下降(变量遮蔽bug)
  • 思维导图包含乱码(清洗逻辑不一致)
  • 章节内容错位(字符串匹配在重复标题上失败)

教训二:修复bug时要警惕"转移重复"

commit 39c3033 的教训:修复一个重复问题时,不要把重复代码搬到另一个地方。应该问自己:“这个功能在项目中是否已经有实现?”

教训三:自动化检测是最后一道防线

即使有文档和行为规则,AI助手仍然可能产生重复。check_duplication.py 在大规模重构后仍然发现了1处遗漏,证明了自动化检测的价值。

教训四:共享组件的API设计要"好用"

ppt-common 的成功在于它提供了多层次的API:

  • 高层:clean_page() — 一行代码完成页面清洗
  • 中层:merge_pdf_lines() — 一行代码完成断行合并
  • 低层:is_cjk_char()GARBAGE_PATTERN — 细粒度组件供组合使用

如果共享组件只提供了一个"大而全"的API,各包可能会因为"不完全匹配需求"而选择自己实现。


7 附录

7.1 各包对共享组件的导入使用情况

1
2
3
4
5
6
7
mindmap-generator  → merge_pdf_lines, clean_page, is_cjk, is_section_header, GARBAGE_PATTERNstudy-route        → is_cjk, LLMClient, parse_llm_jsonppt-generator      → is_cjk, is_cjk_char, clean_page, merge_pdf_lines, LLMClientsummary-generator  → is_section_header, clean_page, GARBAGE_PATTERNrag-indexer        → merge_pdf_lines, clean_page, HEADER_PATTERN, GARBAGE_PATTERNknowledge-base     → merge_pdf_lines, clean_page, HEADER_PATTERN, GARBAGE_PATTERN, is_cjk_pair总计:23个导入点

7.2 自动化检测器的完整禁止模式列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
FORBIDDEN_PATTERNS = [    # 函数定义重复    (r'def\s+_?is_cjk_pair\s*\(',    'is_cjk_pair'),    (r'def\s+_?is_cjk_char\s*\(',    'is_cjk_char'),    (r'def\s+_?is_cjk\s*\(',         'is_cjk'),    (r'def\s+_?merge_pdf_lines\s*\(', 'merge_pdf_lines'),    (r'def\s+_?clean_page\s*\(',     'clean_page'),    (r'def\s+_?is_section_header\s*\(', 'is_section_header'),    # 正则表达式重复    (r'^_GARBAGE_RE\s*=\s*re\.compile', 'GARBAGE_RE'),    (r'^GARBAGE_PATTERN\s*=\s*re\.compile.*u4e00', 'GARBAGE_PATTERN'),    (r'^HEADER_PATTERN\s*=\s*re\.compile.*章节', 'HEADER_PATTERN'),    (r'^_HEADER_RE\s*=\s*re\.compile', 'HEADER_RE'),    # CJK范围检查重复    (r'["\']\u4e00["\'].*<=.*["\']\u9fff["\']', 'CJK range check'),]

7.3 集成测试结果(修复后)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Package                        Status     Time       Details--------------------------------------------------------------------------------✅ ppt-common                   PASS       0.10s      merged_paragraphs: 5✅ pdf-chapter-splitter         PASS       0.64s      chapters: 1, toc_pages: 0✅ pdf-image-extractor          PASS       0.75s      images_found: 29, kept: 1✅ ppt-generator                PASS       36.45s     paragraphs: 242, sections: 25✅ rag-indexer                  PASS       6.55s      indexed: True✅ summary-generator            PASS       0.24s      summaries: 9, key_points: 45✅ knowledge-base               PASS       0.32s      terms: 19, relationships: 27✅ knowledge-graph              PASS       0.09s      nodes: 2, edges: 1✅ mindmap-generator            PASS       0.23s      root_nodes: 7✅ study-route                  PASS       0.00s      steps: 0================================================================================Total: 10 passed, 0 failed in 45.37s

本文涉及的10个通用Python包已独立开源,代码可在 https://github.com/back1992/ppt-bot-v2-packages 获取。


邮箱:linmk@tup.tsinghua.edu.cn

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