177 lines
6.8 KiB
PHP
177 lines
6.8 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace app\common\service;
|
||
|
|
|
||
|
|
use support\exception\BusinessException;
|
||
|
|
use support\Log;
|
||
|
|
use support\Request;
|
||
|
|
|
||
|
|
class UploadService
|
||
|
|
{
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 从服务器本地路径上传文件到 OSS
|
||
|
|
*
|
||
|
|
* @param Request $request
|
||
|
|
* - path: 本地文件绝对路径
|
||
|
|
* - scene: 业务分区/场景
|
||
|
|
* - category: 文件分类(可选)
|
||
|
|
* - original_name: 原始文件名(可选)
|
||
|
|
*/
|
||
|
|
public function uploadLocalFile($param)
|
||
|
|
{
|
||
|
|
try {
|
||
|
|
$filePath = $param['path'];
|
||
|
|
$scene = $param['scene'];
|
||
|
|
$category = $param['category']??'general';
|
||
|
|
$providedOriginalName = $param['original_name']??'';
|
||
|
|
|
||
|
|
if (empty($filePath)) {
|
||
|
|
Log::error('本地文件路径不能为空',$param);
|
||
|
|
throw new BusinessException('本地文件路径不能为空');
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!file_exists($filePath) || !is_readable($filePath)) {
|
||
|
|
Log::error('本地文件不存在或不可读',$param);
|
||
|
|
throw new BusinessException('本地文件不存在或不可读');
|
||
|
|
}
|
||
|
|
if (empty($scene)) {
|
||
|
|
Log::error('上传场景不能为空',$param);
|
||
|
|
throw new BusinessException('上传场景不能为空');
|
||
|
|
}
|
||
|
|
|
||
|
|
$basename = basename($filePath);
|
||
|
|
$originalName = $providedOriginalName ?: $basename;
|
||
|
|
$extensionFromName = pathinfo($originalName, PATHINFO_EXTENSION);
|
||
|
|
$extensionFromPath = pathinfo($basename, PATHINFO_EXTENSION);
|
||
|
|
$extension = $extensionFromName ?: $extensionFromPath;
|
||
|
|
|
||
|
|
$mime = mime_content_type($filePath) ?: 'application/octet-stream';
|
||
|
|
$size = filesize($filePath) ?: 0;
|
||
|
|
|
||
|
|
$sceneUrl = $scene ? ($scene . '/') : '';
|
||
|
|
$dir = 'bsh_sample/' . $sceneUrl . date('Ymd') . '/';
|
||
|
|
// 使用传入的 original_name 命名(缺少扩展名时补上推断的扩展名),并做简单安全处理
|
||
|
|
$safeBaseName = preg_replace('/[\\\\\/]+/', '_', $originalName);
|
||
|
|
if ($extension && strtolower(pathinfo($safeBaseName, PATHINFO_EXTENSION)) !== strtolower($extension)) {
|
||
|
|
// 若提供的名称没有扩展名或扩展名不一致,则以推断扩展名为准
|
||
|
|
$safeBaseName = pathinfo($safeBaseName, PATHINFO_FILENAME) . '.' . $extension;
|
||
|
|
} elseif (!$extension && !pathinfo($safeBaseName, PATHINFO_EXTENSION)) {
|
||
|
|
// 实在没有扩展名信息时,保持原名
|
||
|
|
}
|
||
|
|
|
||
|
|
// 重名检测:同一目录、同名且同类型则在文件名后追加序号
|
||
|
|
$uploadedFilesService = new UploadedFilesService();
|
||
|
|
$candidateName = $safeBaseName;
|
||
|
|
$nameBody = pathinfo($safeBaseName, PATHINFO_FILENAME);
|
||
|
|
$nameExt = pathinfo($safeBaseName, PATHINFO_EXTENSION);
|
||
|
|
$counter = 1;
|
||
|
|
while (true) {
|
||
|
|
$candidatePath = $dir . $candidateName;
|
||
|
|
$existing = $uploadedFilesService->findFile(['file_path' => $candidatePath]);
|
||
|
|
if (!$existing) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
// 若存在同路径同名,且类型也相同,则追加序号
|
||
|
|
if (isset($existing->file_type) && strtolower($existing->file_type) === strtolower($mime)) {
|
||
|
|
$suffix = ' (' . $counter . ')';
|
||
|
|
$candidateName = $nameBody . $suffix . ($nameExt !== '' ? ('.' . $nameExt) : '');
|
||
|
|
$counter++;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
// 类型不同则允许同名,结束循环
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
$objectKey = $dir . $candidateName;
|
||
|
|
|
||
|
|
$ossService = new OssService();
|
||
|
|
$ossService->uploadImage($filePath, $objectKey);
|
||
|
|
$privateUrl = $ossService->generatePrivateUrl($objectKey);
|
||
|
|
|
||
|
|
// 记录文件信息到数据库
|
||
|
|
$uploadedFilesService = new UploadedFilesService();
|
||
|
|
$fileInfo = [
|
||
|
|
'file_name' => basename($objectKey),
|
||
|
|
'file_path' => $objectKey,
|
||
|
|
'file_size' => $size,
|
||
|
|
'file_type' => $mime,
|
||
|
|
'original_name' => $originalName,
|
||
|
|
];
|
||
|
|
$recordResult = $uploadedFilesService->recordFileInfo($fileInfo, null, $scene, $category);
|
||
|
|
|
||
|
|
$data = [
|
||
|
|
'original_name' => $originalName,
|
||
|
|
'file_name' => $objectKey,
|
||
|
|
'file_url' => $privateUrl,
|
||
|
|
'file_id' => $recordResult['id'] ?? null,
|
||
|
|
'partition' => $scene,
|
||
|
|
'category' => $category,
|
||
|
|
];
|
||
|
|
|
||
|
|
return $data;
|
||
|
|
} catch (\Throwable $e) {
|
||
|
|
Log::error($e->getMessage(),$param);
|
||
|
|
throw new BusinessException($e->getMessage());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 删除 OSS 上的文件
|
||
|
|
*
|
||
|
|
* @param array $param
|
||
|
|
* - object_key: OSS 对象键名-oss路径(必需)
|
||
|
|
* - file_id: 数据库文件记录ID(可选,用于删除数据库记录)
|
||
|
|
* @return bool 删除是否成功
|
||
|
|
*
|
||
|
|
* 示例
|
||
|
|
* $uploadService = new UploadService();
|
||
|
|
*
|
||
|
|
* 只删除 OSS 文件
|
||
|
|
* $uploadService->deleteOssFile([
|
||
|
|
* 'object_key' => 'vehicle_album/documents/20241201/abc123.pdf'
|
||
|
|
* ]);
|
||
|
|
*
|
||
|
|
* 同时删除 OSS 文件和数据库记录
|
||
|
|
* $uploadService->deleteOssFile([
|
||
|
|
* 'object_key' => 'vehicle_album/images/20241201/abc123.jpg',
|
||
|
|
* 'file_id' => 123
|
||
|
|
* ]);
|
||
|
|
*/
|
||
|
|
public function deleteOssFile($objectKey,$fileId = null)
|
||
|
|
{
|
||
|
|
try {
|
||
|
|
|
||
|
|
if (empty($objectKey)) {
|
||
|
|
Log::error('OSS对象键名不能为空', $objectKey);
|
||
|
|
throw new BusinessException('OSS对象键名不能为空');
|
||
|
|
}
|
||
|
|
|
||
|
|
$uploadedFilesService = new UploadedFilesService();
|
||
|
|
// 如果提供了文件ID,同时删除数据库记录
|
||
|
|
if ($fileId) {
|
||
|
|
$uploadedFilesService->deleteFile($fileId);
|
||
|
|
}else{
|
||
|
|
$uploadedFilesService = new UploadedFilesService();
|
||
|
|
if (empty($fileId)) {
|
||
|
|
$where = ['file_path' => $objectKey];
|
||
|
|
$file = $uploadedFilesService->getFile($where);
|
||
|
|
$fileId = $file->id;
|
||
|
|
}
|
||
|
|
if(!empty($fileId)){
|
||
|
|
$uploadedFilesService->deleteFile($fileId);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 删除 OSS 上的文件
|
||
|
|
$ossService = new OssService();
|
||
|
|
$ossService->deleteFile($objectKey);
|
||
|
|
|
||
|
|
Log::info('OSS文件删除成功', ['object_key' => $objectKey, 'file_id' => $fileId]);
|
||
|
|
return true;
|
||
|
|
} catch (\Throwable $e) {
|
||
|
|
Log::error('删除OSS文件失败: ' . $e->getMessage());
|
||
|
|
throw new BusinessException('删除OSS文件失败: ' . $e->getMessage());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|