['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'], 'font_size' => ['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['font_size'])) { $size = intval($config['font_size']); if ($size < $fields['font_size']['min'] || $size > $fields['font_size']['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; } }