CleanupBackup.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. <?php
  2. namespace App\Module\Cleanup\Models;
  3. use App\Module\Cleanup\Enums\BACKUP_TYPE;
  4. use App\Module\Cleanup\Enums\COMPRESSION_TYPE;
  5. use App\Module\Cleanup\Enums\BACKUP_STATUS;
  6. use UCore\ModelCore;
  7. use Illuminate\Database\Eloquent\Relations\BelongsTo;
  8. use Illuminate\Database\Eloquent\Relations\HasMany;
  9. /**
  10. * 备份记录模型
  11. *
  12. * 存储计划的备份方案和备份内容
  13. */
  14. class CleanupBackup extends ModelCore
  15. {
  16. /**
  17. * 数据表名
  18. */
  19. protected $table = 'cleanup_backups';
  20. // field start
  21. /**
  22. * @property int $id 主键ID
  23. * @property int $plan_id 关联的清理计划ID
  24. * @property int $task_id 关联的清理任务ID(如果是任务触发的备份)
  25. * @property string $backup_name 备份名称
  26. * @property int $backup_type 备份类型:1SQL,2JSON,3CSV
  27. * @property int $compression_type 压缩类型:1none,2gzip,3zip
  28. * @property string $backup_path 备份文件路径
  29. * @property int $backup_size 备份文件大小(字节)
  30. * @property int $original_size 原始数据大小(字节)
  31. * @property int $tables_count 备份表数量
  32. * @property int $records_count 备份记录数量
  33. * @property int $backup_status 备份状态:1进行中,2已完成,3已失败
  34. * @property string $backup_hash 备份文件MD5哈希
  35. * @property array $backup_config 备份配置信息
  36. * @property \Carbon\Carbon $started_at 备份开始时间
  37. * @property \Carbon\Carbon $completed_at 备份完成时间
  38. * @property \Carbon\Carbon $expires_at 备份过期时间
  39. * @property string $error_message 错误信息
  40. * @property int $created_by 创建者用户ID
  41. * @property \Carbon\Carbon $created_at 创建时间
  42. * @property \Carbon\Carbon $updated_at 更新时间
  43. */
  44. // field end
  45. /**
  46. * 字段类型转换
  47. */
  48. protected $casts = [
  49. 'plan_id' => 'integer',
  50. 'task_id' => 'integer',
  51. 'backup_type' => 'integer',
  52. 'compression_type' => 'integer',
  53. 'backup_size' => 'integer',
  54. 'original_size' => 'integer',
  55. 'tables_count' => 'integer',
  56. 'records_count' => 'integer',
  57. 'backup_status' => 'integer',
  58. 'backup_config' => 'array',
  59. 'created_by' => 'integer',
  60. 'started_at' => 'datetime',
  61. 'completed_at' => 'datetime',
  62. 'expires_at' => 'datetime',
  63. 'created_at' => 'datetime',
  64. 'updated_at' => 'datetime',
  65. ];
  66. /**
  67. * 获取备份类型枚举
  68. */
  69. public function getBackupTypeEnumAttribute(): BACKUP_TYPE
  70. {
  71. return BACKUP_TYPE::from($this->backup_type);
  72. }
  73. /**
  74. * 获取压缩类型枚举
  75. */
  76. public function getCompressionTypeEnumAttribute(): COMPRESSION_TYPE
  77. {
  78. return COMPRESSION_TYPE::from($this->compression_type);
  79. }
  80. /**
  81. * 获取备份状态枚举
  82. */
  83. public function getBackupStatusEnumAttribute(): BACKUP_STATUS
  84. {
  85. return BACKUP_STATUS::from($this->backup_status);
  86. }
  87. /**
  88. * 获取备份类型描述
  89. */
  90. public function getBackupTypeNameAttribute(): string
  91. {
  92. return $this->getBackupTypeEnumAttribute()->getDescription();
  93. }
  94. /**
  95. * 获取压缩类型描述
  96. */
  97. public function getCompressionTypeNameAttribute(): string
  98. {
  99. return $this->getCompressionTypeEnumAttribute()->getDescription();
  100. }
  101. /**
  102. * 获取备份状态描述
  103. */
  104. public function getBackupStatusNameAttribute(): string
  105. {
  106. return $this->getBackupStatusEnumAttribute()->getDescription();
  107. }
  108. /**
  109. * 获取备份状态颜色
  110. */
  111. public function getBackupStatusColorAttribute(): string
  112. {
  113. return $this->getBackupStatusEnumAttribute()->getColor();
  114. }
  115. /**
  116. * 获取备份状态图标
  117. */
  118. public function getBackupStatusIconAttribute(): string
  119. {
  120. return $this->getBackupStatusEnumAttribute()->getIcon();
  121. }
  122. /**
  123. * 获取格式化的备份大小
  124. */
  125. public function getBackupSizeFormattedAttribute(): string
  126. {
  127. return $this->formatBytes($this->backup_size);
  128. }
  129. /**
  130. * 获取格式化的原始大小
  131. */
  132. public function getOriginalSizeFormattedAttribute(): string
  133. {
  134. return $this->formatBytes($this->original_size);
  135. }
  136. /**
  137. * 获取压缩率
  138. */
  139. public function getCompressionRatioAttribute(): float
  140. {
  141. if ($this->original_size == 0) {
  142. return 0;
  143. }
  144. return (1 - $this->backup_size / $this->original_size) * 100;
  145. }
  146. /**
  147. * 获取格式化的压缩率
  148. */
  149. public function getCompressionRatioFormattedAttribute(): string
  150. {
  151. return number_format($this->compression_ratio, 2) . '%';
  152. }
  153. /**
  154. * 获取备份持续时间
  155. */
  156. public function getDurationAttribute(): ?float
  157. {
  158. if (!$this->started_at || !$this->completed_at) {
  159. return null;
  160. }
  161. return $this->started_at->diffInSeconds($this->completed_at);
  162. }
  163. /**
  164. * 获取格式化的备份持续时间
  165. */
  166. public function getDurationFormattedAttribute(): ?string
  167. {
  168. $duration = $this->duration;
  169. return $duration ? $this->formatDuration($duration) : null;
  170. }
  171. /**
  172. * 判断备份是否已过期
  173. */
  174. public function getIsExpiredAttribute(): bool
  175. {
  176. return $this->expires_at && $this->expires_at->isPast();
  177. }
  178. /**
  179. * 获取过期状态文本
  180. */
  181. public function getExpiryStatusAttribute(): string
  182. {
  183. if (!$this->expires_at) {
  184. return '永不过期';
  185. }
  186. if ($this->is_expired) {
  187. return '已过期';
  188. }
  189. return '剩余 ' . $this->expires_at->diffForHumans();
  190. }
  191. /**
  192. * 获取过期状态颜色
  193. */
  194. public function getExpiryColorAttribute(): string
  195. {
  196. if (!$this->expires_at) {
  197. return 'info';
  198. }
  199. if ($this->is_expired) {
  200. return 'danger';
  201. }
  202. $daysLeft = now()->diffInDays($this->expires_at);
  203. if ($daysLeft <= 3) {
  204. return 'warning';
  205. }
  206. return 'success';
  207. }
  208. /**
  209. * 获取文件扩展名
  210. */
  211. public function getFileExtensionAttribute(): string
  212. {
  213. $backupExt = $this->getBackupTypeEnumAttribute()->getFileExtension();
  214. $compressExt = $this->getCompressionTypeEnumAttribute()->getFileExtension();
  215. return $backupExt . $compressExt;
  216. }
  217. /**
  218. * 获取完整文件名
  219. */
  220. public function getFullFilenameAttribute(): string
  221. {
  222. return $this->backup_name . '.' . $this->file_extension;
  223. }
  224. /**
  225. * 判断文件是否存在
  226. */
  227. public function getFileExistsAttribute(): bool
  228. {
  229. return file_exists($this->backup_path);
  230. }
  231. /**
  232. * 关联清理计划
  233. */
  234. public function plan(): BelongsTo
  235. {
  236. return $this->belongsTo(CleanupPlan::class, 'plan_id');
  237. }
  238. /**
  239. * 关联清理任务
  240. */
  241. public function task(): BelongsTo
  242. {
  243. return $this->belongsTo(CleanupTask::class, 'task_id');
  244. }
  245. /**
  246. * 关联备份文件
  247. */
  248. public function files(): HasMany
  249. {
  250. return $this->hasMany(CleanupBackupFile::class, 'backup_id');
  251. }
  252. /**
  253. * 关联SQL备份记录
  254. */
  255. public function sqlBackups(): HasMany
  256. {
  257. return $this->hasMany(CleanupSqlBackup::class, 'backup_id');
  258. }
  259. /**
  260. * 作用域:按计划筛选
  261. */
  262. public function scopeByPlan($query, int $planId)
  263. {
  264. return $query->where('plan_id', $planId);
  265. }
  266. /**
  267. * 作用域:按任务筛选
  268. */
  269. public function scopeByTask($query, int $taskId)
  270. {
  271. return $query->where('task_id', $taskId);
  272. }
  273. /**
  274. * 作用域:按备份状态筛选
  275. */
  276. public function scopeByStatus($query, int $status)
  277. {
  278. return $query->where('backup_status', $status);
  279. }
  280. /**
  281. * 作用域:已完成的备份
  282. */
  283. public function scopeCompleted($query)
  284. {
  285. return $query->where('backup_status', BACKUP_STATUS::COMPLETED->value);
  286. }
  287. /**
  288. * 作用域:已过期的备份
  289. */
  290. public function scopeExpired($query)
  291. {
  292. return $query->where('expires_at', '<', now());
  293. }
  294. /**
  295. * 作用域:即将过期的备份
  296. */
  297. public function scopeExpiringSoon($query, int $days = 7)
  298. {
  299. return $query->whereBetween('expires_at', [now(), now()->addDays($days)]);
  300. }
  301. /**
  302. * 作用域:按备份名称搜索
  303. */
  304. public function scopeSearchName($query, string $search)
  305. {
  306. return $query->where('backup_name', 'like', "%{$search}%");
  307. }
  308. /**
  309. * 格式化字节大小
  310. */
  311. private function formatBytes(int $bytes): string
  312. {
  313. if ($bytes == 0) {
  314. return '0 B';
  315. }
  316. $units = ['B', 'KB', 'MB', 'GB', 'TB'];
  317. $i = floor(log($bytes, 1024));
  318. return round($bytes / pow(1024, $i), 2) . ' ' . $units[$i];
  319. }
  320. /**
  321. * 格式化持续时间
  322. */
  323. private function formatDuration(float $seconds): string
  324. {
  325. if ($seconds < 60) {
  326. return number_format($seconds, 2) . ' 秒';
  327. } elseif ($seconds < 3600) {
  328. return number_format($seconds / 60, 2) . ' 分钟';
  329. } else {
  330. return number_format($seconds / 3600, 2) . ' 小时';
  331. }
  332. }
  333. /**
  334. * 获取备份状态统计
  335. */
  336. public static function getStatusStats(): array
  337. {
  338. $stats = static::selectRaw('backup_status, COUNT(*) as count')
  339. ->groupBy('backup_status')
  340. ->get()
  341. ->keyBy('backup_status')
  342. ->toArray();
  343. $result = [];
  344. foreach (BACKUP_STATUS::cases() as $status) {
  345. $result[$status->value] = [
  346. 'name' => $status->getDescription(),
  347. 'count' => $stats[$status->value]['count'] ?? 0,
  348. 'color' => $status->getColor(),
  349. 'icon' => $status->getIcon(),
  350. ];
  351. }
  352. return $result;
  353. }
  354. /**
  355. * 获取存储统计
  356. */
  357. public static function getStorageStats(): array
  358. {
  359. $stats = static::selectRaw('
  360. COUNT(*) as total_backups,
  361. SUM(backup_size) as total_backup_size,
  362. SUM(original_size) as total_original_size,
  363. AVG(backup_size) as avg_backup_size
  364. ')->first();
  365. return [
  366. 'total_backups' => $stats->total_backups ?? 0,
  367. 'total_backup_size' => $stats->total_backup_size ?? 0,
  368. 'total_original_size' => $stats->total_original_size ?? 0,
  369. 'avg_backup_size' => $stats->avg_backup_size ?? 0,
  370. 'total_compression_ratio' => $stats->total_original_size > 0
  371. ? (1 - $stats->total_backup_size / $stats->total_original_size) * 100
  372. : 0,
  373. ];
  374. }
  375. }