乐于分享
好东西不私藏

重构了自己5年前写的截图插件

重构了自己5年前写的截图插件

前言

时隔5年,断断续续花了亿些时间完成了js-screen-shot项目的重构,这个插件最早的目标很简单:在 Web 端实现一个类似 QQ / 微信截图的功能。用户可以框选区域,然后在画布里画矩形、圆形、箭头、画笔、马赛克、文字,最后保存截图内容。

随着功能越来越多,再加上那会儿我的技术水平还不够好,架构设计的比较差,代码也不可避免地变复杂了。尤其是后面加入了 WebRTC 截屏、自定义工具栏、图片模式、Electron 适配等能力后,入口文件越堆越大。

插件从发布到现在,NPM的周下载量[1]保持在1000+,同时有很多人反馈说画布里的内容无法二次编辑,于是就有了本次重构计划:让画布内的元素真正变成可管理、可选中、可移动、可重绘的对象。

本文就跟大家分享下我这次重构截图插件的整体思路、用到的技术点,以及过程中遇到的一些坑,欢迎各位感兴趣的开发者阅读本文。

为什么要重构

我们先来看下重构前的结构。

早期版本的核心目录大致如下:

src
├── main.ts
└── lib
    ├── main-entrance
    │   ├── CreateDom.ts
    │   ├── InitData.ts
    │   └── PlugInParameters.ts
    ├── split-methods
    ├── common-methods
    └── type
        └── ComponentType.ts

这个结构在功能少的时候是可以接受的,main.ts 负责串联整个截图流程,split-methods 存放绘制逻辑,common-methods 存放一些公共方法,InitData 管理插件运行时数据。

但是当功能继续增加后,它逐渐暴露出了几个问题。

main.ts 变得太重

未重构前,在 pre_release 分支中,main.ts 已经有 1500 多行代码。

里面同时处理了:

  • 插件初始化
  • DOM 创建与获取
  • 截图源加载
  • 鼠标按下、移动、抬起
  • 裁剪框绘制与拖拽
  • 工具栏绘制
  • 文字输入
  • 撤销
  • 保存与确认
  • WebRTC 截屏
  • 自定义工具栏

这会导致一个很直接的问题:任何功能都能改到入口文件。

比如:我只是想优化一下鼠标移动时的命中判断,都需要在 main.ts 里翻很久,因为它既包含画布状态,也包含工具栏状态,还包含 DOM 结构。

状态管理过于集中

旧版本里大量状态集中在 InitData.ts 中,通过模块级变量保存。

let dragging = false;
let toolClickStatus = false;
let selectedColor = "#F53340";
let toolName = "";
let penSize = 2;
let history: Array<Record<stringany>> = [];
let cutOutBoxPosition = {
  startX: 0,
  startY: 0,
  width: 0,
  height: 0
};

这种方式写起来很快,但后期维护会比较痛苦。

因为这些状态虽然都跟截图有关,但它们的职责并不一样,零零散散的包含了:

  • 裁剪框状态
  • 工具栏状态
  • 画布绘制状态
  • DOM 引用
  • 用户传入的配置

当它们全部放在一起时,代码很难看出一个状态到底属于哪个模块,也很难判断修改它会影响哪些地方。

画布内容不可二次编辑

旧版本的绘制逻辑是:“直接画到 canvas 上”。比如用户画了一个矩形,代码会立刻在 canvas 上画线,然后通过 ImageData 保存历史记录,这种方式做撤销很容易,但是要做“二次编辑”就是个大工程了。

小科普:因为 canvas 本身是位图,它并不知道上面哪个区域是矩形、哪个区域是箭头、哪个区域是文字。你一旦画上去,它就变成了像素。所以要支持选中、移动、缩放、删除,就必须额外维护一份“画布元素数据”。

重构后的目录结构

这次重构后,核心目录变成了下面这样:

从大入口到分层模块
src
├── main.ts
├── store
│   ├── CropBoxStore.ts
│   ├── DrawingDataStore.ts
│   ├── ScreenShotCanvasStore.ts
│   ├── TextInputStore.ts
│   ├── ToolBarStore.ts
│   ├── UserParamStore.ts
│   └── dom
├── lib
│   ├── application
│   ├── constants
│   ├── features
│   ├── shared
│   ├── type
│   └── utils
└── tests

入口文件 main.ts 从原来的 1500 多行降到了 200 多行,它现在更像是一个调度器,只负责把各个模块串起来。

