fix: test
This commit is contained in:
+1
-1
@@ -15,7 +15,7 @@ APP_MAINTENANCE_DRIVER=file
|
|||||||
BCRYPT_ROUNDS=12
|
BCRYPT_ROUNDS=12
|
||||||
|
|
||||||
LOG_CHANNEL=stack
|
LOG_CHANNEL=stack
|
||||||
LOG_STACK=single
|
LOG_STACK=daily
|
||||||
LOG_DEPRECATIONS_CHANNEL=null
|
LOG_DEPRECATIONS_CHANNEL=null
|
||||||
LOG_LEVEL=debug
|
LOG_LEVEL=debug
|
||||||
|
|
||||||
|
|||||||
@@ -635,24 +635,19 @@ class DeviceController extends Controller
|
|||||||
if (! in_array($field, $sortable, true)) {
|
if (! in_array($field, $sortable, true)) {
|
||||||
$field = 'id';
|
$field = 'id';
|
||||||
}
|
}
|
||||||
$paginator = $device->keystores()->orderBy($field, $order)->paginate($limit, ['*'], 'page', $page);
|
$cols = WalletKeystore::listColumns();
|
||||||
|
$paginator = $device->keystores()->select($cols)->orderBy($field, $order)->paginate($limit, $cols, 'page', $page);
|
||||||
$portal = $this->portal();
|
$portal = $this->portal();
|
||||||
$data = collect($paginator->items())->map(function (WalletKeystore $row) use ($portal) {
|
$data = collect($paginator->items())->map(function (WalletKeystore $row) use ($portal) {
|
||||||
try {
|
$stats = $row->listStats();
|
||||||
$itemCount = $row->itemCount();
|
|
||||||
$summary = $row->summary();
|
|
||||||
} catch (\Throwable) {
|
|
||||||
$itemCount = 0;
|
|
||||||
$summary = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'id' => $row->id,
|
'id' => $row->id,
|
||||||
'source' => $row->sourceLabel(),
|
'source' => $row->sourceLabel(),
|
||||||
'decrypted' => (int) $row->decrypted,
|
'decrypted' => (int) $row->decrypted,
|
||||||
'kind' => $row->kindLabel(),
|
'kind' => $stats['kind'],
|
||||||
'item_count' => $itemCount,
|
'item_count' => $stats['item_count'],
|
||||||
'summary' => $summary,
|
'summary' => $stats['summary'],
|
||||||
'created_at' => optional($row->created_at)->format('Y-m-d H:i:s'),
|
'created_at' => optional($row->created_at)->format('Y-m-d H:i:s'),
|
||||||
'items_url' => route($portal.'.keystores.items', $row->id),
|
'items_url' => route($portal.'.keystores.items', $row->id),
|
||||||
'decrypt_url' => route($portal.'.keystores.decrypt', $row->id),
|
'decrypt_url' => route($portal.'.keystores.decrypt', $row->id),
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use App\Services\EthKeystore;
|
|||||||
use App\Support\AgentScope;
|
use App\Support\AgentScope;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class KeystoreController extends Controller
|
class KeystoreController extends Controller
|
||||||
{
|
{
|
||||||
@@ -50,12 +51,34 @@ class KeystoreController extends Controller
|
|||||||
|
|
||||||
$limit = max(1, min(100, (int) $request->query('limit', 20)));
|
$limit = max(1, min(100, (int) $request->query('limit', 20)));
|
||||||
$page = max(1, (int) $request->query('page', 1));
|
$page = max(1, (int) $request->query('page', 1));
|
||||||
$paginator = $q->paginate($limit, ['*'], 'page', $page);
|
$cols = array_merge(WalletKeystore::listColumns(), [
|
||||||
|
'devices.device_id as device_key',
|
||||||
|
'devices.channel_id as device_channel_id',
|
||||||
|
]);
|
||||||
|
Log::info('keystore.list.data.start', [
|
||||||
|
'page' => $page,
|
||||||
|
'limit' => $limit,
|
||||||
|
'mem' => memory_get_usage(true),
|
||||||
|
]);
|
||||||
|
$paginator = $q->paginate($limit, $cols, 'page', $page);
|
||||||
|
Log::info('keystore.list.data.page', [
|
||||||
|
'total' => $paginator->total(),
|
||||||
|
'ids' => collect($paginator->items())->pluck('id')->all(),
|
||||||
|
'sizes' => collect($paginator->items())->mapWithKeys(
|
||||||
|
static fn (WalletKeystore $row) => [$row->id => (int) ($row->raw_json_len ?? 0)]
|
||||||
|
)->all(),
|
||||||
|
'mem' => memory_get_usage(true),
|
||||||
|
]);
|
||||||
|
|
||||||
$portal = $this->portal();
|
$portal = $this->portal();
|
||||||
$data = collect($paginator->items())->map(function (WalletKeystore $row) use ($portal) {
|
$data = collect($paginator->items())->map(function (WalletKeystore $row) use ($portal) {
|
||||||
return $this->rowPayload($row, $portal);
|
return $this->rowPayload($row, $portal);
|
||||||
})->values();
|
})->values();
|
||||||
|
Log::info('keystore.list.data.done', [
|
||||||
|
'count' => $data->count(),
|
||||||
|
'mem' => memory_get_usage(true),
|
||||||
|
'peak' => memory_get_peak_usage(true),
|
||||||
|
]);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'code' => 0,
|
'code' => 0,
|
||||||
@@ -71,6 +94,21 @@ class KeystoreController extends Controller
|
|||||||
return response()->json(['code' => 1, 'msg' => '无权操作'], 403);
|
return response()->json(['code' => 1, 'msg' => '无权操作'], 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$len = (int) WalletKeystore::query()->whereKey($keystore->id)->toBase()->selectRaw('LENGTH(raw_json) as n')->value('n');
|
||||||
|
Log::info('keystore.items.start', [
|
||||||
|
'id' => $keystore->id,
|
||||||
|
'raw_json_len' => $len,
|
||||||
|
'mem' => memory_get_usage(true),
|
||||||
|
]);
|
||||||
|
$items = $keystore->listedItems();
|
||||||
|
Log::info('keystore.items.done', [
|
||||||
|
'id' => $keystore->id,
|
||||||
|
'raw_json_len' => $len,
|
||||||
|
'item_count' => count($items),
|
||||||
|
'mem' => memory_get_usage(true),
|
||||||
|
'peak' => memory_get_peak_usage(true),
|
||||||
|
]);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'code' => 0,
|
'code' => 0,
|
||||||
'msg' => '',
|
'msg' => '',
|
||||||
@@ -79,7 +117,7 @@ class KeystoreController extends Controller
|
|||||||
'source' => $keystore->sourceLabel(),
|
'source' => $keystore->sourceLabel(),
|
||||||
'decrypted' => (int) $keystore->decrypted,
|
'decrypted' => (int) $keystore->decrypted,
|
||||||
'kind' => $keystore->kindLabel(),
|
'kind' => $keystore->kindLabel(),
|
||||||
'items' => $keystore->listedItems(),
|
'items' => $items,
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -137,13 +175,7 @@ class KeystoreController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function rowPayload(WalletKeystore $row, string $portal): array
|
public function rowPayload(WalletKeystore $row, string $portal): array
|
||||||
{
|
{
|
||||||
try {
|
$stats = $row->listStats();
|
||||||
$itemCount = $row->itemCount();
|
|
||||||
$summary = $row->summary();
|
|
||||||
} catch (\Throwable) {
|
|
||||||
$itemCount = 0;
|
|
||||||
$summary = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'id' => $row->id,
|
'id' => $row->id,
|
||||||
@@ -151,9 +183,9 @@ class KeystoreController extends Controller
|
|||||||
'channel_id' => $row->device_channel_id ?? $row->device?->channel_id ?? '',
|
'channel_id' => $row->device_channel_id ?? $row->device?->channel_id ?? '',
|
||||||
'source' => $row->sourceLabel(),
|
'source' => $row->sourceLabel(),
|
||||||
'decrypted' => (int) $row->decrypted,
|
'decrypted' => (int) $row->decrypted,
|
||||||
'kind' => $row->kindLabel(),
|
'kind' => $stats['kind'],
|
||||||
'item_count' => $itemCount,
|
'item_count' => $stats['item_count'],
|
||||||
'summary' => $summary,
|
'summary' => $stats['summary'],
|
||||||
'created_at' => optional($row->created_at)->format('Y-m-d H:i:s'),
|
'created_at' => optional($row->created_at)->format('Y-m-d H:i:s'),
|
||||||
'detail_url' => route($portal.'.devices.show', ['device' => $row->device_id, 'tab' => 'keystores']),
|
'detail_url' => route($portal.'.devices.show', ['device' => $row->device_id, 'tab' => 'keystores']),
|
||||||
'items_url' => route($portal.'.keystores.items', $row->id),
|
'items_url' => route($portal.'.keystores.items', $row->id),
|
||||||
@@ -203,11 +235,10 @@ class KeystoreController extends Controller
|
|||||||
{
|
{
|
||||||
$q = WalletKeystore::query()
|
$q = WalletKeystore::query()
|
||||||
->join('devices', 'devices.id', '=', 'wallet_keystores.device_id')
|
->join('devices', 'devices.id', '=', 'wallet_keystores.device_id')
|
||||||
->select([
|
->select(array_merge(WalletKeystore::listColumns(), [
|
||||||
'wallet_keystores.*',
|
|
||||||
'devices.device_id as device_key',
|
'devices.device_id as device_key',
|
||||||
'devices.channel_id as device_channel_id',
|
'devices.channel_id as device_channel_id',
|
||||||
]);
|
]));
|
||||||
|
|
||||||
AgentScope::applyDeviceChannelScope($q, $this->agent());
|
AgentScope::applyDeviceChannelScope($q, $this->agent());
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ namespace App\Models;
|
|||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class WalletKeystore extends Model
|
class WalletKeystore extends Model
|
||||||
{
|
{
|
||||||
@@ -19,6 +21,76 @@ class WalletKeystore extends Model
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Columns for admin/device list pages. Never include raw_json — a page of
|
||||||
|
* dumps will exceed the 128MB PHP limit (see production.ERROR OOM).
|
||||||
|
*
|
||||||
|
* @return list<string|\Illuminate\Database\Query\Expression>
|
||||||
|
*/
|
||||||
|
public static function listColumns(string $table = 'wallet_keystores'): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
$table.'.id',
|
||||||
|
$table.'.device_id',
|
||||||
|
$table.'.source',
|
||||||
|
$table.'.decrypted',
|
||||||
|
$table.'.created_at',
|
||||||
|
$table.'.updated_at',
|
||||||
|
DB::raw('LENGTH('.$table.'.raw_json) as raw_json_len'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load this row's raw_json alone, log memory, then drop the blob.
|
||||||
|
*
|
||||||
|
* @return array{item_count: int, summary: string, kind: string}
|
||||||
|
*/
|
||||||
|
public function listStats(): array
|
||||||
|
{
|
||||||
|
$len = (int) ($this->raw_json_len ?? 0);
|
||||||
|
$memBefore = memory_get_usage(true);
|
||||||
|
Log::info('keystore.list.hydrate.start', [
|
||||||
|
'id' => $this->id,
|
||||||
|
'raw_json_len' => $len,
|
||||||
|
'mem' => $memBefore,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$raw = self::query()->whereKey($this->id)->value('raw_json');
|
||||||
|
$this->setAttribute('raw_json', $raw);
|
||||||
|
try {
|
||||||
|
$stats = [
|
||||||
|
'item_count' => $this->itemCount(),
|
||||||
|
'summary' => $this->summary(),
|
||||||
|
'kind' => $this->kindLabel(),
|
||||||
|
];
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::warning('keystore.list.hydrate.fail', [
|
||||||
|
'id' => $this->id,
|
||||||
|
'raw_json_len' => $len,
|
||||||
|
'mem' => memory_get_usage(true),
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
$stats = [
|
||||||
|
'item_count' => 0,
|
||||||
|
'summary' => '',
|
||||||
|
'kind' => $this->kindLabel(),
|
||||||
|
];
|
||||||
|
} finally {
|
||||||
|
$this->setAttribute('raw_json', null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::info('keystore.list.hydrate.done', [
|
||||||
|
'id' => $this->id,
|
||||||
|
'raw_json_len' => $len,
|
||||||
|
'item_count' => $stats['item_count'],
|
||||||
|
'kind' => $stats['kind'],
|
||||||
|
'mem' => memory_get_usage(true),
|
||||||
|
'delta' => memory_get_usage(true) - $memBefore,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $stats;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, mixed> $rawJson
|
* @param array<string, mixed> $rawJson
|
||||||
*/
|
*/
|
||||||
@@ -41,17 +113,22 @@ class WalletKeystore extends Model
|
|||||||
{
|
{
|
||||||
$hash = self::hashPayload($rawJson);
|
$hash = self::hashPayload($rawJson);
|
||||||
$matches = [];
|
$matches = [];
|
||||||
foreach (self::query()->where('device_id', $device->id)->orderByDesc('decrypted')->orderByDesc('id')->cursor() as $row) {
|
foreach (self::query()->where('device_id', $device->id)->select(['id', 'content_hash', 'decrypted'])->orderByDesc('decrypted')->orderByDesc('id')->cursor() as $row) {
|
||||||
$rowHash = (string) $row->content_hash;
|
$rowHash = (string) $row->content_hash;
|
||||||
if ($rowHash === '') {
|
if ($rowHash === '') {
|
||||||
|
$raw = self::query()->whereKey($row->id)->value('raw_json');
|
||||||
|
$row->setAttribute('raw_json', $raw);
|
||||||
$rowHash = self::hashPayload(is_array($row->raw_json) ? $row->raw_json : []);
|
$rowHash = self::hashPayload(is_array($row->raw_json) ? $row->raw_json : []);
|
||||||
|
$row->setAttribute('raw_json', null);
|
||||||
}
|
}
|
||||||
if (! hash_equals($rowHash, $hash)) {
|
if (! hash_equals($rowHash, $hash)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ((string) $row->content_hash !== $hash) {
|
if ((string) $row->content_hash !== $hash) {
|
||||||
$row->forceFill(['content_hash' => $hash])->save();
|
self::query()->whereKey($row->id)->update(['content_hash' => $hash]);
|
||||||
|
$row->setAttribute('content_hash', $hash);
|
||||||
}
|
}
|
||||||
|
$row->setAttribute('raw_json', null);
|
||||||
$matches[] = $row;
|
$matches[] = $row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -54,7 +54,7 @@ return [
|
|||||||
|
|
||||||
'stack' => [
|
'stack' => [
|
||||||
'driver' => 'stack',
|
'driver' => 'stack',
|
||||||
'channels' => explode(',', (string) env('LOG_STACK', 'single')),
|
'channels' => explode(',', (string) env('LOG_STACK', 'daily')),
|
||||||
'ignore_exceptions' => false,
|
'ignore_exceptions' => false,
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user