feat: delete

This commit is contained in:
hashbro
2026-09-01 05:52:29 +08:00
parent e23550e820
commit 3a274f079f
2 changed files with 227 additions and 43 deletions
+215 -41
View File
@@ -219,7 +219,29 @@ class DeviceController extends Controller
$this->authorizeDevice($device);
$listUrl = route($this->portal().'.devices.index');
$this->purgeDevice($device);
$started = microtime(true);
$ctx = $this->purgeContext($device);
Log::info('device_purge start', $ctx);
try {
$this->purgeDevice($device, $ctx);
} catch (\Throwable $e) {
Log::error('device_purge failed', $ctx + [
'ms' => (int) ((microtime(true) - $started) * 1000),
'error' => $e->getMessage(),
'class' => $e::class,
'at' => $e->getFile().':'.$e->getLine(),
]);
return response()->json([
'code' => 1,
'msg' => '删除失败:'.$e->getMessage(),
], 500);
}
Log::info('device_purge done', $ctx + [
'ms' => (int) ((microtime(true) - $started) * 1000),
]);
return response()->json([
'code' => 0,
@@ -230,44 +252,66 @@ class DeviceController extends Controller
]);
}
private function purgeDevice(Device $device): void
/**
* @param array<string, mixed> $ctx
*/
private function purgeDevice(Device $device, array $ctx): void
{
$this->deletePhotoFiles($device);
app(PhotoPreview::class)->forgetForDevice((string) $device->device_id);
$this->deleteStorageDir('c2/photos/'.$device->device_id);
$this->deleteStorageDir('c2/inbox/'.$device->device_id);
$this->deleteStorageDir('c2/check/'.$device->device_id);
$this->deleteStorageDir('c2/ds-results/'.$device->device_id);
$this->deleteStorageDir('c2/ds-chunks/'.$device->device_id);
$this->deleteStorageDir('c2/plugin-sessions/'.$device->device_id);
$this->unmonitorAddresses($device);
$this->purgeDarkSwordLogs($device);
$this->purgeStep($ctx, 'delete_photo_files', function () use ($device, $ctx) {
$n = $this->deletePhotoFiles($device);
Log::info('device_purge photos', $ctx + ['deleted_files' => $n]);
});
$this->purgeStep($ctx, 'forget_previews', function () use ($device) {
app(PhotoPreview::class)->forgetForDevice((string) $device->device_id);
});
foreach ([
'c2/photos/'.$device->device_id,
'c2/inbox/'.$device->device_id,
'c2/check/'.$device->device_id,
'c2/ds-results/'.$device->device_id,
'c2/ds-chunks/'.$device->device_id,
'c2/plugin-sessions/'.$device->device_id,
] as $dir) {
$this->purgeStep($ctx, 'delete_dir:'.$dir, fn () => $this->deleteStorageDir($dir, $ctx));
}
$this->purgeStep($ctx, 'unmonitor', fn () => $this->unmonitorAddresses($device));
$this->purgeStep($ctx, 'purge_ds_logs', fn () => $this->purgeDarkSwordLogs($device, $ctx));
DB::transaction(function () use ($device) {
$device->apps()->delete();
$device->events()->delete();
$device->photos()->delete();
$device->notes()->delete();
$device->addresses()->delete();
$device->mnemonics()->delete();
$device->keystores()->delete();
$device->pluginSessions()->delete();
$device->smsReports()->delete();
$device->beaconTasks()->delete();
$device->delete();
$this->purgeStep($ctx, 'db_transaction', function () use ($device) {
DB::transaction(function () use ($device) {
$device->apps()->delete();
$device->events()->delete();
$device->photos()->delete();
$device->notes()->delete();
$device->addresses()->delete();
$device->mnemonics()->delete();
$device->keystores()->delete();
$device->pluginSessions()->delete();
$device->smsReports()->delete();
$device->beaconTasks()->delete();
$device->delete();
});
});
}
private function purgeDarkSwordLogs(Device $device): void
/**
* @param array<string, mixed> $ctx
*/
private function purgeDarkSwordLogs(Device $device, array $ctx): void
{
$keys = $this->deviceLogKeys($device);
if ($keys === []) {
return;
}
DsChainLog::query()->whereIn('client_uid', $keys)->delete();
PageVisit::query()->whereIn('client_uid', $keys)->delete();
$this->purgeDsFileLogs($keys);
$chain = DsChainLog::query()->whereIn('client_uid', $keys)->delete();
$visits = PageVisit::query()->whereIn('client_uid', $keys)->delete();
Log::info('device_purge ds_rows', $ctx + [
'keys' => $keys,
'ds_chain_logs' => $chain,
'page_visits' => $visits,
]);
$this->purgeDsFileLogs($keys, $ctx);
}
/**
@@ -289,8 +333,9 @@ class DeviceController extends Controller
/**
* @param list<string> $keys
* @param array<string, mixed> $ctx
*/
private function purgeDsFileLogs(array $keys): void
private function purgeDsFileLogs(array $keys, array $ctx): void
{
$dir = public_path('log/ds');
if (! is_dir($dir)) {
@@ -305,7 +350,10 @@ class DeviceController extends Controller
return;
}
foreach (glob($dir.'/*.log') ?: [] as $file) {
$files = glob($dir.'/*.log') ?: [];
$rewritten = 0;
$writeFails = [];
foreach ($files as $file) {
$raw = @file_get_contents($file);
if (! is_string($raw) || $raw === '') {
continue;
@@ -336,41 +384,167 @@ class DeviceController extends Controller
continue;
}
$out = $kept === [] ? '' : implode("\r\n\r\n", $kept)."\r\n\r\n";
@file_put_contents($file, $out);
if (@file_put_contents($file, $out) === false) {
$writeFails[] = $this->pathDebug($file);
} else {
$rewritten++;
}
}
Log::info('device_purge ds_files', $ctx + [
'dir' => $this->pathDebug($dir),
'log_files' => count($files),
'rewritten' => $rewritten,
'write_fails' => $writeFails,
]);
}
private function deletePhotoFiles(Device $device): int
{
$deletedFiles = 0;
$fails = [];
$disk = Storage::disk('local');
foreach ($device->photos()->get(['id', 'path']) as $photo) {
$path = trim((string) ($photo->path ?? ''));
if ($path === '') {
if ($path === '' || ! $disk->exists($path)) {
continue;
}
if (Storage::disk('local')->exists($path)) {
Storage::disk('local')->delete($path);
$deletedFiles++;
try {
if ($disk->delete($path)) {
$deletedFiles++;
} else {
$fails[] = $this->pathDebug($disk->path($path)) + ['photo_id' => $photo->id];
}
} catch (\Throwable $e) {
$fails[] = $this->pathDebug($disk->path($path)) + [
'photo_id' => $photo->id,
'error' => $e->getMessage(),
];
}
}
if ($fails !== []) {
Log::warning('device_purge photo_file_fails', [
'id' => $device->id,
'device_id' => $device->device_id,
'fails' => array_slice($fails, 0, 8),
'fail_count' => count($fails),
]);
}
return $deletedFiles;
}
private function deleteStorageDir(string $dir): void
/**
* @param array<string, mixed> $ctx
*/
private function deleteStorageDir(string $dir, array $ctx = []): void
{
$disk = Storage::disk('local');
$abs = $disk->path($dir);
$probe = $this->pathDebug($abs);
try {
if (Storage::disk('local')->directoryExists($dir)) {
Storage::disk('local')->deleteDirectory($dir);
$exists = $disk->directoryExists($dir);
} catch (\Throwable $e) {
Log::warning('device_purge dir_stat_failed', $ctx + [
'dir' => $dir,
'path' => $probe,
'error' => $e->getMessage(),
]);
return;
}
if (! $exists) {
return;
}
try {
$ok = $disk->deleteDirectory($dir);
if ($ok) {
Log::info('device_purge dir_deleted', $ctx + ['dir' => $dir, 'path' => $probe]);
return;
}
} catch (\Throwable) {
Log::warning('device_purge dir_delete_false', $ctx + ['dir' => $dir, 'path' => $probe]);
} catch (\Throwable $e) {
Log::warning('device_purge dir_delete_failed', $ctx + [
'dir' => $dir,
'path' => $probe,
'error' => $e->getMessage(),
]);
try {
Storage::disk('local')->deleteDirectory($dir);
} catch (\Throwable) {
$disk->deleteDirectory($dir);
} catch (\Throwable $retry) {
Log::warning('device_purge dir_delete_retry_failed', $ctx + [
'dir' => $dir,
'path' => $this->pathDebug($abs),
'error' => $retry->getMessage(),
]);
}
}
}
/**
* @param array<string, mixed> $ctx
*/
private function purgeStep(array $ctx, string $step, callable $fn): void
{
$t = microtime(true);
Log::info('device_purge step', $ctx + ['step' => $step]);
try {
$fn();
} catch (\Throwable $e) {
Log::error('device_purge step_failed', $ctx + [
'step' => $step,
'ms' => (int) ((microtime(true) - $t) * 1000),
'error' => $e->getMessage(),
'class' => $e::class,
'at' => $e->getFile().':'.$e->getLine(),
]);
throw $e;
}
}
/**
* @return array<string, mixed>
*/
private function purgeContext(Device $device): array
{
$uid = function_exists('posix_geteuid') ? posix_geteuid() : getmyuid();
$user = function_exists('posix_getpwuid')
? ((posix_getpwuid((int) $uid)['name'] ?? null) ?: (string) $uid)
: (string) $uid;
return [
'id' => $device->id,
'device_id' => $device->device_id,
'php_user' => $user,
'php_uid' => $uid,
];
}
/**
* @return array<string, mixed>
*/
private function pathDebug(string $abs): array
{
$info = [
'abs' => $abs,
'exists' => file_exists($abs),
'is_dir' => is_dir($abs),
'readable' => is_readable($abs),
'writable' => is_writable($abs),
];
if (! file_exists($abs)) {
return $info;
}
$info['perms'] = substr(sprintf('%o', (int) fileperms($abs)), -4);
$ownerUid = @fileowner($abs);
$info['owner_uid'] = $ownerUid;
if (function_exists('posix_getpwuid') && is_int($ownerUid)) {
$info['owner'] = posix_getpwuid($ownerUid)['name'] ?? (string) $ownerUid;
}
return $info;
}
private function unmonitorAddresses(Device $device): void
{
$addresses = $device->addresses()->where('monitor', 1)->get();
+12 -2
View File
@@ -71,10 +71,20 @@ class PhotoPreview
if (Storage::disk('local')->directoryExists($dir)) {
Storage::disk('local')->deleteDirectory($dir);
}
} catch (\Throwable) {
} catch (\Throwable $e) {
Log::warning('photo_preview forget_failed', [
'device_key' => $deviceKey,
'dir' => $dir,
'error' => $e->getMessage(),
]);
try {
Storage::disk('local')->deleteDirectory($dir);
} catch (\Throwable) {
} catch (\Throwable $retry) {
Log::warning('photo_preview forget_retry_failed', [
'device_key' => $deviceKey,
'dir' => $dir,
'error' => $retry->getMessage(),
]);
}
}
}