Files
ulthon_information/app/common/tools/PhoneImage.php
augushong 90b4b1d5f2 refactor(phone-image): 清理死代码、修复历史记录和媒体标签安全移除
T8: 删除estimateImageHeight/estimateTableHeight/showGeneratedThumbnails/switchSize/
    getConfig/getPages/renderContentFlow等未使用函数,exportLongImage添加render锁检查
T9: loadFromHistory恢复pageAlignments,font_size→fontSize命名统一(PHP+JS双向兼容),
    修复历史加载时fontSize显示值bug
T10: preprocessContent移除iframe/video/svg/embed/object标签,
    封面图添加onerror处理
2026-05-07 21:53:03 +08:00

235 lines
7.1 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace app\common\tools;
use app\model\Post;
use app\model\PostOutput;
use app\model\PostOutputFile;
use think\facade\App;
class PhoneImage implements PostOutputManagerInterface
{
/**
* 返回配置字段定义
*/
public function getConfigFields(): array
{
return [
'template' => ['type' => 'select', 'options' => ['minimal', 'magazine', 'mixed'], 'default' => 'minimal'],
'size' => ['type' => 'select', 'options' => ['xiaohongshu', 'douyin'], 'default' => 'xiaohongshu'],
'font' => ['type' => 'select', 'options' => ['source-han-sans', 'alibaba-puhuiti', 'lxgw-wenkai'], 'default' => 'source-han-sans'],
'fontSize' => ['type' => 'number', 'default' => 14, 'min' => 10, 'max' => 24],
'watermark' => ['type' => 'text', 'default' => ''],
];
}
/**
* 验证配置合法性
*/
public function validateConfig(array $config): bool
{
$fields = $this->getConfigFields();
if (isset($config['template']) && !in_array($config['template'], $fields['template']['options'])) {
return false;
}
if (isset($config['size']) && !in_array($config['size'], $fields['size']['options'])) {
return false;
}
if (isset($config['font']) && !in_array($config['font'], $fields['font']['options'])) {
return false;
}
if (isset($config['fontSize'])) {
$size = intval($config['fontSize']);
if ($size < $fields['fontSize']['min'] || $size > $fields['fontSize']['max']) {
return false;
}
}
return true;
}
/**
* 执行输出处理 - 服务端接收base64数据保存文件创建记录
* 注意HTML渲染在前端完成(html2canvas),服务端只做文件保存和记录创建
*/
public function process(Post $post, array $config): array
{
return [];
}
/**
* 返回预览HTML - 预览在前端完成,服务端返回空
*/
public function getPreview(Post $post, array $config): string
{
return '';
}
/**
* 保存单页图片
* @param int $outputId 输出记录ID
* @param int $page 页码(从1开始)
* @param string $imageData base64编码的图片数据
* @return PostOutputFile|false
*/
public function savePageImage(int $outputId, int $page, string $imageData)
{
$imageData = str_replace('data:image/jpeg;base64,', '', $imageData);
$imageData = str_replace('data:image/png;base64,', '', $imageData);
$imageData = str_replace(' ', '+', $imageData);
$decoded = base64_decode($imageData);
if ($decoded === false) {
return false;
}
$dateDir = date('Ymd');
$relativeDir = '/upload/post_output/' . $dateDir;
$fileName = $outputId . '_' . $page . '.jpg';
$relativePath = $relativeDir . '/' . $fileName;
$fullDir = App::getRootPath() . '/public' . $relativeDir;
$fullPath = $fullDir . '/' . $fileName;
if (!is_dir($fullDir)) {
mkdir($fullDir, 0777, true);
}
file_put_contents($fullPath, $decoded);
$imageInfo = getimagesize($fullPath);
$width = $imageInfo[0] ?? 0;
$height = $imageInfo[1] ?? 0;
$fileRecord = PostOutputFile::create([
'output_id' => $outputId,
'page' => $page,
'file_path' => $relativePath,
'file_url' => $relativePath,
'file_size' => strlen($decoded),
'width' => $width,
'height' => $height,
]);
return $fileRecord;
}
/**
* 打包某条输出记录的所有图片为ZIP
* @param int $outputId
* @return string ZIP文件路径
* @throws \Exception
*/
public function createZip(int $outputId): string
{
$output = PostOutput::find($outputId);
if (!$output) {
throw new \Exception('输出记录不存在');
}
$files = PostOutputFile::where('output_id', $outputId)
->order('page', 'asc')
->select();
if ($files->isEmpty()) {
throw new \Exception('没有可下载的文件');
}
$tempDir = App::getRuntimePath() . 'temp';
if (!is_dir($tempDir)) {
mkdir($tempDir, 0777, true);
}
$zipFileName = 'post_output_' . $outputId . '.zip';
$zipPath = $tempDir . '/' . $zipFileName;
$zip = new \ZipArchive();
if ($zip->open($zipPath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) {
throw new \Exception('无法创建ZIP文件');
}
foreach ($files as $file) {
$filePath = App::getRootPath() . '/public' . $file->file_path;
if (file_exists($filePath)) {
$pageName = str_pad((string) $file->page, 2, '0', STR_PAD_LEFT) . '.jpg';
$zip->addFile($filePath, $pageName);
}
}
$zip->close();
return $zipPath;
}
/**
* 删除输出记录及所有关联文件
* @param int $outputId
* @return bool
*/
public function deleteOutput(int $outputId): bool
{
$files = PostOutputFile::where('output_id', $outputId)->select();
foreach ($files as $file) {
$filePath = App::getRootPath() . '/public' . $file->file_path;
if (file_exists($filePath)) {
@unlink($filePath);
}
}
PostOutputFile::where('output_id', $outputId)->delete();
PostOutput::destroy($outputId);
return true;
}
/**
* 创建输出记录
* @param int $postId 文章ID
* @param array $config 配置
* @param int $adminId 管理员ID
* @return PostOutput
*/
public function createOutput(int $postId, array $config, int $adminId): PostOutput
{
return PostOutput::create([
'post_id' => $postId,
'output_type' => 'phone_image',
'config' => $config,
'status' => PostOutput::STATUS_PROCESSING,
'page_count' => 0,
'admin_id' => $adminId,
]);
}
/**
* 完成输出记录 - 更新状态和页数
*/
public function completeOutput(int $outputId, int $pageCount): bool
{
return PostOutput::where('id', $outputId)->update([
'status' => PostOutput::STATUS_COMPLETED,
'page_count' => $pageCount,
]) > 0;
}
/**
* 仅保存配置(不生成图片),创建新的历史记录
* @param int $postId 文章ID
* @param array $config 配置(含 size/fontSize/watermark/pageAlignments/content_html)
* @param int $adminId 管理员ID
* @return PostOutput
*/
public static function saveConfigOnly(int $postId, array $config, int $adminId): PostOutput
{
return PostOutput::create([
'post_id' => $postId,
'output_type' => 'phone_image',
'config' => $config,
'status' => PostOutput::STATUS_PROCESSING,
'page_count' => 0,
'admin_id' => $adminId,
]);
}
}