菜鸟-创作你的创作

基于PHP搭建一个Word文档处理框架

好——下面给你一份可直接落地的方案:用 PHP 搭建一个可扩展、可测试、可部署的 Word 文档处理框架(支持生成、模板填充、解析、合并、转换为 PDF/图片、权限与防宏处理、异步任务与扩展接口)。我把设计、关键组件、代码示例(可复制)、部署与安全注意点都写清楚,按你能直接实现的顺序来。

如果你想让我把这些文件生成为项目骨架(composer.json、示例类、API 路由等),告诉我你想用的框架(原生 PHP / Laravel / Slim / Lumen),我可以直接输出文件内容。

目标功能(最小可用版本 MVP)

  1. 上传 Word(.docx、.doc、.docm)并存储
  2. 从模板生成 Word(占位符替换)
  3. 读取/提取 Word 文本与元数据
  4. 合并多个 Word 文档
  5. 转换 Word → PDF(或图片)
  6. 防宏检测与拒绝含宏的文件(.docm)
  7. 支持异步任务(大文件/转换在后台处理)
  8. REST API:上传、创建、转换、下载、状态查询

技术栈与第三方工具(推荐)


架构设计(模块化)


项目目录示例

doc-processor/
├─ src/
│  ├─ Controller/
│  │  ├─ UploadController.php
│  │  ├─ TemplateController.php
│  ├─ Service/
│  │  ├─ DocumentService.php
│  │  ├─ TemplateService.php
│  │  ├─ ConversionService.php
│  ├─ Worker/
│  │  └─ ConversionWorker.php
│  ├─ Storage/
│  │  └─ FilesystemFactory.php
│  └─ Utils/
│     └─ MacroScanner.php
├─ public/
│  └─ index.php
├─ tests/
├─ composer.json
└─ docker/
   └─ Dockerfile + docker-compose.yml


关键实现(代码示例——框架无关,composer 可直接运行)

先给 composer.json 主要依赖:

{
  "require": {
    "php": "^8.1",
    "phpoffice/phpword": "^1.1",
    "league/flysystem": "^2.0",
    "symfony/http-foundation": "^6.0",
    "monolog/monolog": "^2.0"
  }
}

1) 简单的 DocumentService(上传 + 存储 + 防宏检查)

<?php
namespace App\Service;

use League\Flysystem\FilesystemOperator;
use Symfony\Component\HttpFoundation\File\UploadedFile;

class DocumentService {
    private FilesystemOperator $storage;
    private string $tmpDir;
    private MacroScanner $scanner;

    public function __construct(FilesystemOperator $storage, MacroScanner $scanner, string $tmpDir = '/tmp') {
        $this->storage = $storage;
        $this->scanner = $scanner;
        $this->tmpDir = $tmpDir;
    }

    public function saveUploadedFile(UploadedFile $file, string $ownerId): array {
        // 基础验证
        $ext = strtolower($file->getClientOriginalExtension());
        if (!in_array($ext, ['doc','docx','docm'])) {
            throw new \InvalidArgumentException('不支持的文件类型');
        }

        // 存到临时位置
        $tmpPath = $this->tmpDir . '/' . uniqid('upload_') . '.' . $ext;
        $file->move(dirname($tmpPath), basename($tmpPath));

        // 宏检测(如果 docm/含宏就拒绝或隔离)
        if ($this->scanner->hasMacros($tmpPath)) {
            unlink($tmpPath);
            throw new \RuntimeException('文件包含宏,被拒绝上传');
        }

        // 进一步可调用 ClamAV 扫描...

        // 以日期分桶存储
        $dest = "documents/{$ownerId}/" . date('Y/m/d') . '/' . basename($tmpPath);
        $stream = fopen($tmpPath, 'rb');
        $this->storage->writeStream($dest, $stream);
        if (is_resource($stream)) fclose($stream);
        unlink($tmpPath);

        // 返回元数据(可存 DB)
        return ['path' => $dest, 'ext' => $ext, 'uploaded_at' => time()];
    }
}

