통계 분석(전년대비·월별·계절별), 수급계획·LOT 수불, 지정판매소·실사·메뉴 링크 등을 포함한다. Co-authored-by: Cursor <cursoragent@cursor.com>
107 lines
3.4 KiB
PHP
107 lines
3.4 KiB
PHP
<?php
|
|
|
|
namespace App\Libraries\Blockchain;
|
|
|
|
use App\Models\BlockchainLedgerModel;
|
|
|
|
class SqlLedger
|
|
{
|
|
private BlockchainLedgerModel $ledgerModel;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->ledgerModel = model(BlockchainLedgerModel::class);
|
|
}
|
|
|
|
/**
|
|
* @param array<string,mixed> $payload
|
|
* @return array{index:int,hash:string,previous_hash:string}
|
|
*/
|
|
public function appendBlock(string $txType, array $payload, ?string $entityUuid, int $entityVersion, ?int $actorIdx, ?int $lgIdx): array
|
|
{
|
|
$latest = $this->ledgerModel->orderBy('bl_idx', 'DESC')->first();
|
|
// 원장이 비어 있으면 $latest 가 null — $latest->bl_hash 는 PHP 8에서 치명 오류(? 는 ?? 와 달리 속성 접근 자체가 먼저 평가됨)
|
|
$previousHash = ($latest === null || ! isset($latest->bl_hash) || (string) $latest->bl_hash === '')
|
|
? str_repeat('0', 64)
|
|
: (string) $latest->bl_hash;
|
|
$now = date('Y-m-d H:i:s');
|
|
|
|
$normalizedPayload = $this->normalizeArray($payload);
|
|
$payloadJson = json_encode($normalizedPayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: '{}';
|
|
|
|
$hashInput = implode('|', [
|
|
$now,
|
|
$txType,
|
|
$entityUuid ?? '',
|
|
(string) $entityVersion,
|
|
$payloadJson,
|
|
$previousHash,
|
|
'0',
|
|
]);
|
|
$currentHash = hash('sha256', $hashInput);
|
|
|
|
$this->ledgerModel->insert([
|
|
'bl_created_at' => $now,
|
|
'bl_tx_type' => $txType,
|
|
'bl_entity_uuid' => $entityUuid,
|
|
'bl_entity_version' => $entityVersion,
|
|
'bl_payload' => $payloadJson,
|
|
'bl_previous_hash' => $previousHash,
|
|
'bl_hash' => $currentHash,
|
|
'bl_nonce' => 0,
|
|
'bl_actor_idx' => $actorIdx,
|
|
'bl_lg_idx' => $lgIdx,
|
|
]);
|
|
|
|
return [
|
|
'index' => (int) $this->ledgerModel->getInsertID(),
|
|
'hash' => $currentHash,
|
|
'previous_hash' => $previousHash,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param array<string,mixed> $data
|
|
* @return array<string,mixed>
|
|
*/
|
|
private function normalizeArray(array $data): array
|
|
{
|
|
ksort($data);
|
|
foreach ($data as $key => $value) {
|
|
if (is_array($value)) {
|
|
if ($this->isAssoc($value)) {
|
|
/** @var array<string,mixed> $assoc */
|
|
$assoc = $value;
|
|
$data[$key] = $this->normalizeArray($assoc);
|
|
} else {
|
|
$normalizedList = [];
|
|
foreach ($value as $item) {
|
|
if (is_array($item) && $this->isAssoc($item)) {
|
|
/** @var array<string,mixed> $assoc */
|
|
$assoc = $item;
|
|
$normalizedList[] = $this->normalizeArray($assoc);
|
|
} else {
|
|
$normalizedList[] = $item;
|
|
}
|
|
}
|
|
$data[$key] = $normalizedList;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* @param array<mixed> $array
|
|
*/
|
|
private function isAssoc(array $array): bool
|
|
{
|
|
if ($array === []) {
|
|
return false;
|
|
}
|
|
|
|
return array_keys($array) !== range(0, count($array) - 1);
|
|
}
|
|
}
|