文档目录
动态资源处理

简介

本技术指南围绕 DouPHP 的动态资源处理能力,系统阐述用户上传文件的存储机制、临时文件生命周期管理、日志组织、缓存设计、安全控制与大文件优化方案。文档以代码为依据,聚焦附件服务、文件系统抽象与分块上传处理器等核心实现,帮助开发者在业务中正确、安全、高效地处理动态资源。

项目结构

DouPHP 将“动态资源”相关能力集中在以下位置:

  • 附件领域服务:负责上传、草稿、认领、URL 解析、画廊聚合等
  • 文件系统抽象:统一磁盘配置、路径归一化与驱动扩展点
  • 分块上传处理器:实现大文件分片合并、状态记录与落库
  • 配置中心:集中定义默认上传策略、允许扩展名、大小限制与缩略图策略
  • 门面与入口:对外暴露 attachment() 静态调用方式,简化业务接入
graph TB
A["前端/小程序"] --> B["控制器<br/>如 UserController"]
B --> C["附件门面 Attachment"]
C --> D["附件服务 AttachmentService"]
D --> E["分块处理器 ChunkedUploadHandler"]
D --> F["文件系统管理器 FilesystemManager"]
F --> G["磁盘 Disklocal"]
D --> H["数据库 dou_file"]
D --> I["图片处理 ImageManager"]

核心组件

  • 附件服务 AttachmentService:封装上传、草稿、认领、内容拉图、URL 生成、画廊查询等完整流程;内置类型校验、大小限制、水印与缩略图生成;支持分块上传与草稿认领。
  • 分块上传处理器 ChunkedUploadHandler:接收分片参数,按顺序合并为完整文件,写入磁盘并持久化元数据;支持草稿模式与拥有者身份标记。
  • 文件系统管理器 FilesystemManager:根据命名约定与配置文件解析磁盘根、URL 前缀与上传策略;提供 build 动态构造磁盘的能力。
  • 配置 file.php:集中声明默认磁盘、上传最大尺寸、允许扩展名、图片质量与缩略图子目录等。
  • 门面 Attachment:通过静态方法暴露附件服务能力,便于业务侧直接调用。

架构总览

下图展示一次典型上传请求从控制器到落盘与写库的调用链,以及可选的图片后处理与缩略图生成。

sequenceDiagram
participant Client as "客户端"
participant Ctrl as "控制器"
participant Facade as "Attachment 门面"
participant Service as "AttachmentService"
participant FS as "FilesystemManager/Disk"
participant DB as "dou_file 表"
participant Img as "ImageManager"
Client->>Ctrl : 提交表单/分片
Ctrl->>Facade : attachment()->store(...)
Facade->>Service : store(module, itemId, file, type, options)
Service->>FS : 解析磁盘/路径/策略
Service->>Img : 可选 resize/watermark/thumb
Service->>DB : insert/update 元数据
Service-->>Client : 返回 number/url

详细组件分析

附件服务 AttachmentService

  • 单文件上传 store:
    • 解析 disk、允许扩展名、大小上限、图片质量与缩略图目录
    • 若业务字段已有 number 且存在对应行,走更新分支;否则分配新 number
    • 校验扩展名与大小,创建目录并移动临时文件
    • 可选图片缩放、水印、缩略图生成
    • 计算最终体积并写入 dou_file(insert/update),记录上传者身份与时间戳
  • 替换原图 replaceByNumber:
    • 仅对可编辑扩展名(jpg/jpeg/png/webp)生效
    • 先备份原文件,再移动或转码覆盖,必要时重建缩略图
  • 仅落盘 storeToDirectory:
    • 不写 dou_file,适合临时或受控目录的文件保存
  • 分块上传 chunkedStore / chunkedStoreDraft:
    • 委托给 ChunkedUploadHandler,支持草稿模式与拥有者身份
  • 内容拉图 storeContentImages / storeFromUrl:
    • 扫描富文本远程图,下载并替换为本地链接,支持草稿模式
  • 草稿认领 claimByToken:
    • 将草稿附件归属到真实业务主键,同时校验 module + token + 身份 + 状态,防止越权
  • 清理过期草稿 cleanupUserDrafts:
    • 基于 draft_expire_at 清理过期草稿及其物理文件
