PdfStorageService.php 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. <?php
  2. namespace App\Services;
  3. use Illuminate\Support\Facades\Http;
  4. use Illuminate\Support\Facades\Log;
  5. use Illuminate\Support\Facades\Storage;
  6. use Illuminate\Support\Str;
  7. class PdfStorageService
  8. {
  9. /**
  10. * 保存二进制 PDF,返回可访问 URL
  11. */
  12. public function put(string $path, string $binary): ?string
  13. {
  14. $driver = config('services.pdf_storage.driver', env('PDF_STORAGE_DRIVER', 'local'));
  15. return match ($driver) {
  16. 'upyun' => $this->putUpyun($path, $binary),
  17. 'chunsun' => $this->putChunsun($path, $binary),
  18. default => $this->putLocal($path, $binary),
  19. };
  20. }
  21. private function putLocal(string $path, string $binary): ?string
  22. {
  23. $disk = 'public';
  24. // 确保目录存在
  25. $dir = Str::beforeLast($path, '/');
  26. if ($dir && $dir !== $path) {
  27. Storage::disk($disk)->makeDirectory($dir);
  28. }
  29. $written = Storage::disk($disk)->put($path, $binary);
  30. if (!$written) {
  31. Log::error('PdfStorageService: 本地存储失败', ['path' => $path, 'disk' => $disk]);
  32. return null;
  33. }
  34. return url(Storage::url($path));
  35. }
  36. /**
  37. * 又拍云存储(REST API),需要以下 env:
  38. * PDF_STORAGE_DRIVER=upyun
  39. * UPYUN_BUCKET=xxx
  40. * UPYUN_OPERATOR=xxx
  41. * UPYUN_PASSWORD=xxx
  42. * UPYUN_DOMAIN=https://your.cdn.domain 或 https://v0.api.upyun.com
  43. */
  44. private function putUpyun(string $path, string $binary): ?string
  45. {
  46. $bucket = env('UPYUN_BUCKET');
  47. $operator = env('UPYUN_OPERATOR');
  48. $password = env('UPYUN_PASSWORD');
  49. $domain = rtrim(env('UPYUN_DOMAIN', 'https://v0.api.upyun.com'), '/');
  50. if (!$bucket || !$operator || !$password) {
  51. Log::error('PdfStorageService: 又拍云配置缺失', compact('bucket', 'operator'));
  52. return null;
  53. }
  54. $target = "{$domain}/{$bucket}/" . ltrim($path, '/');
  55. try {
  56. $response = Http::withHeaders([
  57. 'Content-Type' => 'application/pdf',
  58. 'Content-Length' => strlen($binary),
  59. ])
  60. ->withBasicAuth($operator, $password)
  61. ->timeout(30)
  62. ->withBody($binary, 'application/pdf')
  63. ->put($target);
  64. if (!$response->successful()) {
  65. Log::error('PdfStorageService: 又拍云上传失败', [
  66. 'status' => $response->status(),
  67. 'body' => $response->body(),
  68. 'target' => $target,
  69. ]);
  70. return null;
  71. }
  72. // 生成访问 URL,优先使用 CDN 域名
  73. $cdn = env('UPYUN_CDN_DOMAIN');
  74. if ($cdn) {
  75. return rtrim($cdn, '/') . '/' . ltrim($path, '/');
  76. }
  77. return $target;
  78. } catch (\Throwable $e) {
  79. Log::error('PdfStorageService: 又拍云异常', [
  80. 'error' => $e->getMessage(),
  81. 'target' => $target ?? null,
  82. ]);
  83. return null;
  84. }
  85. }
  86. /**
  87. * Chunsun云存储上传(POST multipart/form-data)
  88. * 环境变量:
  89. * PDF_STORAGE_DRIVER=chunsun
  90. * CHUNSUN_UPLOAD_URL=https://crmapi.dcjxb.yunzhixue.cn
  91. */
  92. private function putChunsun(string $path, string $binary): ?string
  93. {
  94. $uploadUrl = env('CHUNSUN_UPLOAD_URL', 'https://crmapi.dcjxb.yunzhixue.cn');
  95. try {
  96. // 创建临时文件
  97. $tempFile = tempnam(sys_get_temp_dir(), 'chunsun_pdf_') . '.pdf';
  98. file_put_contents($tempFile, $binary);
  99. // 发送POST请求上传文件
  100. $response = Http::timeout(60)
  101. ->attach('file', file_get_contents($tempFile), basename($tempFile) . '.pdf')
  102. ->post($uploadUrl);
  103. // 清理临时文件
  104. @unlink($tempFile);
  105. if (!$response->successful()) {
  106. Log::error('PdfStorageService: Chunsun上传失败', [
  107. 'status' => $response->status(),
  108. 'body' => $response->body(),
  109. 'url' => $uploadUrl,
  110. ]);
  111. return null;
  112. }
  113. $data = $response->json();
  114. // 检查响应格式
  115. if (!isset($data['code']) || $data['code'] !== 200) {
  116. Log::error('PdfStorageService: Chunsun上传失败,响应格式错误', [
  117. 'response' => $data,
  118. ]);
  119. return null;
  120. }
  121. // 检查返回数据
  122. if (!isset($data['data']['url'])) {
  123. Log::error('PdfStorageService: Chunsun上传失败,未返回URL', [
  124. 'response' => $data,
  125. ]);
  126. return null;
  127. }
  128. $uploadedUrl = $data['data']['url'];
  129. $uploadedName = $data['data']['name'] ?? null;
  130. Log::info('PdfStorageService: Chunsun上传成功', [
  131. 'path' => $path,
  132. 'url' => $uploadedUrl,
  133. 'name' => $uploadedName,
  134. ]);
  135. return $uploadedUrl;
  136. } catch (\Throwable $e) {
  137. Log::error('PdfStorageService: Chunsun上传异常', [
  138. 'error' => $e->getMessage(),
  139. 'path' => $path,
  140. 'trace' => $e->getTraceAsString(),
  141. ]);
  142. return null;
  143. }
  144. }
  145. }