diff --git a/app/Coding/Disk.php b/app/Coding/Disk.php index c04ddc0..06db24a 100644 --- a/app/Coding/Disk.php +++ b/app/Coding/Disk.php @@ -58,7 +58,7 @@ public function createFile(string $token, string $projectName, array $data): arr return $result['Response']['Data']; } - public function uploadAttachments(string $token, string $projectName, string $dataPath, array $attachments): array + public function uploadAttachments(string $token, string $projectName, string $dataDir, array $attachments): array { if (empty($attachments)) { return []; @@ -72,13 +72,14 @@ public function uploadAttachments(string $token, string $projectName, string $da $projectName, $filename ); + $filePath = $dataDir . DIRECTORY_SEPARATOR . $path; $result = []; try { - $this->upload($uploadToken, $dataPath . $path); + $this->upload($uploadToken, $filePath); $result = $this->createFile($token, $projectName, [ "OriginalFileName" => $filename, - "MimeType" => mime_content_type($dataPath . $path), - "FileSize" => filesize($dataPath . $path), + "MimeType" => mime_content_type($filePath), + "FileSize" => filesize($filePath), "StorageKey" => $uploadToken['StorageKey'], "Time" => $uploadToken['Time'], "AuthToken" => $uploadToken['AuthToken'], diff --git a/app/Commands/WikiImportCommand.php b/app/Commands/WikiImportCommand.php index 10108ac..c45d961 100644 --- a/app/Commands/WikiImportCommand.php +++ b/app/Commands/WikiImportCommand.php @@ -148,8 +148,12 @@ private function handleConfluenceApi(): int private function handleConfluenceHtml(): int { - $htmlDir = $this->unzipConfluenceHtml(); - $filePath = $htmlDir . 'index.html'; + $path = $this->unzipConfluenceHtml(); + if (str_ends_with($path, '.html')) { + return $this->uploadConfluencePage($path); + } + $htmlDir = $path; + $filePath = $htmlDir . DIRECTORY_SEPARATOR . 'index.html'; if (!file_exists($filePath)) { $message = "文件不存在:$filePath"; $this->error($message); @@ -188,55 +192,75 @@ private function handleConfluenceHtml(): int return 0; } - private function uploadConfluencePages(string $dataPath, array $tree, array $titles, int $parentId = 0): void + private function uploadConfluencePages(string $htmlDir, array $tree, array $titles, int $parentId = 0): void { foreach ($tree as $page => $subPages) { $title = $titles[$page]; - $this->info('标题:' . $title); - try { - $markdown = $this->confluence->htmlFile2Markdown($dataPath . $page); - } catch (FileNotFoundException $e) { - $this->error('页面不存在:' . $dataPath . $page); - continue; + $wikiId = $this->uploadConfluencePage($htmlDir . DIRECTORY_SEPARATOR . $page, $title, $parentId); + if ($wikiId && !empty($subPages)) { + $this->info('发现 ' . count($subPages) . ' 个子页面'); + // TODO tests + $this->uploadConfluencePages($htmlDir, $subPages, $titles, $wikiId); } - $mdFilename = $this->dealAttachments($dataPath, $page, $markdown); - $zipFilePath = $this->codingWiki->createMarkdownZip($markdown, $dataPath, $mdFilename, $title); - $result = $this->codingWiki->createWikiByUploadZip( + } + } + + private function uploadConfluencePage(string $filePath, string $title = '', int $parentId = 0): int + { + try { + $markdown = $this->confluence->htmlFile2Markdown($filePath); + } catch (FileNotFoundException $e) { + $message = '页面不存在:' . $filePath; + $this->error($message); + $this->errors[] = $message; + return false; + } + libxml_use_internal_errors(true); + $this->document->loadHTMLFile($filePath); + if (empty($title)) { + $title = $this->document->getElementsByTagName('title')[0]->nodeValue; + } + $this->info('标题:' . $title); + + $htmlDir = dirname($filePath); + $page = basename($filePath); + $markdown = $this->dealAttachments($filePath, $markdown); + $mdFilename = substr($page, 0, -5) . '.md'; + if ($this->option('save-markdown')) { + file_put_contents($htmlDir . DIRECTORY_SEPARATOR . $mdFilename, $markdown . "\n"); + } + $zipFilePath = $this->codingWiki->createMarkdownZip($markdown, $htmlDir, $mdFilename, $title); + $result = $this->codingWiki->createWikiByUploadZip( + $this->codingToken, + $this->codingProjectUri, + $zipFilePath, + $parentId, + ); + $this->info('上传成功,正在处理,任务 ID:' . $result['JobId']); + $wikiId = null; + try { + $jobStatus = $this->codingWiki->getImportJobStatusWithRetry( $this->codingToken, $this->codingProjectUri, - $zipFilePath, - $parentId, + $result['JobId'] ); - $this->info('上传成功,正在处理,任务 ID:' . $result['JobId']); - $wikiId = null; - try { - $jobStatus = $this->codingWiki->getImportJobStatusWithRetry( - $this->codingToken, - $this->codingProjectUri, - $result['JobId'] - ); - } catch (Exception $e) { - $message = '错误:导入失败,跳过 ' . $title . ' ' . $page; - $this->error($message); - $this->errors[] = $message; - continue; - } - if ($jobStatus['Status'] == 'success') { - $wikiId = intval($jobStatus['Iids'][0]); - } - if (empty($wikiId)) { - $message = '错误:导入失败,跳过 ' . $title . ' ' . $page; - $this->error($message); - $this->errors[] = $message; - continue; - } - $this->codingWiki->updateTitle($this->codingToken, $this->codingProjectUri, $wikiId, $title); - if (!empty($subPages)) { - $this->info('发现 ' . count($subPages) . ' 个子页面'); - // TODO tests - $this->uploadConfluencePages($dataPath, $subPages, $titles, $wikiId); - } + } catch (Exception $e) { + $message = '错误:导入失败,跳过 ' . $title . ' ' . $page; + $this->error($message); + $this->errors[] = $message; + return false; + } + if ($jobStatus['Status'] == 'success') { + $wikiId = intval($jobStatus['Iids'][0]); } + if (empty($wikiId)) { + $message = '错误:导入失败,跳过 ' . $title . ' ' . $page; + $this->error($message); + $this->errors[] = $message; + return false; + } + $this->codingWiki->updateTitle($this->codingToken, $this->codingProjectUri, $wikiId, $title); + return $wikiId; } private function unzipConfluenceHtml(): string @@ -263,16 +287,16 @@ private function unzipConfluenceHtml(): string $zip->close(); return $tmpDir . '/' . scandir($tmpDir, 1)[0] . '/'; } - return str_ends_with($dataPath, '/index.html') ? substr($dataPath, 0, -10) : Str::finish($dataPath, '/'); + return rtrim($dataPath, '/'); } - private function dealAttachments(string $dataPath, string $page, string $markdown): string + private function dealAttachments(string $filePath, string $markdown): string { - $attachments = $this->confluence->parseAttachments($dataPath . $page, $markdown); + $attachments = $this->confluence->parseAttachments($filePath, $markdown); $codingAttachments = $this->codingDisk->uploadAttachments( $this->codingToken, $this->codingProjectUri, - $dataPath, + dirname($filePath), $attachments ); foreach ($codingAttachments as $attachmentPath => $codingAttachment) { @@ -282,11 +306,6 @@ private function dealAttachments(string $dataPath, string $page, string $markdow $this->errors[] = $message; } } - $markdown = $this->codingWiki->replaceAttachments($markdown, $codingAttachments); - $mdFilename = substr($page, 0, -5) . '.md'; - if ($this->option('save-markdown')) { - file_put_contents($dataPath . $mdFilename, $markdown . "\n"); - } - return $mdFilename; + return $this->codingWiki->replaceAttachments($markdown, $codingAttachments); } } diff --git a/app/Commands/WikiUploadCommand.php b/app/Commands/WikiUploadCommand.php index 1d80766..bf6342c 100644 --- a/app/Commands/WikiUploadCommand.php +++ b/app/Commands/WikiUploadCommand.php @@ -30,7 +30,7 @@ class WikiUploadCommand extends Command * * @var string */ - protected $description = '上传 Zip 导入 Wiki'; + protected $description = '上传 Markdown 和图片的 Zip 导入 Wiki'; /** * Execute the console command. diff --git a/tests/Feature/WikiImportCommandTest.php b/tests/Feature/WikiImportCommandTest.php index 3d3a2fb..a549855 100755 --- a/tests/Feature/WikiImportCommandTest.php +++ b/tests/Feature/WikiImportCommandTest.php @@ -152,7 +152,7 @@ public function testHandleConfluenceHtmlFileNotExist() ->expectsQuestion('数据来源?', 'Confluence') ->expectsQuestion('数据类型?', 'HTML') ->expectsQuestion('空间导出的 HTML zip 文件路径', '/dev/null/index.html') - ->expectsOutput('文件不存在:/dev/null/index.html') + ->expectsOutput('页面不存在:/dev/null/index.html') ->assertExitCode(1); } @@ -188,7 +188,6 @@ public function testHandleConfluenceHtmlSuccess() ->expectsOutput('空间标识:space1') ->expectsOutput('发现 3 个一级页面') ->expectsOutput("开始导入 CODING:") - ->expectsOutput('标题:Not Found') ->expectsOutput('页面不存在:' . $this->dataDir . 'confluence/space1/not-found.html') ->expectsOutput('标题:Image Demo') ->expectsOutput('上传成功,正在处理,任务 ID:a12353fa-f45b-4af2-83db-666bf9f66615') @@ -199,13 +198,15 @@ public function testHandleConfluenceHtmlSuccess() ->expectsOutput('上传成功,正在处理,任务 ID:a12353fa-f45b-4af2-83db-666bf9f66615') ->expectsOutput('标题:Text Demo') ->expectsOutput('上传成功,正在处理,任务 ID:a12353fa-f45b-4af2-83db-666bf9f66615') - ->assertExitCode(0); - $this->assertFileExists($this->dataDir . 'confluence/space1/65591.md'); - $this->assertFileExists($this->dataDir . 'confluence/space1/attachment-demo_65615.md'); - $this->assertFileExists($this->dataDir . 'confluence/space1/text-demo_65601.md'); - unlink($this->dataDir . 'confluence/space1/65591.md'); - unlink($this->dataDir . 'confluence/space1/attachment-demo_65615.md'); - unlink($this->dataDir . 'confluence/space1/text-demo_65601.md'); + ->expectsOutput('报错信息汇总:') + ->expectsOutput('页面不存在:' . $this->dataDir . 'confluence/space1/not-found.html') + ->assertExitCode(1); + $this->assertFileExists($this->dataDir . '/confluence/space1/65591.md'); + $this->assertFileExists($this->dataDir . '/confluence/space1/attachment-demo_65615.md'); + $this->assertFileExists($this->dataDir . '/confluence/space1/text-demo_65601.md'); + unlink($this->dataDir . '/confluence/space1/65591.md'); + unlink($this->dataDir . '/confluence/space1/attachment-demo_65615.md'); + unlink($this->dataDir . '/confluence/space1/text-demo_65601.md'); } public function testAskNothing() @@ -269,4 +270,33 @@ public function testHandleConfluenceHtmlZipSuccess() ->expectsOutput('上传成功,正在处理,任务 ID:a12353fa-f45b-4af2-83db-666bf9f66615') ->assertExitCode(0); } + + public function testHandleConfluenceSingleHtmlSuccess() + { + $this->setConfig(); + + // 注意:不能使用 partialMock + // https://laracasts.com/discuss/channels/testing/this-partialmock-doesnt-call-the-constructor + $mock = \Mockery::mock(Wiki::class, [])->makePartial(); + $this->instance(Wiki::class, $mock); + + $mock->shouldReceive('createWikiByUploadZip')->times(1)->andReturn(json_decode( + file_get_contents($this->dataDir . 'coding/' . 'CreateWikiByZipResponse.json'), + true + )['Response']); + $mock->shouldReceive('getImportJobStatus')->times(1)->andReturn(json_decode( + file_get_contents($this->dataDir . 'coding/' . 'DescribeImportJobStatusResponse.json'), + true + )['Response']['Data']); + $mock->shouldReceive('updateTitle')->times(1)->andReturn(true); + + + $this->artisan('wiki:import') + ->expectsQuestion('数据来源?', 'Confluence') + ->expectsQuestion('数据类型?', 'HTML') + ->expectsQuestion('空间导出的 HTML zip 文件路径', $this->dataDir . 'confluence/space1/image-demo_65619.html') + ->expectsOutput('标题:空间 1 : Image Demo') + ->expectsOutput('上传成功,正在处理,任务 ID:a12353fa-f45b-4af2-83db-666bf9f66615') + ->assertExitCode(0); + } }