flowchart TD
Start(["进入 store"]) --> Parse["解析磁盘/策略/业务字段"]
Parse --> CheckExt{"扩展名允许?"}
CheckExt -- 否 --> Fail["返回错误"]
CheckExt -- 是 --> CheckSize{"大小<=上限?"}
CheckSize -- 否 --> Fail
CheckSize -- 是 --> Move["创建目录并移动文件"]
Move --> PostProc{"需要图片后处理?"}
PostProc -- 是 --> Resize["缩放/水印/缩略图"]
PostProc -- 否 --> SizeCalc["计算体积"]
Resize --> SizeCalc
SizeCalc --> WriteDB["写入 dou_file"]
WriteDB --> End(["返回 number"])

分块上传处理器 ChunkedUploadHandler

  • 职责:接收分片参数(blob_num、total_blob_num、file_name、sql_link_url、file),按序合并为完整文件,写入磁盘并持久化元数据
  • 草稿模式:当传入 draftCtx 时,以 status='draft' 写入,item_id=0,并携带 uploader_type/uploader_id/draft_token/draft_expire_at
  • 拥有者模式:当传入 ownedCtx 时,写入 uploader_type/uploader_id 与 status='owned'
  • 完成判定:当所有分片到达后,计算最终 size 并落库
sequenceDiagram
participant FE as "前端"
participant Handler as "ChunkedUploadHandler"
participant Disk as "Disk"
participant Repo as "AttachmentRepository"
FE->>Handler : 发送分片 (blob_num, total, file)
Handler->>Disk : 追加写入分片
alt 最后一个分片
Handler->>Disk : 关闭/刷新
Handler->>Repo : insert/update 元数据
Handler-->>FE : 返回进度/结果
else 中间分片
Handler-->>FE : 返回继续上传
end

文件系统与磁盘策略 FilesystemManager

  • 磁盘解析顺序:upload_defaults → 命名约定推导 → disks 显式声明
  • 命名约定:
    • {module}_icon → images/{module}/icon/
    • 标准名 [a-z0-9_]+ → images/{name}/
  • 动态构建:build(relativeRoot, overrides) 用于无固定配置的视图目录
  • 默认驱动:local,可扩展自定义驱动工厂 extend(driverName, factory)

配置与策略 file.php

  • upload_defaults:
    • upload_max_kb:单文件上传上限(KB)
    • allow_extensions:允许的扩展名清单(逗号分隔,小写)
    • image_quality:图片压缩质量(0-100)
    • thumb_directory:缩略图相对子目录
  • disks:
    • local:root 指向 images/upload/
    • avatar_admin:覆盖 upload_max_kb 为 100

控制器接入示例

  • 前台用户控制器使用 UploadedFile 组装多文件,并通过 attachment()->store/storeDraft 进行上传或草稿暂存,支持自定义文件名、宽度限制与水印

依赖关系分析

  • 附件服务依赖:
    • FilesystemManager/Disk:统一磁盘访问与路径归一化
    • ImageManager:图片缩放、水印、缩略图
    • AttachmentRepository:dou_file 表的增删改查与草稿认领
    • ChunkedUploadHandler:分块上传逻辑
  • 配置依赖:
    • file.php 中的 upload_defaults 与 disks 决定全局与模块级上传策略
  • 门面依赖:
    • Attachment 门面代理到 AttachmentService,保持调用简洁
classDiagram
class AttachmentService {
+store(...)
+replaceByNumber(...)
+storeToDirectory(...)
+chunkedStore(...)
+claimByToken(...)
+cleanupUserDrafts(...)
}
class ChunkedUploadHandler {
+handle(...)
}
class FilesystemManager {
+disk(name)
+build(root, overrides)
+extend(driver, factory)
}
class Attachment {
<<facade>>
}
Attachment --> AttachmentService : "代理"
AttachmentService --> ChunkedUploadHandler : "委托分块上传"
AttachmentService --> FilesystemManager : "获取磁盘"

性能考量

  • 分块上传:通过 ChunkedUploadHandler 将大文件拆分为多个小分片,降低单次请求压力与超时风险,提升稳定性与可恢复性
  • 图片后处理:按需执行 resize/watermark/thumb,避免不必要的 CPU 与 IO 开销
  • 磁盘策略:利用 upload_defaults 与 disks 覆盖,针对不同模块设置合理的 upload_max_kb 与 quality,平衡质量与体积
  • 缩略图目录:thumb_directory 分离缩略图与原图,便于 CDN 缓存与独立清理策略
  • 懒 GC:cleanupUserDrafts 定期清理过期草稿,释放磁盘空间与数据库行数

