Files
ai-web/app/Http/Controllers/Admin/UploadController.php

180 lines
5.8 KiB
PHP
Raw Normal View History

2026-02-11 17:28:36 +08:00
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use GdImage;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use RuntimeException;
use Throwable;
class UploadController extends Controller
{
private const MAX_FILE_KB = 6144;
private const MAX_MAIN_WIDTH = 2400;
private const THUMB_WIDTH = 320;
private const MAIN_WEBP_QUALITY = 86;
private const THUMB_WEBP_QUALITY = 80;
public function markdownImage(Request $request): JsonResponse
{
$validated = $request->validate([
'image' => ['required', 'file', 'image', 'mimes:jpg,jpeg,png,webp,gif', 'max:'.self::MAX_FILE_KB],
], [
'image.required' => '请选择要上传的图片。',
'image.image' => '仅支持图片文件上传。',
'image.mimes' => '图片格式仅支持 jpg、jpeg、png、webp、gif。',
'image.max' => '图片大小不能超过 6MB。',
]);
if (! function_exists('imagecreatefromstring') || ! function_exists('imagewebp')) {
return response()->json([
'success' => false,
'message' => '服务器不支持图片处理能力GD/WebP。',
], 500);
}
/** @var UploadedFile $file */
$file = $validated['image'];
$sourceImage = null;
$mainImage = null;
$thumbImage = null;
try {
[$sourceImage, $sourceWidth, $sourceHeight] = $this->createImageFromUpload($file);
$mainWidth = min(self::MAX_MAIN_WIDTH, $sourceWidth);
$mainImage = $this->resizeImage($sourceImage, $sourceWidth, $sourceHeight, $mainWidth);
$thumbWidth = min(self::THUMB_WIDTH, imagesx($mainImage));
$thumbImage = $this->resizeImage($mainImage, imagesx($mainImage), imagesy($mainImage), $thumbWidth);
$mainBinary = $this->encodeWebp($mainImage, self::MAIN_WEBP_QUALITY);
$thumbBinary = $this->encodeWebp($thumbImage, self::THUMB_WEBP_QUALITY);
$subDir = now()->format('Y/m/d');
$baseName = now()->format('YmdHis').'-'.Str::lower(Str::random(8));
$mainPath = "markdown-images/{$subDir}/{$baseName}.webp";
$thumbPath = "markdown-images/{$subDir}/{$baseName}_thumb.webp";
Storage::disk('public')->put($mainPath, $mainBinary);
Storage::disk('public')->put($thumbPath, $thumbBinary);
2026-02-12 13:06:12 +08:00
$mainUrl = '/storage/'.ltrim($mainPath, '/');
$thumbUrl = '/storage/'.ltrim($thumbPath, '/');
2026-02-11 17:28:36 +08:00
return response()->json([
'success' => true,
'url' => $mainUrl,
'thumb_url' => $thumbUrl,
'filename' => basename($mainPath),
'markdown' => '![]('.$mainUrl.')',
]);
} catch (Throwable $exception) {
report($exception);
return response()->json([
'success' => false,
'message' => '图片上传失败,请稍后重试。',
], 422);
} finally {
if ($sourceImage instanceof GdImage) {
imagedestroy($sourceImage);
}
if ($mainImage instanceof GdImage) {
imagedestroy($mainImage);
}
if ($thumbImage instanceof GdImage) {
imagedestroy($thumbImage);
}
}
}
/**
* @return array{0:GdImage,1:int,2:int}
*/
private function createImageFromUpload(UploadedFile $file): array
{
$path = $file->getRealPath();
if ($path === false || $path === '') {
throw new RuntimeException('上传临时文件不可用。');
}
$binary = file_get_contents($path);
if ($binary === false) {
throw new RuntimeException('读取上传文件失败。');
}
$image = @imagecreatefromstring($binary);
if (! $image instanceof GdImage) {
throw new RuntimeException('无法解析图片内容。');
}
if (! imageistruecolor($image)) {
imagepalettetotruecolor($image);
}
$width = imagesx($image);
$height = imagesy($image);
if ($width < 1 || $height < 1) {
imagedestroy($image);
throw new RuntimeException('图片尺寸无效。');
}
return [$image, $width, $height];
}
private function resizeImage(GdImage $source, int $sourceWidth, int $sourceHeight, int $targetWidth): GdImage
{
$targetWidth = max(1, $targetWidth);
if ($sourceWidth === $targetWidth) {
$targetHeight = $sourceHeight;
} else {
$targetHeight = max(1, (int) round($sourceHeight * ($targetWidth / $sourceWidth)));
}
$canvas = imagecreatetruecolor($targetWidth, $targetHeight);
if (! $canvas instanceof GdImage) {
throw new RuntimeException('创建图片画布失败。');
}
$background = imagecolorallocate($canvas, 255, 255, 255);
imagefill($canvas, 0, 0, $background);
if (! imagecopyresampled($canvas, $source, 0, 0, 0, 0, $targetWidth, $targetHeight, $sourceWidth, $sourceHeight)) {
imagedestroy($canvas);
throw new RuntimeException('图片缩放失败。');
}
return $canvas;
}
private function encodeWebp(GdImage $image, int $quality): string
{
ob_start();
$saved = imagewebp($image, null, $quality);
$binary = ob_get_clean();
if (! $saved || ! is_string($binary) || $binary === '') {
throw new RuntimeException('WebP 编码失败。');
}
return $binary;
}
}