사이트·관리자 봉투 물류 기능(수불·통계·레포트·재고·발주)과 DB·메뉴·E2E를 운영 반영한다.
통계 분석(전년대비·월별·계절별), 수급계획·LOT 수불, 지정판매소·실사·메뉴 링크 등을 포함한다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
106
app/Libraries/Blockchain/SqlLedger.php
Normal file
106
app/Libraries/Blockchain/SqlLedger.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user