故障排查指南

  • 上传失败常见原因:
    • 扩展名不在 allow_extensions 白名单内
    • 文件大小超过 upload_max_kb
    • 目标目录不可写或缺少权限
    • move 操作失败(临时文件异常或磁盘空间不足)
  • 草稿未认领:
    • 检查 claimByToken 是否传入正确的 module、draft_token、identityKind、identityId
    • 确认草稿未过期(draft_expire_at)
  • 分块上传不完整:
    • 核对 blob_num 与 total_blob_num 是否一致
    • 检查 sql_link_url 是否正确传递
    • 查看最后一次分片是否成功触发 insert/update
  • 图片处理后问题:
    • 确认图片格式支持(resize/watermark 支持的格式)
    • 检查缩略图尺寸与质量配置

结论

DouPHP 的动态资源处理以 AttachmentService 为核心,结合 FilesystemManager 的统一磁盘抽象与 ChunkedUploadHandler 的大文件分片能力,提供了安全、可控、可扩展的上传与存储方案。通过 file.php 的配置集中管理上传策略,配合草稿认领与懒 GC,确保系统在高性能与安全之间取得平衡。建议在实际业务中严格遵循白名单与大小限制,合理使用分块上传与图片后处理,并建立完善的清理与监控机制。

附录

用户上传文件存储机制

  • 文件类型验证:基于 allow_extensions 白名单校验,拒绝非法扩展名
  • 大小限制:依据 upload_max_kb 进行限制,超出则拒绝上传
  • 路径生成规则:
    • 由 FilesystemManager 根据命名约定或 disks 配置确定 root
    • 业务层通过 options.directory 指定子目录
    • 文件名由 basename + 原始扩展名组成,必要时随机化
  • 落盘与写库:move 成功后计算 size,写入 dou_file,记录上传者身份与时间戳

临时文件生命周期管理

  • 会话数据:由上层框架管理,附件服务不直接持有会话
  • 上传临时文件:由 PHP 运行时管理,AttachmentService 通过 UploadedFile 读取并移动到目标目录
  • 缓存数据:Excel 等库内部使用内存/磁盘缓存,可通过配置切换缓存策略(如 memory、php_temp 等)
  • 草稿附件:以 status='draft' 写入,携带 draft_expire_at,由 cleanupUserDrafts 定期清理

日志文件组织结构

  • 访问日志、错误日志、业务日志在本仓库中未见统一的日志子系统实现;建议在业务层通过审计或日志组件记录关键事件(如登录失败、上传失败、草稿清理等)
  • 建议分类:
    • 访问日志:记录接口请求与响应
    • 错误日志:记录异常堆栈与上下文
    • 业务日志:记录关键业务流程(上传、认领、清理)

缓存系统设计

  • 数据缓存:可在业务层引入 Redis/Memcache 等缓存后端,对热点数据进行缓存
  • 模板缓存:模板编译产物可缓存至磁盘或内存,减少重复编译
  • 查询缓存:对频繁查询的结果进行缓存,注意失效策略与一致性
  • Excel 库缓存:可通过 Settings::setCacheStorageMethod 切换缓存方式,优化大文件处理性能

文件安全控制机制

  • 文件类型白名单:allow_extensions 严格控制允许扩展名
  • 路径遍历防护:通过 FilesystemManager 的路径归一化与 Disk 的安全访问,避免越权访问
  • 权限控制:目录写入前检查 FileHelper::permission,确保目标目录可写
  • 草稿认领安全:claimByToken 同时校验 module、draft_token、uploader_type、uploader_id 与 status,防止跨身份越权

大文件处理优化方案

  • 分片上传:通过 ChunkedUploadHandler 实现分片合并与状态跟踪
  • 断点续传:前端需记录已上传分片,服务端按 blob_num 判断是否继续
  • 进度监控:服务端返回当前分片序号与总数,前端据此更新进度条
  • 并发控制:限制同一文件并发分片数,避免服务器过载

存储空间管理与清理策略

  • 草稿清理:cleanupUserDrafts 基于 draft_expire_at 清理过期草稿及物理文件
  • 缩略图管理:缩略图与原图分离,可按目录策略清理
  • 定期任务:建议定时任务扫描 dou_file 中孤立文件并清理
  • 配额管理:结合 upload_max_kb 与磁盘容量监控,防止磁盘爆满
添加日期:2026-10-05