exportdefaultclass ScreenShot {
constructor(options: ScreenShotOptions) {
const normalizedOptions = normalizeScreenShotOptions(options);

    setPlugInParameters(normalizedOptions);
new CreateDom(normalizedOptions);
    screenDomStore.initWebRtcDom();

    setOptionalParameter(normalizedOptions);
    screenDomStore.hydrateDomRefs();
    toolPanelDomStore.hydrateDomRefs();

this.load(normalizedOptions);
  }
}

这样调整后,入口文件不再关心具体怎么画矩形、怎么判断箭头命中、怎么移动文字,它只负责组织流程。

我的重构思路

这次重构我主要按下面几个方向推进。

重构后的分层关系

按业务流程拆 application 层

application 目录负责插件运行流程。

src/lib/application
├── core
│   ├── ScreenFlowLoader.ts
│   ├── ScreenFrameDrawer.ts
│   ├── ScreenInitializer.ts
│   ├── ScreenShotModeExecutor.ts
│   ├── ScreenShotModeResolver.ts
│   ├── ScreenSourceManager.ts
│   └── UiCoordinator.ts
├── mouse
│   ├── CanvasMouseClickHandlers.ts
│   ├── CanvasMouseDownHandlers.ts
│   ├── CanvasMouseMoveHandlers.ts
│   ├── ToolbarDrawingHandler.ts
│   └── CustomToolEventBridge.ts
└── CreateDom.ts

这一层解决的是“截图流程怎么跑起来”的问题。

比如截图源加载,旧版本会在入口文件里判断 enableWebRtcimgSrcscreenFlow 等参数。现在我把这块整理成了截图模式解析和执行流程。

const plan = resolveScreenShotPlan();

executeLoadPlan(
  plan,
  mouseEvents,
  context,
  triggerCallback,
  cancelCallback,
() =>this.screenShotImageController,
canvas => {
this.screenShotImageController = canvas;
  }
);

这样后续如果要继续增加新的截图来源,不需要继续往 main.ts 里塞条件判断,而是扩展模式解析和执行器。

按功能拆 features 层

features 目录负责具体能力,比如绘制、配置处理、事件处理、历史记录。

src/lib/features/canvas
├── calculations
├── config
├── drawing
├── events
├── state
└── utils

这里面比较特殊的是 drawing 目录,它只处理 canvas 绘制。

drawing
├── DrawArrow.ts
├── DrawCircle.ts
├── DrawCutOutBox.ts
├── DrawImgToCanvas.ts
├── DrawLineArrow.ts
├── DrawMasking.ts
├── DrawMosaic.ts
├── DrawPencil.ts
├── DrawRectangle.ts
└── DrawText.ts

原来这些文件放在 split-methods 下面,名字虽然是拆开了,但从目录上看不出它们属于哪个业务模块。现在放到 features/canvas/drawing 后,职责会更明确:这些文件就是 canvas 绘制能力。

把可复用能力放到 shared 层

shared 目录放的是跨流程复用的能力。

比如:

src/lib/shared
├── canvas
│   ├── CanvasElementHitTest.ts
│   ├── CanvasElementSelection.ts
│   ├── CanvasElementTransform.ts
│   ├── CanvasElementToolbarSync.ts
│   ├── CustomCanvasElementUtils.ts
│   └── TextEditingController.ts
├── dom
├── platform
├── text
└── ui

这里最核心的是 shared/canvas

因为这次大版本更新的重点是“画布内元素可二次编辑”,选中、命中检测、拖拽、缩放、重绘这些逻辑并不属于某一个具体工具,它们是所有画布元素都要复用的能力。

使用 Store 拆分运行时状态

为了尽可能的轻量化,这次我选择引入 mobx 来做全局的状态管理。

以前 InitData 里面放了所有状态,现在拆成了多个 store:

src/store
├── CropBoxStore.ts
├── DrawingDataStore.ts
├── ScreenShotCanvasStore.ts
├── TextInputStore.ts
├── ToolBarStore.ts
├── UserParamStore.ts
└── dom
    ├── ScreenDomStore.ts
    └── ToolPanelDomStore.ts

这样拆完后,每个 store 的职责就比较清楚了。

  • CropBoxStore 负责裁剪框位置、拖拽、缩放等状态
  • ToolBarStore 负责当前工具、画笔大小、颜色、工具栏位置
  • DrawingDataStore 负责画布元素、历史记录、当前选中元素
  • UserParamStore 负责用户传入的配置
  • ScreenDomStore 负责截图相关 DOM 引用
  • ToolPanelDomStore 负责工具面板相关 DOM 引用

其中 DrawingDataStore 是这次改动的核心。

canvasElements: [],
activeElementId: null,
rectOperateIndex: null,
editingTextElementId: null,
pendingEditingTextElement: null

这些状态让画布上的内容从“像素”变成了“元素对象”。

画布元素二次编辑是怎么实现的

canvas 的难点在于:它不会帮你保存图形对象。

数据驱动画布重绘

当你在 canvas 上画了一个矩形,它只知道某些像素变成了红色,并不知道这里原来是一个矩形。

所以,这次我为每个绘制内容都维护了一份快照。

exportinterface BaseCanvasElement {
  id: string;
  x: number;
  y: number;
  drawNode?: boolean;
  dotRadius?: number;
}

exportinterface SquareElement extends BaseCanvasElement {
  width: number;
  height: number;
  borderWidth: number;
  color: string;
}

exportinterface TextElement extends BaseCanvasElement {
  width: number;
  height: number;
  color: string;
  fontSize: number;
  text: string;
  borderWidth: number;
}

画布中的元素会统一存到 canvasElements 中。

exporttype CanvasElement =
  | SquareElement
  | RoundElement
  | LineArrowElement
  | ArrowElement
  | PencilElement
  | MosaicElement
  | TextElement
  | CustomCanvasElement;

当用户绘制时,流程变成了这样:

  • 鼠标按下时创建当前元素 ID
  • 鼠标移动时绘制临时图形
  • 同步更新当前元素快照
  • 鼠标抬起时保存历史记录
  • 后续重绘时根据 canvasElements 重新画一遍

这样做以后,移动和缩放就不是去“移动像素”,而是修改元素数据,然后清空画布重新绘制。

clearCanvasSurface();
drawingDataStore.redrawCanvasElements();

这也是 canvas 编辑器比较常见的实现方式:数据驱动画布重绘

元素选中与命中检测

支持二次编辑后,第一个要解决的问题就是:鼠标点下去时,怎么知道点中了哪个元素?

元素二次编辑的交互链路

由于不同元素的命中规则是不一样的,矩形可以判断鼠标是否在边框附近,圆形要判断是否在椭圆边缘,箭头要判断鼠标是否在箭头线段附近,文字和画笔更适合用包围盒处理。

因此我把这块放到了 DrawingDataStore 和 CanvasElementSelection 中统一处理。

drawingDataStore.checkMouseInElement(x, y, elementId => {
if (elementId) {
    selectCanvasElementBorder(elementId, dotRadius);
  }
});

选中元素后,会记录当前选中的元素 ID。

drawingDataStore.updateActiveElementId(canvasElement.id);

并且给当前元素打上 drawNode 标识,重绘时根据这个标识画出选中边框和操作节点。

这块实现后,矩形、圆形、箭头、画笔、文字、自定义元素都可以进入同一套选中逻辑。

元素移动与缩放

移动元素的核心逻辑放在 CanvasElementTransform.ts

它并不直接操作 DOM,也不直接关心鼠标事件,只接收当前鼠标位置、拖拽偏移量和目标元素 ID。

