# 使用 Apache 部署 使用 Apache HTTP Server 托管 BentoPDF。 ## 前置条件 - Apache 2.4+ - 已启用 mod_rewrite - SSL 证书(推荐) ## 第 1 步:构建项目 ```bash git clone https://github.com/alam00000/bentopdf.git cd bentopdf npm install npm run build ``` 如需自定义品牌,请在构建前设置环境变量: ```bash VITE_BRAND_NAME="AcmePDF" VITE_BRAND_LOGO="images/acme-logo.svg" npm run build ``` ## 第 2 步:复制文件 ```bash sudo mkdir -p /var/www/bentopdf sudo cp -r dist/* /var/www/bentopdf/ sudo chown -R www-data:www-data /var/www/bentopdf ``` ## 第 3 步:Apache 配置 创建 `/etc/apache2/sites-available/bentopdf.conf`: ```apache ServerName your-domain.com Redirect permanent / https://your-domain.com/ ServerName your-domain.com DocumentRoot /var/www/bentopdf SSLEngine on SSLCertificateFile /etc/letsencrypt/live/your-domain.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/your-domain.com/privkey.pem Options -Indexes +FollowSymLinks AllowOverride All Require all granted # WASM MIME type AddType application/wasm .wasm # Prevent double-compression of pre-compressed files SetEnvIfNoCase Request_URI "\.gz$" no-gzip # Compression AddOutputFilterByType DEFLATE text/html text/plain text/css application/javascript application/json application/wasm # Cache headers ExpiresActive On ExpiresByType application/javascript "access plus 1 year" ExpiresByType text/css "access plus 1 year" ExpiresByType application/wasm "access plus 1 year" ExpiresByType image/png "access plus 1 year" ExpiresByType image/svg+xml "access plus 1 year" # Required headers for SharedArrayBuffer (LibreOffice WASM) Header always set Cross-Origin-Embedder-Policy "require-corp" Header always set Cross-Origin-Opener-Policy "same-origin" Header always set Cross-Origin-Resource-Policy "cross-origin" # Security headers Header always set X-Frame-Options "SAMEORIGIN" Header always set X-Content-Type-Options "nosniff" # Pre-compressed LibreOffice WASM files ForceType application/wasm Header set Content-Encoding "gzip" ForceType application/octet-stream Header set Content-Encoding "gzip" ``` ## 第 4 步:用于路由的 .htaccess 创建 `/var/www/bentopdf/.htaccess`: ```apache RewriteEngine On RewriteBase / # Existing files/dirs - serve directly RewriteCond %{REQUEST_FILENAME} -f [OR] RewriteCond %{REQUEST_FILENAME} -d RewriteRule ^ - [L] # ============================================ # LANGUAGE ROUTES # ============================================ # Supported languages: de, es, zh, zh-TW, vi, it, id, tr, fr, pt # English has no prefix - served from root # English prefix redirects to root RewriteRule ^en/?$ / [R=301,L] RewriteRule ^en/(.+)$ /$1 [R=301,L] # Language prefix root (e.g., /de/ -> /de/index.html) RewriteCond %{DOCUMENT_ROOT}/$1/index.html -f RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/?$ /$1/index.html [L] # Language prefix with path (e.g., /de/merge-pdf -> /de/merge-pdf.html) RewriteCond %{DOCUMENT_ROOT}/$1/$2.html -f RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/([^/]+)/?$ /$1/$2.html [L] # ============================================ # ADD .HTML EXTENSION (ROOT LEVEL) # ============================================ RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME}.html -f RewriteRule ^([^/]+)$ $1.html [L] ErrorDocument 404 /404.html ``` ## 第 5 步:启用所需模块 ```bash sudo a2enmod rewrite sudo a2enmod ssl sudo a2enmod headers sudo a2enmod expires sudo a2enmod deflate ``` ## 第 6 步:启用站点 ```bash sudo a2ensite bentopdf.conf sudo apache2ctl configtest sudo systemctl reload apache2 ``` ## 子目录部署 要部署在 `/pdf/` 下: 1. 使用基础 URL 构建: ```bash BASE_URL=/pdf/ npm run build ``` 2. 更新 `.htaccess`: ```apache RewriteEngine On RewriteBase /pdf/ # Existing files/dirs - serve directly RewriteCond %{REQUEST_FILENAME} -f [OR] RewriteCond %{REQUEST_FILENAME} -d RewriteRule ^ - [L] # Language routes RewriteRule ^en/?$ /pdf/ [R=301,L] RewriteRule ^en/(.+)$ /pdf/$1 [R=301,L] RewriteCond %{DOCUMENT_ROOT}/pdf/$1/index.html -f RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/?$ /pdf/$1/index.html [L] RewriteCond %{DOCUMENT_ROOT}/pdf/$1/$2.html -f RewriteRule ^(de|es|zh|zh-TW|vi|it|id|tr|fr|pt)/([^/]+)/?$ /pdf/$1/$2.html [L] # Root level .html extension RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME}.html -f RewriteRule ^([^/]+)$ $1.html [L] ErrorDocument 404 /pdf/404.html ``` ## 故障排查 ### WASM 404 错误 请确认已配置 MIME 类型: ```apache AddType application/wasm .wasm ``` ### 签名 PDF 或表单填写器显示空白查看器(`.mjs` MIME 错误) 如果浏览器控制台显示: ``` Failed to load module script: The server responded with a non-JavaScript MIME type of "application/octet-stream". Strict MIME type checking is enforced for module scripts per HTML spec. ``` ……并且失败的请求以 `.mjs` 结尾,说明您的 Apache 配置缺少 `.mjs → application/javascript` 的映射。内置的 PDF 查看器使用扩展名为 `.mjs` 的 ES 模块文件,而 Apache 自带的 `mime.types` 默认不包含它们。 将以下内容添加到您的 VirtualHost 或 `.htaccess`: ```apache AddType application/javascript .mjs ``` 然后重新加载 Apache: ```bash sudo apache2ctl configtest && sudo systemctl reload apache2 ``` 验证方法:在浏览器 DevTools 的 Network 标签页中找到失败的 `.mjs` 请求,检查 `Content-Type` 响应头。现在它应为 `application/javascript`,而不是 `application/octet-stream`。 ### Rewrite 不生效 请检查 mod_rewrite 是否已启用: ```bash sudo a2enmod rewrite ``` ### Word/ODT/Excel 转 PDF 无法使用 LibreOffice WASM 需要 `SharedArrayBuffer`,这需要以下响应头: ```apache Header always set Cross-Origin-Embedder-Policy "require-corp" Header always set Cross-Origin-Opener-Policy "same-origin" ``` 它还需要安全上下文。`http://localhost` 可用于本地测试,但 `http://192.168.x.x` 等局域网 IP 通常需要 HTTPS。如果响应头已存在,但 `window.crossOriginIsolated` 仍为 `false`,请检查页面是否在非环回来源上通过普通 HTTP 打开。 预压缩的 `.wasm.gz` 和 `.data.gz` 文件还需要正确的 `Content-Encoding`: ```apache ForceType application/wasm Header set Content-Encoding "gzip" ForceType application/octet-stream Header set Content-Encoding "gzip" ``` 请确保已启用 `mod_headers`:`sudo a2enmod headers` ### 权限被拒绝 ```bash sudo chown -R www-data:www-data /var/www/bentopdf sudo chmod -R 755 /var/www/bentopdf ```