Skip to content

Commit

Permalink
add batch export for project
Browse files Browse the repository at this point in the history
  • Loading branch information
mylxsw committed May 12, 2019
1 parent b43cb9d commit 87e8ff5
Show file tree
Hide file tree
Showing 12 changed files with 515 additions and 8 deletions.
273 changes: 273 additions & 0 deletions app/Http/Controllers/BatchExportController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
<?php
/**
* wizard
*
* @link https://www.yunsom.com/
* @copyright 管宜尧 <[email protected]>
*/

namespace App\Http\Controllers;


use App\Policies\ProjectPolicy;
use App\Repositories\Document;
use App\Repositories\Project;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Request;
use Mpdf\Mpdf;
use Log;
use SoapBox\Formatter\Formatter;
use ZipStream\Option\Archive;
use ZipStream\ZipStream;

class BatchExportController extends Controller
{
/**
* 最大处理超时时间
*/
const TIMEOUT = 320;

/**
* 批量导出文档
*
* @param Request $request
* @param $project_id
*
* @throws \Illuminate\Validation\ValidationException
* @throws \Mpdf\MpdfException
* @throws \ZipStream\Exception\OverflowException
*/
public function batchExport(Request $request, $project_id)
{
$this->canExport($project_id);

$this->validate(
$request,
[
'pid' => 'integer',
'type' => 'required|in:pdf,raw'
]
);

$pid = (int)$request->input('pid', 0);
$type = $request->input('type');

/** @var Project $project */
$project = Project::where('id', $project_id)->firstOrFail();

/** @var Collection $documents */
$documents = $project->pages;
$navigators = navigatorSort(navigator($project_id, 0));

if ($pid !== 0) {
$navigators = $this->filterNavigators($navigators, function (array $nav) use ($pid) {
return (int)$nav['id'] === $pid;
});
}

switch ($type) {
case 'pdf':
$this->exportPDF($navigators, $project, $documents);
break;
case 'raw':
$this->exportRaw($navigators, $project, $documents);
break;
}
}

/**
* 过滤要导出的文档
*
* @param array $navigators
* @param \Closure $filter
*
* @return array|mixed
*/
private function filterNavigators(array $navigators, \Closure $filter)
{
foreach ($navigators as $nav) {
if ($filter($nav)) {
return $nav['nodes'] ?? [];
}
}

return [];
}

/**
* Export to zip archive
*
* @param array $navigators
* @param Project $project
* @param Collection $documents
*
* @throws \ZipStream\Exception\OverflowException
*/
private function exportRaw(array $navigators, Project $project, Collection $documents)
{
set_time_limit(self::TIMEOUT);

$options = new Archive();
$options->setSendHttpHeaders(true);

$zip = new ZipStream("{$project->name}.zip", $options);
$this->traverseNavigators(
$navigators,
function ($id, array $parents) use ($documents, $zip) {
/** @var Document $doc */
$doc = $documents->where('id', $id)->first();

switch ($doc->type) {
case Document::TYPE_DOC:
$ext = 'md';
$content = $doc->content;
break;
case Document::TYPE_SWAGGER:
$ext = 'yml';
if (isJson($doc->content)) {
$formatter = Formatter::make($doc->content, Formatter::JSON);
$content = $formatter->toYaml();
} else {
$content = $doc->content;
}
break;
default:
$ext = 'txt';
$content = $doc->content;
}

$path = collect($parents)->implode('name', '/');
$filename = "{$path}/{$doc->title}.{$ext}";

$fp = fopen('php://memory', 'r+');
fwrite($fp, $content);
rewind($fp);
$zip->addFileFromStream($filename, $fp);
},
[]
);

$zip->finish();
}

/**
* Export to pdf document
*
* @param array $navigators
* @param Project $project
* @param Collection $documents
*
* @throws \Mpdf\MpdfException
*/
private function exportPDF(array $navigators, Project $project, Collection $documents)
{
set_time_limit(self::TIMEOUT);

$mpdf = new Mpdf([
'mode' => 'utf-8',
'tempDir' => sys_get_temp_dir(),
'defaultfooterline' => false,
]);

$mpdf->allow_charset_conversion = true;
$mpdf->useAdobeCJK = true;
$mpdf->autoLangToFont = true;
$mpdf->autoScriptToLang = true;
$mpdf->author = $author ?? \Auth::user()->name ?? 'wizard';

$mpdf->SetFooter('{PAGENO} / {nbpg}');
$mpdf->SetTitle($project->name);

$header = '<link href="/assets/css/normalize.css" rel="stylesheet">';
$header .= '<link href="/assets/vendor/editor-md/css/editormd.preview.css" rel="stylesheet"/>';
$header .= '<link href="/assets/vendor/markdown-body.css" rel="stylesheet">';
$header .= '<link href="/assets/css/style.css" rel="stylesheet">';
$header .= '<link href="/assets/css/pdf.css" rel="stylesheet">';
$mpdf->WriteHTML($header);

$pageNo = 1;
$this->traverseNavigators($navigators,
function ($id, array $parents) use ($documents, $mpdf, &$pageNo) {
if ($pageNo > 1) {
$mpdf->AddPage();
}

/** @var Document $doc */
$doc = $documents->where('id', $id)->first();

$title = "* {$doc->title}";

if ($doc->type != Document::TYPE_DOC) {
$raw = "# {$title}\n\n暂不支持该类型的文档。";
} else {
$raw = "# {$title}\n\n" . $doc->content;
}

$html = (new \Parsedown())->text($raw);
$html =
"<div class='markdown-body wz-markdown-style-fix wz-pdf-content'>{$html}</div>";

$mpdf->Bookmark($doc->title, count($parents));
try {
$mpdf->WriteHTML($html);
} catch (\Exception $ex) {
Log::error('html_to_pdf_failed', [
'error' => $ex->getMessage(),
'code' => $ex->getCode(),
'doc' => [
'id' => $doc->id,
'title' => $doc->title,
'content' => $html,
]
]);

$mpdf->WriteHTML('<p class="pdf-error">部分文档生成失败</p>');
}

$pageNo++;
}, []);

$mpdf->Output();
}

/**
* 遍历所有目录
*
* @param array $navigators
* @param \Closure $callback
* @param array $parents
*/
private function traverseNavigators(array $navigators, \Closure $callback, array $parents = [])
{
foreach ($navigators as $nav) {
$callback($nav['id'], $parents);

if (!empty($nav['nodes'])) {
array_push($parents, ['id' => $nav['id'], 'name' => $nav['name']]);
$this->traverseNavigators($nav['nodes'], $callback, $parents);
array_pop($parents);
}
}
}


/**
* 检查是否用户有导出权限
*
* @param int $projectId
*
* @return Project
*/
private function canExport($projectId)
{
/** @var Project $project */
$project = Project::findOrFail($projectId);

$policy = new ProjectPolicy();
if (!$policy->view(\Auth::user(), $project)) {
abort(404);
}

return $project;
}
}
4 changes: 4 additions & 0 deletions app/Http/Controllers/DocumentController.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class DocumentController extends Controller
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
* @throws \Illuminate\Validation\ValidationException
*/
public function newPage(Request $request, $id)
{
Expand Down Expand Up @@ -97,6 +98,7 @@ public function editPage($id, $page_id)
*
* @return array
* @throws \Illuminate\Auth\Access\AuthorizationException
* @throws \Illuminate\Validation\ValidationException
*/
public function newPageHandle(Request $request, $id)
{
Expand Down Expand Up @@ -170,6 +172,7 @@ public function newPageHandle(Request $request, $id)
*
* @return array|\Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
* @throws \Illuminate\Validation\ValidationException
*/
public function editPageHandle(Request $request, $id, $page_id)
{
Expand Down Expand Up @@ -264,6 +267,7 @@ public function editPageHandle(Request $request, $id, $page_id)
* @param $page_id
*
* @return array
* @throws \Illuminate\Validation\ValidationException
*/
public function checkPageExpired(Request $request, $id, $page_id)
{
Expand Down
18 changes: 14 additions & 4 deletions app/Http/Controllers/ExportController.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Log;
use Mpdf\Mpdf;

class ExportController extends Controller
Expand Down Expand Up @@ -43,8 +44,6 @@ public function pdf(Request $request, $type)
$title = $request->input('title');
$author = $request->input('author');

// $html = (new \Parsedown())->text("# {$doc->title}\n\n" . $doc->content);

$mpdf = new Mpdf([
'mode' => 'utf-8',
'tempDir' => sys_get_temp_dir()
Expand Down Expand Up @@ -74,10 +73,21 @@ public function pdf(Request $request, $type)
$header .= '<link href="/assets/css/pdf.css" rel="stylesheet">';
$mpdf->WriteHTML($header);


$html = "<div class='markdown-body wz-markdown-style-fix wz-pdf-content'>{$content}</div>";
$mpdf->Bookmark($title, 0);
$mpdf->WriteHTML($html);
try {
$mpdf->WriteHTML($html);
} catch (\Exception $ex) {
Log::error('html_to_pdf_failed', [
'error' => $ex->getMessage(),
'code' => $ex->getCode(),
'doc' => [
'content' => $html,
]
]);

$mpdf->WriteHTML('<p class="pdf-error">部分文档生成失败</p>');
}

$mpdf->Output();
}
Expand Down
Loading

0 comments on commit 87e8ff5

Please sign in to comment.