exportconst moveCanvasElementOnCanvas = (
  mouseX: number,
  mouseY: number,
  dragOffset: { x: number; y: number },
  elementId: string | null
) => {
const targetElement = resolveCanvasElement(elementId);
if (targetElement == nullreturn;

  drawingDataStore.updateDrawStatus(true);
  clearCanvasSurface();

// 根据元素类型更新位置
// ...

  drawingDataStore.redrawCanvasElements();
};

矩形和文字这类元素比较简单,只需要更新 x / y

箭头就要麻烦一些,因为它除了包围盒,还有起点、终点、箭头顶点等信息。

画笔和马赛克也不能只更新包围盒,还要把内部的点位一起平移。

points: originalPoints.map(point => ({
  x: point.x + deltaX,
  y: point.y + deltaY
}))

这也是这次重构里比较容易踩坑的地方:不同元素看起来都叫移动,但内部数据结构并不一样。

如果强行用一套 x / y / width / height 处理所有元素,箭头、画笔、马赛克很快就会出问题。

自定义工具如何接入编辑逻辑

旧版本已经支持用户自定义工具栏,但那时的自定义工具是“把 canvas 暴露出去,让用户自己画”。

自定义元素接入编辑体系

这种方式虽然灵活,但它画出来的内容无法进入插件内部的编辑系统。

这次重构后,我增加了 customElementAdapters 和 customElementApi

自定义元素需要满足一个基础结构:

exportinterface CustomCanvasElement extends BaseCanvasElement {
  customType: "custom";
  width: number;
  height: number;
  toolId?: number;
  toolName?: string;
  payload?: unknown;
}

插件内部会给自定义工具回调传入一组 API:

exporttype CustomCanvasElementApi = {
  addElement: (input: CustomCanvasElementInput) => CanvasElementSnapshot | null;
  updateElement: (element: CanvasElement) =>void;
  removeElement: (id: string) =>void;
  selectElement: (id: string) =>boolean;
  getElement: (id: string) => CanvasElementSnapshot | undefined;
  getActiveElement: () => CanvasElementSnapshot | undefined;
  redraw: () =>void;
};

用户自定义工具在绘制完成后,不再只是把内容画到 canvas 上,而是可以通过 addElement 把元素注册进插件内部。

同时,用户可以通过 adapter 告诉插件这个元素如何绘制、如何命中、如何移动、如何缩放。

exporttype CustomCanvasElementAdapter = {
  draw: (
    element: CustomCanvasElement,
    context: CanvasRenderingContext2D
  ) => void;
  hitTest?: (
    element: CustomCanvasElement,
    point: { x: number; y: number }
  ) => boolean;
  move?: (
    element: CustomCanvasElement,
    delta: { x: number; y: number },
    bounds: CropBoxBounds
  ) => CustomCanvasElement | void;
  resize?: (
    element: CustomCanvasElement,
    handleIndex: number,
    point: { x: number; y: number },
    bounds: CropBoxBounds
  ) => CustomCanvasElement | void;
};

这样做之后,自定义元素就不再是插件体系外的“自由绘制内容”,而是可以进入统一的选中、移动、重绘、删除逻辑。

比如五角星这种自定义图形,就可以通过 draw 负责画星星,通过 hitTest 判断鼠标是否点中,通过 move 控制移动边界。

有关此处的使用,详细文档请移步:工具栏模块化扩展[2]

优化截图源配置定义

这次还顺手整理了截图源的配置传入字段,以前配置项比较分散,比如:

  • enableWebRtc
  • screenFlow
  • imgSrc
  • wrcWindowMode

这些参数都是在描述截图来源和渲染方式,但分散在多个字段里,后面继续扩展会越来越难理解。

因此现在增加了一个新的 capture 配置。

exporttype ScreenShotCaptureOptions = {
  source?: "display-media" | "injected-stream" | "dom" | "image";
  render?: "browser-frame" | "window-frame";
  stream?: MediaStream;
  imageSrc?: string;
};

处于兼容性考虑,插件内部会先把新旧参数统一归一化。

const normalizedOptions = normalizeScreenShotOptions(options);

如果用户继续使用旧参数,插件仍然兼容,只是内部会统一转成新的截图模式。

这块的好处是后面如果继续扩展截图来源,比如增加新的图片输入方式,或者增加某种自定义渲染模式,不需要再让入口文件继续膨胀。

这次遇到的几个坑

整个重构过程中自然遇到了一些问题,这里简单跟大家分享下。

选中态必须在正确时机清理

做元素编辑时,一开始很容易出现一个 bug:用户选中了一个旧元素,然后开始画新元素,旧元素的选中边框还在。

这个问题本质上是状态没有归属清楚。

现在的处理方式是:开始绘制新元素前,需要先清理当前选中元素的状态。

drawingDataStore.updateActiveElementId(null);
drawingDataStore.updateRectOperateIndex(null);
drawingDataStore.resetCanvasElementNodeState();

否则用户看到的就是“我明明在画新矩形,但旧元素还处于选中状态”。

这类问题不是 canvas 绘制问题,而是交互状态问题。

拖拽已有元素后,要切换当前选中元素

另一个问题是:如果当前已经选中了 A 元素,然后用户直接拖拽 B 元素,拖拽结束后应该选中 B。

这个逻辑看起来很自然,但实现时要注意鼠标按下、移动、抬起之间的状态传递。

现在我用一个 pointerSession 保存本次指针操作的信息。

const pointerSession = {
  prevElementId: null,
  dragOffset: { x: 0, y: 0 },
  transformingExisting: false
};

这样在 mousedown 时确认命中的元素,在 mousemove 时移动这个元素,在 mouseup 时完成本次编辑状态同步。

文字编辑不能只当普通矩形处理

设计文字元素结构对象的时候,它看起来也有 x / y / width / height,于是我就想把它纳入普通矩形去,实际做的时候,发现它还涉及输入框、文本内容、字号、二次编辑。就出现了两个问题:

  • 空文本元素被当成宽高为 0 的无效元素删掉
  • 二次编辑时文本输入框和画布文本状态不同步

最后只好将文字相关逻辑拆到 shared/text 和 TextEditingController 中,单独处理文本输入、提交、点击编辑等流程。

canvas 历史记录不能只保存 ImageData

旧版本的撤销主要依赖 ImageData

但是做二次编辑后,只保存 ImageData 不够了。

因为画布元素还存在于 canvasElements 中,如果撤销时只恢复像素,不恢复元素数据,用户下一次选中、移动、删除时就会出现数据和画面不一致的问题。

所以现在历史记录需要同时保存画布像素和元素快照。

{
  data: imageData,
  canvasElements: [...]
}

注意:这点非常重要。只要你的 canvas 是“可编辑画布”,就不能只把它当成图片处理。

构建和开发体验优化

除了业务代码,这次也顺手调整了构建体验。

包管理器从原来的 yarn 切到了 pnpm,并在 package.json 中固定了版本。

{
"packageManager""pnpm@10.32.1"
}

Rollup 构建也做了一些优化,比如显式配置 babelHelpers,减少无意义警告;开发构建时输出更清晰的进度信息;启动时打印项目名,让终端输出更容易识别。

babel({
  babelHelpers: "bundled"
})

这些不是核心功能,但对维护项目很重要。

我做事情喜欢做到极致,在开发阶段我会消除所有的警告,让后续的调试、构建、发布都尽量顺手。

项目地址

  • js-screen-shot官网[3]
  • GitHub仓库地址[4]

这次重构最大的变化,是把截图插件从“过程式地操作 canvas”调整成了“用数据描述画布元素,再根据数据重绘 canvas”。

简单来说,就是从:

用户操作 -> 直接画到 canvas -> 保存像素历史

变成:

用户操作 -> 更新元素数据 -> 清空画布 -> 根据元素数据重绘 -> 保存像素和元素快照

这个变化带来的收益非常明显:

  • 入口文件变轻
  • 运行时状态更清晰
  • 内置元素完美支持二次编辑
  • 自定义元素可以接入编辑体系
  • 截图源配置更统一
  • 后续功能扩展有了更明确的位置

当然,代价也有。

二次编辑会让 canvas 逻辑复杂很多,尤其是不同元素的命中检测、移动、缩放、历史记录同步,都需要单独处理。

但从长期维护角度看,这是值得的。

写在最后

至此,文章就分享完毕了。

我是神奇的程序员,一位前端开发工程师。

如果你对我感兴趣,请移步我的个人网站[5],进一步了解。

  • 文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注😊
参考资料
[1] 

NPM的周下载量: https://www.npmjs.com/package/js-web-screen-shot

[2] 

工具栏模块化扩展: https://www.kaisir.cn/js-screen-shot/docs/config/#5bel5yw35qcp5qih5z2x5yyw5omp5bgv

[3] 

js-screen-shot官网: https://www.kaisir.cn/js-screen-shot

[4] 

GitHub仓库地址: https://github.com/likaia

[5] 

个人网站: https://www.kaisir.cn/

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-05-15 07:37:15 HTTP/1.1 GET : https://www.yeyulingfeng.com/a/627268.html
  2. 运行时间 : 0.233594s [ 吞吐率:4.28req/s ] 内存消耗:4,736.29kb 文件加载:145
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=937c122e0cd4ec55232963811395177d
  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.001627s ] mysql:host=127.0.0.1;port=3306;dbname=wenku;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.001394s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.012748s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000765s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.001613s ]
  6. SELECT * FROM `set` [ RunTime:0.002560s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.001676s ]
  8. SELECT * FROM `article` WHERE `id` = 627268 LIMIT 1 [ RunTime:0.000971s ]
  9. UPDATE `article` SET `lasttime` = 1778801836 WHERE `id` = 627268 [ RunTime:0.009312s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 64 LIMIT 1 [ RunTime:0.000608s ]
  11. SELECT * FROM `article` WHERE `id` < 627268 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.001680s ]
  12. SELECT * FROM `article` WHERE `id` > 627268 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.005016s ]
  13. SELECT * FROM `article` WHERE `id` < 627268 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.007340s ]
  14. SELECT * FROM `article` WHERE `id` < 627268 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.001964s ]
  15. SELECT * FROM `article` WHERE `id` < 627268 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.002574s ]
0.237558s