MacroScanner(简单版)

<?php
namespace App\Utils;

class MacroScanner {
    // 用最简单的方式:检测 .docm 扩展或 docx 中是否含 vbaProject.bin
    public function hasMacros(string $filePath): bool {
        $ext = pathinfo($filePath, PATHINFO_EXTENSION);
        if ($ext === 'docm') return true;
        if ($ext === 'docx') {
            // docx 其实是 zip,检查是否含 vbaProject.bin
            $zip = new \ZipArchive();
            if ($zip->open($filePath) === true) {
                $idx = $zip->locateName('word/vbaProject.bin', \ZipArchive::FL_NOCASE);
                $zip->close();
                return $idx !== false;
            }
        }
        return false;
    }
}

2) 模板填充(PhpWord Template)

<?php
use PhpOffice\PhpWord\TemplateProcessor;

class TemplateService {
    public function fillTemplate(string $templatePath, array $vars, string $outPath) {
        $tp = new TemplateProcessor($templatePath);

        foreach ($vars as $k => $v) {
            // 模板中用 ${name} 这样的占位
            $tp->setValue($k, $v);
        }
        $tp->saveAs($outPath);
    }
}

示例模板中占位符用 ${name}${date}

3) 读取 .docx 文本(简易)

PhpWord 的读取支持有限,但可以用 Reader:

<?php
use PhpOffice\PhpWord\IOFactory;

function extractTextFromDocx(string $path): string {
    $phpWord = IOFactory::load($path, 'Word2007');
    $text = '';
    foreach ($phpWord->getSections() as $section) {
        $elements = $section->getElements();
        foreach ($elements as $el) {
            if (method_exists($el, 'getText')) $text .= $el->getText() . "\n";
        }
    }
    return $text;
}

注意:复杂排版、表格、页眉页脚可能需要额外处理或用 LibreOffice 提取文本更稳妥。

4) 合并多个 docx(PhpWord)

合并较复杂,但思路是把文档内容复制到主文档(保持样式可能需额外处理)。示例参考 PhpWord 的 addExternalFile() 或循环拷贝元素。

5) 转换 Word → PDF(用 LibreOffice headless)

部署服务器需要安装 LibreOffice。命令行示例:

soffice --headless --convert-to pdf --outdir /path/to/out /path/to/input.docx

PHP 调用:

exec("soffice --headless --convert-to pdf --outdir " . escapeshellarg($outDir) . " " . escapeshellarg($inPath), $output, $ret);
if ($ret !== 0) { throw new \RuntimeException("转换失败: " . implode("\n",$output)); }

如果要把 PDF 转图片可使用 pdftoppm 或 ImageMagick(注意安全与性能)。


安全注意(必须执行)

  1. 拒绝包含宏的文件 或隔离处理(.docm 必慎)
  2. 对上传文件做 malware scan(ClamAV)
  3. 不要反序列化用户提交数据(和 serialize 相关)
  4. 限制文件大小(如 50MB),超大文件必须走分片上传与后台处理
  5. 对外部命令使用 escapeshellarg、限制运行用户(不要以 root 执行 soffice)
  6. 使用 CDN/S3 做公开下载,签名 URL(短期有效)
  7. 日志隐私:不在日志写入文件内容(敏感内容)

性能与可扩展性建议


REST API 设计(示例)


部署建议(Docker + docker-compose)


单元测试与 CI


常见问题与解决方案


进阶功能(可选)


小结(按步骤落地)

  1. 建立基础项目结构、安装 phpoffice/phpword 与 flysystem
  2. 实现上传 + MacroScanner + ClamAV 扫描
  3. 实现模板填充(TemplateProcessor)与文档下载
  4. 集成 LibreOffice 命令行进行转换(先在本地或开发容器测试)
  5. 把转换/合并任务搬到队列 worker(防止超时)
  6. 加入文件存储(S3)、日志、监控与安全扫描
  7. 编写单元测试,部署到生产容器,做好字体/soffice 配置
退出移动版