init
This commit is contained in:
179
app/Http/Controllers/Admin/UploadController.php
Normal file
179
app/Http/Controllers/Admin/UploadController.php
Normal file
@@ -0,0 +1,179 @@
|
||||
<?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);
|
||||
|
||||
$mainUrl = Storage::disk('public')->url($mainPath);
|
||||
$thumbUrl = Storage::disk('public')->url($thumbPath);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'url' => $mainUrl,
|
||||
'thumb_url' => $thumbUrl,
|
||||
'filename' => basename($mainPath),
|
||||
'markdown' => '',
|
||||
]);
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user