248 lines
6.1 KiB
PHP
248 lines
6.1 KiB
PHP
<?php
|
||
|
||
namespace App\Models;
|
||
|
||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||
use Illuminate\Database\Eloquent\Model;
|
||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||
use Illuminate\Support\Facades\Storage;
|
||
|
||
class File extends Model
|
||
{
|
||
use HasFactory;
|
||
|
||
protected $fillable = [
|
||
'user_id',
|
||
'original_name',
|
||
'storage_name',
|
||
'path',
|
||
'url',
|
||
'mime_type',
|
||
'size',
|
||
'extension',
|
||
'disk',
|
||
'module',
|
||
'purpose',
|
||
'description',
|
||
'status',
|
||
];
|
||
|
||
protected $casts = [
|
||
'size' => 'integer',
|
||
];
|
||
|
||
/**
|
||
* 关联用户
|
||
*/
|
||
public function user(): BelongsTo
|
||
{
|
||
return $this->belongsTo(User::class);
|
||
}
|
||
|
||
/**
|
||
* 获取文件完整路径
|
||
*/
|
||
public function getFullPathAttribute(): string
|
||
{
|
||
return $this->path . '/' . $this->storage_name;
|
||
}
|
||
|
||
/**
|
||
* 获取文件大小(可读格式)
|
||
*/
|
||
public function getSizeFormattedAttribute(): string
|
||
{
|
||
$size = $this->size;
|
||
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||
|
||
for ($i = 0; $size >= 1024 && $i < 4; $i++) {
|
||
$size /= 1024;
|
||
}
|
||
|
||
return round($size, 2) . ' ' . $units[$i];
|
||
}
|
||
|
||
/**
|
||
* 获取文件图标
|
||
*/
|
||
public function getIconAttribute(): string
|
||
{
|
||
$extension = strtolower($this->extension);
|
||
|
||
$icons = [
|
||
// 图片
|
||
'jpg' => 'image', 'jpeg' => 'image', 'png' => 'image', 'gif' => 'image',
|
||
'bmp' => 'image', 'svg' => 'image', 'webp' => 'image',
|
||
|
||
// 文档
|
||
'pdf' => 'picture_as_pdf',
|
||
'doc' => 'description', 'docx' => 'description',
|
||
'xls' => 'table_chart', 'xlsx' => 'table_chart',
|
||
'ppt' => 'slideshow', 'pptx' => 'slideshow',
|
||
|
||
// 文本
|
||
'txt' => 'text_fields', 'md' => 'text_fields',
|
||
'json' => 'code', 'xml' => 'code', 'html' => 'code',
|
||
|
||
// 压缩文件
|
||
'zip' => 'folder_zip', 'rar' => 'folder_zip', '7z' => 'folder_zip',
|
||
'tar' => 'folder_zip', 'gz' => 'folder_zip',
|
||
|
||
// 其他
|
||
'csv' => 'table_rows',
|
||
];
|
||
|
||
return $icons[$extension] ?? 'insert_drive_file';
|
||
}
|
||
|
||
/**
|
||
* 检查文件是否存在
|
||
*/
|
||
public function exists(): bool
|
||
{
|
||
return Storage::disk($this->disk)->exists($this->getFullPathAttribute());
|
||
}
|
||
|
||
/**
|
||
* 获取文件内容
|
||
*/
|
||
public function getContent(): string
|
||
{
|
||
return Storage::disk($this->disk)->get($this->getFullPathAttribute());
|
||
}
|
||
|
||
/**
|
||
* 删除物理文件
|
||
*/
|
||
public function deletePhysicalFile(): bool
|
||
{
|
||
if ($this->exists()) {
|
||
return Storage::disk($this->disk)->delete($this->getFullPathAttribute());
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 获取文件URL
|
||
*/
|
||
public function getUrl(): string
|
||
{
|
||
if ($this->url) {
|
||
return $this->url;
|
||
}
|
||
|
||
return Storage::disk($this->disk)->url($this->getFullPathAttribute());
|
||
}
|
||
|
||
/**
|
||
* 获取缩略图URL(如果是图片)
|
||
*/
|
||
public function getThumbnailUrl($width = 200, $height = 200): ?string
|
||
{
|
||
if (strpos($this->mime_type, 'image/') === 0) {
|
||
// 这里可以集成图片处理库生成缩略图
|
||
// 暂时返回原图URL
|
||
return $this->getUrl();
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 获取文件统计
|
||
*/
|
||
public static function getStatistics($userId = null)
|
||
{
|
||
$query = self::query();
|
||
|
||
if ($userId) {
|
||
$query->where('user_id', $userId);
|
||
}
|
||
|
||
$total = $query->count();
|
||
$totalSize = $query->sum('size');
|
||
$byModule = $query->selectRaw('module, COUNT(*) as count, SUM(size) as size')
|
||
->groupBy('module')
|
||
->orderBy('count', 'desc')
|
||
->get();
|
||
$byType = $query->selectRaw('mime_type, COUNT(*) as count')
|
||
->groupBy('mime_type')
|
||
->orderBy('count', 'desc')
|
||
->limit(10)
|
||
->get();
|
||
|
||
return [
|
||
'total' => $total,
|
||
'total_size' => $totalSize,
|
||
'total_size_formatted' => self::formatSize($totalSize),
|
||
'by_module' => $byModule,
|
||
'by_type' => $byType,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 格式化文件大小
|
||
*/
|
||
private static function formatSize($bytes): string
|
||
{
|
||
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||
|
||
for ($i = 0; $bytes >= 1024 && $i < 4; $i++) {
|
||
$bytes /= 1024;
|
||
}
|
||
|
||
return round($bytes, 2) . ' ' . $units[$i];
|
||
}
|
||
|
||
/**
|
||
* 根据模块获取文件
|
||
*/
|
||
public static function getByModule($module, $purpose = null, $limit = 20)
|
||
{
|
||
$query = self::where('module', $module)->where('status', 'active');
|
||
|
||
if ($purpose) {
|
||
$query->where('purpose', $purpose);
|
||
}
|
||
|
||
return $query->orderBy('created_at', 'desc')->limit($limit)->get();
|
||
}
|
||
|
||
/**
|
||
* 清理过期文件
|
||
*/
|
||
public static function cleanupExpired($days = 30)
|
||
{
|
||
$expiredDate = now()->subDays($days);
|
||
|
||
$files = self::where('created_at', '<', $expiredDate)
|
||
->where('status', '!=', 'permanent')
|
||
->get();
|
||
|
||
$deletedCount = 0;
|
||
$failedFiles = [];
|
||
|
||
foreach ($files as $file) {
|
||
try {
|
||
// 删除物理文件
|
||
$file->deletePhysicalFile();
|
||
|
||
// 删除数据库记录
|
||
$file->delete();
|
||
|
||
$deletedCount++;
|
||
} catch (\Exception $e) {
|
||
$failedFiles[] = [
|
||
'id' => $file->id,
|
||
'name' => $file->original_name,
|
||
'error' => $e->getMessage(),
|
||
];
|
||
}
|
||
}
|
||
|
||
return [
|
||
'deleted_count' => $deletedCount,
|
||
'failed_files' => $failedFiles,
|
||
];
|
||
}
|
||
} |