Skip to content

Commit cac1ecb

Browse files
committed
feat: #66 import single html
1 parent 9c268e3 commit cac1ecb

File tree

4 files changed

+117
-67
lines changed

4 files changed

+117
-67
lines changed

app/Coding/Disk.php

+5-4
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public function createFile(string $token, string $projectName, array $data): arr
5858
return $result['Response']['Data'];
5959
}
6060

61-
public function uploadAttachments(string $token, string $projectName, string $dataPath, array $attachments): array
61+
public function uploadAttachments(string $token, string $projectName, string $dataDir, array $attachments): array
6262
{
6363
if (empty($attachments)) {
6464
return [];
@@ -72,13 +72,14 @@ public function uploadAttachments(string $token, string $projectName, string $da
7272
$projectName,
7373
$filename
7474
);
75+
$filePath = $dataDir . DIRECTORY_SEPARATOR . $path;
7576
$result = [];
7677
try {
77-
$this->upload($uploadToken, $dataPath . $path);
78+
$this->upload($uploadToken, $filePath);
7879
$result = $this->createFile($token, $projectName, [
7980
"OriginalFileName" => $filename,
80-
"MimeType" => mime_content_type($dataPath . $path),
81-
"FileSize" => filesize($dataPath . $path),
81+
"MimeType" => mime_content_type($filePath),
82+
"FileSize" => filesize($filePath),
8283
"StorageKey" => $uploadToken['StorageKey'],
8384
"Time" => $uploadToken['Time'],
8485
"AuthToken" => $uploadToken['AuthToken'],

app/Commands/WikiImportCommand.php

+72-53
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,12 @@ private function handleConfluenceApi(): int
148148

149149
private function handleConfluenceHtml(): int
150150
{
151-
$htmlDir = $this->unzipConfluenceHtml();
152-
$filePath = $htmlDir . 'index.html';
151+
$path = $this->unzipConfluenceHtml();
152+
if (str_ends_with($path, '.html')) {
153+
return $this->uploadConfluencePage($path);
154+
}
155+
$htmlDir = $path;
156+
$filePath = $htmlDir . DIRECTORY_SEPARATOR . 'index.html';
153157
if (!file_exists($filePath)) {
154158
$message = "文件不存在:$filePath";
155159
$this->error($message);
@@ -188,55 +192,75 @@ private function handleConfluenceHtml(): int
188192
return 0;
189193
}
190194

191-
private function uploadConfluencePages(string $dataPath, array $tree, array $titles, int $parentId = 0): void
195+
private function uploadConfluencePages(string $htmlDir, array $tree, array $titles, int $parentId = 0): void
192196
{
193197
foreach ($tree as $page => $subPages) {
194198
$title = $titles[$page];
195-
$this->info('标题:' . $title);
196-
try {
197-
$markdown = $this->confluence->htmlFile2Markdown($dataPath . $page);
198-
} catch (FileNotFoundException $e) {
199-
$this->error('页面不存在:' . $dataPath . $page);
200-
continue;
199+
$wikiId = $this->uploadConfluencePage($htmlDir . DIRECTORY_SEPARATOR . $page, $title, $parentId);
200+
if ($wikiId && !empty($subPages)) {
201+
$this->info('发现 ' . count($subPages) . ' 个子页面');
202+
// TODO tests
203+
$this->uploadConfluencePages($htmlDir, $subPages, $titles, $wikiId);
201204
}
202-
$mdFilename = $this->dealAttachments($dataPath, $page, $markdown);
203-
$zipFilePath = $this->codingWiki->createMarkdownZip($markdown, $dataPath, $mdFilename, $title);
204-
$result = $this->codingWiki->createWikiByUploadZip(
205+
}
206+
}
207+
208+
private function uploadConfluencePage(string $filePath, string $title = '', int $parentId = 0): int
209+
{
210+
try {
211+
$markdown = $this->confluence->htmlFile2Markdown($filePath);
212+
} catch (FileNotFoundException $e) {
213+
$message = '页面不存在:' . $filePath;
214+
$this->error($message);
215+
$this->errors[] = $message;
216+
return false;
217+
}
218+
libxml_use_internal_errors(true);
219+
$this->document->loadHTMLFile($filePath);
220+
if (empty($title)) {
221+
$title = $this->document->getElementsByTagName('title')[0]->nodeValue;
222+
}
223+
$this->info('标题:' . $title);
224+
225+
$htmlDir = dirname($filePath);
226+
$page = basename($filePath);
227+
$markdown = $this->dealAttachments($filePath, $markdown);
228+
$mdFilename = substr($page, 0, -5) . '.md';
229+
if ($this->option('save-markdown')) {
230+
file_put_contents($htmlDir . DIRECTORY_SEPARATOR . $mdFilename, $markdown . "\n");
231+
}
232+
$zipFilePath = $this->codingWiki->createMarkdownZip($markdown, $htmlDir, $mdFilename, $title);
233+
$result = $this->codingWiki->createWikiByUploadZip(
234+
$this->codingToken,
235+
$this->codingProjectUri,
236+
$zipFilePath,
237+
$parentId,
238+
);
239+
$this->info('上传成功,正在处理,任务 ID:' . $result['JobId']);
240+
$wikiId = null;
241+
try {
242+
$jobStatus = $this->codingWiki->getImportJobStatusWithRetry(
205243
$this->codingToken,
206244
$this->codingProjectUri,
207-
$zipFilePath,
208-
$parentId,
245+
$result['JobId']
209246
);
210-
$this->info('上传成功,正在处理,任务 ID:' . $result['JobId']);
211-
$wikiId = null;
212-
try {
213-
$jobStatus = $this->codingWiki->getImportJobStatusWithRetry(
214-
$this->codingToken,
215-
$this->codingProjectUri,
216-
$result['JobId']
217-
);
218-
} catch (Exception $e) {
219-
$message = '错误:导入失败,跳过 ' . $title . ' ' . $page;
220-
$this->error($message);
221-
$this->errors[] = $message;
222-
continue;
223-
}
224-
if ($jobStatus['Status'] == 'success') {
225-
$wikiId = intval($jobStatus['Iids'][0]);
226-
}
227-
if (empty($wikiId)) {
228-
$message = '错误:导入失败,跳过 ' . $title . ' ' . $page;
229-
$this->error($message);
230-
$this->errors[] = $message;
231-
continue;
232-
}
233-
$this->codingWiki->updateTitle($this->codingToken, $this->codingProjectUri, $wikiId, $title);
234-
if (!empty($subPages)) {
235-
$this->info('发现 ' . count($subPages) . ' 个子页面');
236-
// TODO tests
237-
$this->uploadConfluencePages($dataPath, $subPages, $titles, $wikiId);
238-
}
247+
} catch (Exception $e) {
248+
$message = '错误:导入失败,跳过 ' . $title . ' ' . $page;
249+
$this->error($message);
250+
$this->errors[] = $message;
251+
return false;
252+
}
253+
if ($jobStatus['Status'] == 'success') {
254+
$wikiId = intval($jobStatus['Iids'][0]);
239255
}
256+
if (empty($wikiId)) {
257+
$message = '错误:导入失败,跳过 ' . $title . ' ' . $page;
258+
$this->error($message);
259+
$this->errors[] = $message;
260+
return false;
261+
}
262+
$this->codingWiki->updateTitle($this->codingToken, $this->codingProjectUri, $wikiId, $title);
263+
return $wikiId;
240264
}
241265

242266
private function unzipConfluenceHtml(): string
@@ -263,16 +287,16 @@ private function unzipConfluenceHtml(): string
263287
$zip->close();
264288
return $tmpDir . '/' . scandir($tmpDir, 1)[0] . '/';
265289
}
266-
return str_ends_with($dataPath, '/index.html') ? substr($dataPath, 0, -10) : Str::finish($dataPath, '/');
290+
return rtrim($dataPath, '/');
267291
}
268292

269-
private function dealAttachments(string $dataPath, string $page, string $markdown): string
293+
private function dealAttachments(string $filePath, string $markdown): string
270294
{
271-
$attachments = $this->confluence->parseAttachments($dataPath . $page, $markdown);
295+
$attachments = $this->confluence->parseAttachments($filePath, $markdown);
272296
$codingAttachments = $this->codingDisk->uploadAttachments(
273297
$this->codingToken,
274298
$this->codingProjectUri,
275-
$dataPath,
299+
dirname($filePath),
276300
$attachments
277301
);
278302
foreach ($codingAttachments as $attachmentPath => $codingAttachment) {
@@ -282,11 +306,6 @@ private function dealAttachments(string $dataPath, string $page, string $markdow
282306
$this->errors[] = $message;
283307
}
284308
}
285-
$markdown = $this->codingWiki->replaceAttachments($markdown, $codingAttachments);
286-
$mdFilename = substr($page, 0, -5) . '.md';
287-
if ($this->option('save-markdown')) {
288-
file_put_contents($dataPath . $mdFilename, $markdown . "\n");
289-
}
290-
return $mdFilename;
309+
return $this->codingWiki->replaceAttachments($markdown, $codingAttachments);
291310
}
292311
}

app/Commands/WikiUploadCommand.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class WikiUploadCommand extends Command
3030
*
3131
* @var string
3232
*/
33-
protected $description = '上传 Zip 导入 Wiki';
33+
protected $description = '上传 Markdown 和图片的 Zip 导入 Wiki';
3434

3535
/**
3636
* Execute the console command.

tests/Feature/WikiImportCommandTest.php

+39-9
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ public function testHandleConfluenceHtmlFileNotExist()
152152
->expectsQuestion('数据来源?', 'Confluence')
153153
->expectsQuestion('数据类型?', 'HTML')
154154
->expectsQuestion('空间导出的 HTML zip 文件路径', '/dev/null/index.html')
155-
->expectsOutput('文件不存在:/dev/null/index.html')
155+
->expectsOutput('页面不存在:/dev/null/index.html')
156156
->assertExitCode(1);
157157
}
158158

@@ -188,7 +188,6 @@ public function testHandleConfluenceHtmlSuccess()
188188
->expectsOutput('空间标识:space1')
189189
->expectsOutput('发现 3 个一级页面')
190190
->expectsOutput("开始导入 CODING:")
191-
->expectsOutput('标题:Not Found')
192191
->expectsOutput('页面不存在:' . $this->dataDir . 'confluence/space1/not-found.html')
193192
->expectsOutput('标题:Image Demo')
194193
->expectsOutput('上传成功,正在处理,任务 ID:a12353fa-f45b-4af2-83db-666bf9f66615')
@@ -199,13 +198,15 @@ public function testHandleConfluenceHtmlSuccess()
199198
->expectsOutput('上传成功,正在处理,任务 ID:a12353fa-f45b-4af2-83db-666bf9f66615')
200199
->expectsOutput('标题:Text Demo')
201200
->expectsOutput('上传成功,正在处理,任务 ID:a12353fa-f45b-4af2-83db-666bf9f66615')
202-
->assertExitCode(0);
203-
$this->assertFileExists($this->dataDir . 'confluence/space1/65591.md');
204-
$this->assertFileExists($this->dataDir . 'confluence/space1/attachment-demo_65615.md');
205-
$this->assertFileExists($this->dataDir . 'confluence/space1/text-demo_65601.md');
206-
unlink($this->dataDir . 'confluence/space1/65591.md');
207-
unlink($this->dataDir . 'confluence/space1/attachment-demo_65615.md');
208-
unlink($this->dataDir . 'confluence/space1/text-demo_65601.md');
201+
->expectsOutput('报错信息汇总:')
202+
->expectsOutput('页面不存在:' . $this->dataDir . 'confluence/space1/not-found.html')
203+
->assertExitCode(1);
204+
$this->assertFileExists($this->dataDir . '/confluence/space1/65591.md');
205+
$this->assertFileExists($this->dataDir . '/confluence/space1/attachment-demo_65615.md');
206+
$this->assertFileExists($this->dataDir . '/confluence/space1/text-demo_65601.md');
207+
unlink($this->dataDir . '/confluence/space1/65591.md');
208+
unlink($this->dataDir . '/confluence/space1/attachment-demo_65615.md');
209+
unlink($this->dataDir . '/confluence/space1/text-demo_65601.md');
209210
}
210211

211212
public function testAskNothing()
@@ -269,4 +270,33 @@ public function testHandleConfluenceHtmlZipSuccess()
269270
->expectsOutput('上传成功,正在处理,任务 ID:a12353fa-f45b-4af2-83db-666bf9f66615')
270271
->assertExitCode(0);
271272
}
273+
274+
public function testHandleConfluenceSingleHtmlSuccess()
275+
{
276+
$this->setConfig();
277+
278+
// 注意:不能使用 partialMock
279+
// https://laracasts.com/discuss/channels/testing/this-partialmock-doesnt-call-the-constructor
280+
$mock = \Mockery::mock(Wiki::class, [])->makePartial();
281+
$this->instance(Wiki::class, $mock);
282+
283+
$mock->shouldReceive('createWikiByUploadZip')->times(1)->andReturn(json_decode(
284+
file_get_contents($this->dataDir . 'coding/' . 'CreateWikiByZipResponse.json'),
285+
true
286+
)['Response']);
287+
$mock->shouldReceive('getImportJobStatus')->times(1)->andReturn(json_decode(
288+
file_get_contents($this->dataDir . 'coding/' . 'DescribeImportJobStatusResponse.json'),
289+
true
290+
)['Response']['Data']);
291+
$mock->shouldReceive('updateTitle')->times(1)->andReturn(true);
292+
293+
294+
$this->artisan('wiki:import')
295+
->expectsQuestion('数据来源?', 'Confluence')
296+
->expectsQuestion('数据类型?', 'HTML')
297+
->expectsQuestion('空间导出的 HTML zip 文件路径', $this->dataDir . 'confluence/space1/image-demo_65619.html')
298+
->expectsOutput('标题:空间 1 : Image Demo')
299+
->expectsOutput('上传成功,正在处理,任务 ID:a12353fa-f45b-4af2-83db-666bf9f66615')
300+
->assertExitCode(0);
301+
}
272302
}

0 commit comments

Comments
 (0)