PdfStorageService.php 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  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/file/upload
  91. */
  92. private function putChunsun(string $path, string $binary): ?string
  93. {
  94. Log::info('PdfStorageService: Chunsun上传开始', [
  95. 'path' => $path,
  96. ]);
  97. $uploadUrl = env('CHUNSUN_UPLOAD_URL', 'https://crmapi.dcjxb.yunzhixue.cn/file/upload');
  98. try {
  99. // 获取扩展名并决定 MIME
  100. $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION) ?: 'jpg');
  101. $mimeMap = [
  102. 'pdf' => 'application/pdf',
  103. 'md' => 'text/markdown',
  104. 'markdown' => 'text/markdown',
  105. 'txt' => 'text/plain',
  106. 'png' => 'image/png',
  107. 'jpg' => 'image/jpeg',
  108. 'jpeg' => 'image/jpeg',
  109. ];
  110. $mimeType = $mimeMap[$extension] ?? 'application/octet-stream';
  111. $baseName = basename($path);
  112. // 发送POST请求上传内容
  113. $response = Http::timeout(60)
  114. ->attach('file', $binary, $baseName, ['Content-Type' => $mimeType])
  115. ->post($uploadUrl);
  116. if (!$response->successful()) {
  117. Log::error('PdfStorageService: Chunsun上传失败', [
  118. 'status' => $response->status(),
  119. 'body' => $response->body(),
  120. 'url' => $uploadUrl,
  121. ]);
  122. return null;
  123. }
  124. $data = $response->json();
  125. // 检查响应格式
  126. if (!isset($data['code']) || $data['code'] !== 200) {
  127. Log::error('PdfStorageService: Chunsun上传失败,响应格式错误', [
  128. 'response' => $data,
  129. ]);
  130. return null;
  131. }
  132. // 检查返回数据
  133. if (!isset($data['data']['url'])) {
  134. Log::error('PdfStorageService: Chunsun上传失败,未返回URL', [
  135. 'response' => $data,
  136. ]);
  137. return null;
  138. }
  139. $uploadedUrl = $data['data']['url'];
  140. Log::info('PdfStorageService: Chunsun上传成功', [
  141. 'path' => $path,
  142. 'url' => $uploadedUrl,
  143. ]);
  144. return $uploadedUrl;
  145. } catch (\Throwable $e) {
  146. Log::error('PdfStorageService: Chunsun上传异常', [
  147. 'error' => $e->getMessage(),
  148. 'path' => $path,
  149. ]);
  150. return null;
  151. }
  152. }
  153. }