laravel10 dcat admin2 上传文件到oss
[TOC]
1. 安装 OSS SDK V2
安装扩展包
composer require alibabacloud/oss-v2
如果连接超时,可配置镜像源
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/清除缓存
composer clear-cache
2. 设置配置文件
设置配置文件.env
# 阿里云 OSS 配置
OSS_ACCESS_KEY_ID=your_access_key_id
OSS_ACCESS_KEY_SECRET=your_access_key_secret
OSS_BUCKET=your_bucket_name
OSS_REGION=cn-hangzhou
OSS_ENDPOINT=
OSS_DOMAIN=https://your-bucket.oss-cn-hangzhou.aliyuncs.com
OSS_IS_CNAME=false
OSS_PREFIX=
OSS_USE_INTERNAL=false
3. 配置 config/filesystems.php
'disks' => [
// ... 其他配置
'oss' => [
'driver' => 'oss',
'access_key_id' => env('OSS_ACCESS_KEY_ID'),
'access_key_secret' => env('OSS_ACCESS_KEY_SECRET'),
'bucket' => env('OSS_BUCKET'),
'region' => env('OSS_REGION', 'cn-hangzhou'),
'endpoint' => env('OSS_ENDPOINT', ''),
'is_cname' => env('OSS_IS_CNAME', false),
'domain' => env('OSS_DOMAIN'),
'prefix' => env('OSS_PREFIX', ''),
'use_internal' => env('OSS_USE_INTERNAL', false), // 是否使用内网
'url_expire' => env('OSS_URL_EXPIRE', 3600), // 签名URL过期时间
]
],
4. 配置 OSS 服务类
<?php
namespace App\Services\Tool;
use AlibabaCloud\Oss\V2 as Oss;
use Illuminate\Http\UploadedFile;
class OssService
{
private Oss\Client $client;
private string $bucket;
private string $region;
private ?string $endpoint;
private ?string $domain;
private bool $isCname;
public function __construct()
{
$credentialsProvider = new Oss\Credentials\EnvironmentVariableCredentialsProvider();
// 配置客户端
$cfg = Oss\Config::loadDefault();
$cfg->setCredentialsProvider(credentialsProvider: $credentialsProvider);
$cfg->setRegion(region: env('OSS_REGION', 'cn-hangzhou'));
if ($endpoint = env('OSS_ENDPOINT')) {
$cfg->setEndpoint(endpoint: $endpoint);
}
// 设置是否使用内网
if (env('OSS_USE_INTERNAL', false)) {
$cfg->setUseInternalEndpoint(true);
}
// 设置是否使用 CNAME
if (env('OSS_IS_CNAME', false)) {
$cfg->setUseCname(true);
}
// 初始化客户端
$this->client = new Oss\Client($cfg);
$this->bucket = env('OSS_BUCKET');
$this->region = env('OSS_REGION', 'cn-hangzhou');
$this->endpoint = env('OSS_ENDPOINT');
$this->domain = env('OSS_DOMAIN');
$this->isCname = env('OSS_IS_CNAME', false);
}
/**
* 上传文件
*/
public function uploadFile($file, string $path = null, bool $useOriginalName = false): array
{
try {
if ($file instanceof UploadedFile) {
$extension = $file->getClientOriginalExtension();
if ($useOriginalName) {
$filename = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
$filename = $filename . '_' . time() . '.' . $extension;
} else {
$filename = uniqid() . '_' . time() . '.' . $extension;
}
} else {
$filename = basename($file);
}
$objectKey = trim($path ?? date('Y/m/d'), '/') . '/' . $filename;
$request = new Oss\Models\PutObjectRequest(
bucket: $this->bucket,
key: $objectKey
);
if ($file instanceof UploadedFile) {
$request->body = Oss\Utils::streamFor(fopen($file->getPathname(), 'r'));
$request->contentType = $file->getMimeType();
} else {
if (is_string($file) && file_exists($file)) {
$request->body = Oss\Utils::streamFor(fopen($file, 'r'));
$request->contentType = mime_content_type($file);
} else {
$request->body = Oss\Utils::streamFor($file);
}
}
$result = $this->client->putObject($request);
if ($result->statusCode === 200) {
return [
'success' => true,
'path' => $objectKey,
'url' => $this->getFileUrl($objectKey),
'public_url' => $this->getPublicUrl($objectKey),
'etag' => trim($result->etag, '"'),
];
}
return [
'success' => false,
'message' => '文件上传失败'
];
} catch (\Exception $e) {
return [
'success' => false,
'message' => $e->getMessage()
];
}
}
/**
* 批量上传
*/
public function uploadFiles(array $files, string $path = null): array
{
$results = [];
foreach ($files as $file) {
$results[] = $this->uploadFile($file, $path);
}
return $results;
}
/**
* 删除文件
*/
public function deleteFile(string $objectKey): array
{
try {
$request = new Oss\Models\DeleteObjectRequest(
bucket: $this->bucket,
key: $objectKey
);
$result = $this->client->deleteObject($request);
return [
'success' => true,
'statusCode' => $result->statusCode
];
} catch (\Exception $e) {
return [
'success' => false,
'message' => $e->getMessage()
];
}
}
/**
* 批量删除文件
*/
public function deleteFiles(array $objectKeys): array
{
try {
$objects = [];
foreach ($objectKeys as $key) {
$obj = new Oss\Models\DeleteObject();
$obj->key = $key;
$objects[] = $obj;
}
$delete = new Oss\Models\Delete();
$delete->objects = $objects;
$request = new Oss\Models\DeleteMultipleObjectsRequest(
bucket: $this->bucket,
delete: $delete
);
$result = $this->client->deleteMultipleObjects($request);
return [
'success' => true,
'deleted' => $result->deletedObjects ?? []
];
} catch (\Exception $e) {
return [
'success' => false,
'message' => $e->getMessage()
];
}
}
/**
* 检查文件是否存在
*/
public function fileExists(string $objectKey): bool
{
try {
$request = new Oss\Models\HeadObjectRequest(
bucket: $this->bucket,
key: $objectKey
);
$this->client->headObject($request);
return true;
} catch (\Exception $e) {
return false;
}
}
/**
* 获取文件信息
*/
public function getFileInfo(string $objectKey): ?array
{
try {
$request = new Oss\Models\HeadObjectRequest(
bucket: $this->bucket,
key: $objectKey
);
$result = $this->client->headObject($request);
return [
'size' => $result->contentLength,
'type' => $result->contentType,
'etag' => trim($result->etag, '"'),
'last_modified' => $result->lastModified?->format('Y-m-d H:i:s'),
];
} catch (\Exception $e) {
return null;
}
}
/**
* 复制文件
*/
public function copyFile(string $sourceKey, string $destinationKey): array
{
try {
$request = new Oss\Models\CopyObjectRequest(
bucket: $this->bucket,
key: $destinationKey
);
$request->sourceKey = $sourceKey;
$result = $this->client->copyObject($request);
return [
'success' => true,
'etag' => trim($result->copyObjectResult->etag ?? '', '"')
];
} catch (\Exception $e) {
return [
'success' => false,
'message' => $e->getMessage()
];
}
}
/**
* 获取文件签名URL(带过期时间)
*/
public function getFileUrl(string $objectKey, int $expires = null): string
{
try {
$request = new Oss\Models\GetObjectRequest(
bucket: $this->bucket,
key: $objectKey
);
$expires = $expires ?? (int)env('OSS_URL_EXPIRE', 3600);
return $this->client->signUrl($request, $expires);
} catch (\Exception $e) {
return '';
}
}
/**
* 获取公共访问URL(无签名)
*/
public function getPublicUrl(string $objectKey): string
{
if ($this->domain) {
return rtrim($this->domain, '/') . '/' . ltrim($objectKey, '/');
}
if ($this->isCname && $this->endpoint) {
return 'https://' . rtrim($this->endpoint, '/') . '/' . ltrim($objectKey, '/');
}
return "https://{$this->bucket}.oss-{$this->region}.aliyuncs.com/" . ltrim($objectKey, '/');
}
/**
* 列出文件
*/
public function listFiles(string $prefix = '', int $maxKeys = 100): array
{
try {
$request = new Oss\Models\ListObjectsV2Request(
bucket: $this->bucket
);
$request->prefix = $prefix;
$request->maxKeys = $maxKeys;
$result = $this->client->listObjectsV2($request);
$files = [];
foreach ($result->contents ?? [] as $content) {
$files[] = [
'key' => $content->key,
'size' => $content->size,
'last_modified' => $content->lastModified?->format('Y-m-d H:i:s'),
'etag' => trim($content->etag ?? '', '"'),
'url' => $this->getPublicUrl($content->key),
];
}
return [
'success' => true,
'files' => $files,
'count' => count($files),
];
} catch (\Exception $e) {
return [
'success' => false,
'message' => $e->getMessage()
];
}
}
}
5. 创建 OSS Adapter(Laravel Filesystem 集成)
app/Support/OssAdapter.php:
<?php
namespace App\Support;
use League\Flysystem\Config;
use League\Flysystem\FileAttributes;
use League\Flysystem\FilesystemAdapter;
use League\Flysystem\UnableToReadFile;
use League\Flysystem\UnableToWriteFile;
use League\Flysystem\UnableToDeleteFile;
use League\Flysystem\UnableToCopyFile;
use League\Flysystem\UnableToMoveFile;
use AlibabaCloud\Oss\V2 as Oss;
class OssAdapter implements FilesystemAdapter
{
protected Oss\Client $client;
protected string $bucket;
protected array $config;
public function __construct(Oss\Client $client, string $bucket, array $config = [])
{
$this->client = $client;
$this->bucket = $bucket;
$this->config = $config;
}
public function fileExists(string $path): bool
{
try {
$request = new Oss\Models\HeadObjectRequest(
bucket: $this->bucket,
key: $this->prefixPath($path)
);
$this->client->headObject($request);
return true;
} catch (\Exception $e) {
return false;
}
}
public function directoryExists(string $path): bool
{
return true;
}
public function write(string $path, string $contents, Config $config): void
{
try {
$request = new Oss\Models\PutObjectRequest(
bucket: $this->bucket,
key: $this->prefixPath($path)
);
$request->body = $contents;
if ($mimeType = $config->get('mimetype')) {
$request->contentType = $mimeType;
}
$this->client->putObject($request);
} catch (\Exception $e) {
throw UnableToWriteFile::atLocation($path, $e->getMessage());
}
}
public function writeStream(string $path, $contents, Config $config): void
{
try {
$request = new Oss\Models\PutObjectRequest(
bucket: $this->bucket,
key: $this->prefixPath($path)
);
$request->body = Oss\Utils::streamFor($contents);
if ($mimeType = $config->get('mimetype')) {
$request->contentType = $mimeType;
}
$this->client->putObject($request);
} catch (\Exception $e) {
throw UnableToWriteFile::atLocation($path, $e->getMessage());
}
}
public function read(string $path): string
{
try {
$request = new Oss\Models\GetObjectRequest(
bucket: $this->bucket,
key: $this->prefixPath($path)
);
$result = $this->client->getObject($request);
return $result->body->getContents();
} catch (\Exception $e) {
throw UnableToReadFile::fromLocation($path, $e->getMessage());
}
}
public function readStream(string $path)
{
try {
$request = new Oss\Models\GetObjectRequest(
bucket: $this->bucket,
key: $this->prefixPath($path)
);
$result = $this->client->getObject($request);
return $result->body->detach();
} catch (\Exception $e) {
throw UnableToReadFile::fromLocation($path, $e->getMessage());
}
}
public function delete(string $path): void
{
try {
$request = new Oss\Models\DeleteObjectRequest(
bucket: $this->bucket,
key: $this->prefixPath($path)
);
$this->client->deleteObject($request);
} catch (\Exception $e) {
throw UnableToDeleteFile::atLocation($path, $e->getMessage());
}
}
public function deleteDirectory(string $path): void
{
$prefix = rtrim($this->prefixPath($path), '/') . '/';
try {
$continuationToken = null;
do {
$request = new Oss\Models\ListObjectsV2Request(
bucket: $this->bucket
);
$request->prefix = $prefix;
$request->maxKeys = 1000;
if ($continuationToken) {
$request->continuationToken = $continuationToken;
}
$result = $this->client->listObjectsV2($request);
if (!empty($result->contents)) {
$objects = [];
foreach ($result->contents as $item) {
$obj = new Oss\Models\DeleteObject();
$obj->key = $item->key;
$objects[] = $obj;
}
$delete = new Oss\Models\Delete();
$delete->objects = $objects;
$deleteRequest = new Oss\Models\DeleteMultipleObjectsRequest(
bucket: $this->bucket,
delete: $delete
);
$this->client->deleteMultipleObjects($deleteRequest);
}
$continuationToken = $result->nextContinuationToken ?? null;
} while ($continuationToken);
} catch (\Exception $e) {
// 忽略错误
}
}
public function createDirectory(string $path, Config $config): void
{
}
public function setVisibility(string $path, string $visibility): void
{
}
public function visibility(string $path): FileAttributes
{
return new FileAttributes($path, null, 'public');
}
public function mimeType(string $path): FileAttributes
{
try {
$request = new Oss\Models\HeadObjectRequest(
bucket: $this->bucket,
key: $this->prefixPath($path)
);
$result = $this->client->headObject($request);
return new FileAttributes($path, null, null, null, $result->contentType);
} catch (\Exception $e) {
return new FileAttributes($path);
}
}
public function lastModified(string $path): FileAttributes
{
try {
$request = new Oss\Models\HeadObjectRequest(
bucket: $this->bucket,
key: $this->prefixPath($path)
);
$result = $this->client->headObject($request);
$timestamp = $result->lastModified ? $result->lastModified->getTimestamp() : null;
return new FileAttributes($path, null, null, $timestamp);
} catch (\Exception $e) {
return new FileAttributes($path);
}
}
public function fileSize(string $path): FileAttributes
{
try {
$request = new Oss\Models\HeadObjectRequest(
bucket: $this->bucket,
key: $this->prefixPath($path)
);
$result = $this->client->headObject($request);
return new FileAttributes($path, $result->contentLength);
} catch (\Exception $e) {
return new FileAttributes($path);
}
}
public function listContents(string $path, bool $deep): iterable
{
$prefix = rtrim($this->prefixPath($path), '/');
if ($prefix) {
$prefix .= '/';
}
try {
$continuationToken = null;
do {
$request = new Oss\Models\ListObjectsV2Request(
bucket: $this->bucket
);
$request->prefix = $prefix;
$request->maxKeys = 1000;
if ($continuationToken) {
$request->continuationToken = $continuationToken;
}
if (!$deep) {
$request->delimiter = '/';
}
$result = $this->client->listObjectsV2($request);
if (!empty($result->contents)) {
foreach ($result->contents as $content) {
yield new FileAttributes(
$this->removePrefix($content->key),
$content->size,
null,
$content->lastModified ? $content->lastModified->getTimestamp() : null
);
}
}
$continuationToken = $result->nextContinuationToken ?? null;
} while ($continuationToken);
} catch (\Exception $e) {
return;
}
}
public function move(string $source, string $destination, Config $config): void
{
try {
$this->copy($source, $destination, $config);
$this->delete($source);
} catch (\Exception $e) {
throw UnableToMoveFile::fromLocationTo($source, $destination, $e);
}
}
public function copy(string $source, string $destination, Config $config): void
{
try {
$request = new Oss\Models\CopyObjectRequest(
bucket: $this->bucket,
key: $this->prefixPath($destination)
);
$request->sourceKey = $this->prefixPath($source);
$this->client->copyObject($request);
} catch (\Exception $e) {
throw UnableToCopyFile::fromLocationTo($source, $destination, $e);
}
}
public function getUrl(string $path): string
{
$path = $this->prefixPath($path);
if ($domain = $this->config['domain'] ?? null) {
return rtrim($domain, '/') . '/' . ltrim($path, '/');
}
$region = $this->config['region'] ?? 'cn-hangzhou';
if (($this->config['is_cname'] ?? false) && !empty($this->config['endpoint'])) {
return 'https://' . rtrim($this->config['endpoint'], '/') . '/' . ltrim($path, '/');
}
if ($this->config['use_internal'] ?? false) {
return "https://{$this->bucket}.oss-{$region}-internal.aliyuncs.com/{$path}";
}
return "https://{$this->bucket}.oss-{$region}.aliyuncs.com/{$path}";
}
protected function prefixPath(string $path): string
{
$prefix = $this->config['prefix'] ?? '';
return trim($prefix . '/' . ltrim($path, '/'), '/');
}
protected function removePrefix(string $path): string
{
$prefix = $this->config['prefix'] ?? '';
if ($prefix && strpos($path, $prefix) === 0) {
return substr($path, strlen($prefix) + 1);
}
return $path;
}
}
6. 创建 Service Provider
创建app/Support/FilesystemWrapper.php
<?php
namespace App\Support;
use League\Flysystem\Filesystem;
use League\Flysystem\FilesystemAdapter;
/**
* Flysystem V3 兼容包装类
* 提供 Flysystem V2 API 兼容性(用于 Dcat Admin)
*/
class FilesystemWrapper extends Filesystem
{
public function __construct(FilesystemAdapter $adapter, array $config = [])
{
parent::__construct($adapter, $config);
}
/**
* 兼容 Flysystem V2 的 exists() 方法
*/
public function exists(string $path): bool
{
return $this->fileExists($path);
}
/**
* 兼容 Flysystem V2 的 get() 方法
*/
public function get(string $path): string
{
return $this->read($path);
}
/**
* 兼容 Flysystem V2 的 put() 方法
*/
public function put(string $path, $contents, array $config = []): bool
{
try {
if (is_resource($contents)) {
$this->writeStream($path, $contents, $config);
} else {
$this->write($path, $contents, $config);
}
return true;
} catch (\Exception $e) {
return false;
}
}
/**
* 兼容 Flysystem V2 的 getSize() 方法
*/
public function getSize(string $path): int
{
return $this->fileSize($path);
}
/**
* 兼容 Flysystem V2 的 getMimetype() 方法
*/
public function getMimetype(string $path): string
{
return $this->mimeType($path);
}
/**
* 兼容 Flysystem V2 的 getTimestamp() 方法
*/
public function getTimestamp(string $path): int
{
return $this->lastModified($path);
}
/**
* 兼容 Flysystem V2 的 getVisibility() 方法
*/
public function getVisibility(string $path): string
{
return $this->visibility($path);
}
/**
* 兼容 Flysystem V2 的 getMetadata() 方法
*/
public function getMetadata(string $path): array
{
return [
'type' => $this->fileExists($path) ? 'file' : 'dir',
'path' => $path,
'timestamp' => $this->lastModified($path),
'size' => $this->fileExists($path) ? $this->fileSize($path) : 0,
'mimetype' => $this->fileExists($path) ? $this->mimeType($path) : null,
'visibility' => $this->visibility($path),
];
}
/**
* 兼容 Flysystem V2 的 rename() 方法
*/
public function rename(string $path, string $newpath, array $config = []): bool
{
try {
$this->move($path, $newpath, $config);
return true;
} catch (\Exception $e) {
return false;
}
}
/**
* 兼容 Flysystem V2 的 deleteDir() 方法
*/
public function deleteDir(string $dirname): bool
{
try {
$this->deleteDirectory($dirname);
return true;
} catch (\Exception $e) {
return false;
}
}
/**
* 兼容 Flysystem V2 的 createDir() 方法
*/
public function createDir(string $dirname, array $config = []): bool
{
try {
$this->createDirectory($dirname, $config);
return true;
} catch (\Exception $e) {
return false;
}
}
/**
* 兼容 Flysystem V2 的 listContents() 方法返回数组
*/
public function listContentsArray(string $directory = '', bool $recursive = false): array
{
$listing = $this->listContents($directory, $recursive);
$result = [];
foreach ($listing as $item) {
$result[] = $item->jsonSerialize();
}
return $result;
}
}
创建app/Providers/OssServiceProvider.php:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Storage;
use Illuminate\Filesystem\FilesystemAdapter;
use App\Support\OssAdapter;
use App\Support\FilesystemWrapper;
use AlibabaCloud\Oss\V2 as Oss;
class OssServiceProvider extends ServiceProvider
{
public function boot()
{
Storage::extend('oss', function ($app, $config) {
$provider = new Oss\Credentials\StaticCredentialsProvider(
$config['access_key_id'],
$config['access_key_secret']
);
$cfg = Oss\Config::loadDefault();
$cfg->setCredentialsProvider(credentialsProvider: $provider);
$cfg->setRegion(region: $config['region']);
if (!empty($config['endpoint'])) {
$cfg->setEndpoint(endpoint: $config['endpoint']);
}
if ($config['use_internal'] ?? false) {
$cfg->setUseInternalEndpoint(true);
}
if ($config['is_cname'] ?? false) {
$cfg->setUseCname(true);
}
$client = new Oss\Client($cfg);
$adapter = new OssAdapter($client, $config['bucket'], $config);
$filesystem = new FilesystemWrapper($adapter, [
'url' => $config['domain'] ?? ''
]);
return new FilesystemAdapter($filesystem, $adapter, $config);
});
}
public function register()
{
$this->app->singleton(\App\Services\Tool\OssService::class, function ($app) {
return new \App\Services\Tool\OssService();
});
}
}
7. 注册 Service Provider
在 config/app.php 中添加:
'providers' => [
// ...
App\Providers\OssServiceProvider::class,
],
8. Dcat Admin 配置
在 config/admin.php 中:
'upload' => [
'disk' => 'oss',
'directory' => [
'image' => 'images',
'file' => 'files',
],
],
9. 使用示例
在 Dcat Admin 表单中使用
use Dcat\Admin\Form;
$form->image('avatar', '头像')
->disk('oss')
->uniqueName()
->autoUpload();
$form->multipleImage('gallery', '相册')
->disk('oss')
->uniqueName()
->removable()
->sortable();
$form->file('attachment', '附件')
->disk('oss')
->uniqueName();
使用 Storage 门面
use Illuminate\Support\Facades\Storage;
// 上传文件
$path = Storage::disk('oss')->put('images', $request->file('image'));
// 获取 URL
$url = Storage::disk('oss')->url($path);
// 删除文件
Storage::disk('oss')->delete($path);
使用 OssService
use App\Services\Tool\OssService;
$ossService = app(OssService::class);
// 上传文件
$result = $ossService->uploadFile($request->file('image'), 'uploads/images');
if ($result['success']) {
echo $result['url']; // 签名URL
echo $result['public_url']; // 公共URL
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
关于 LearnKu
推荐文章: