feat: scan

This commit is contained in:
hashbro
2026-09-12 23:53:53 +08:00
parent e294053285
commit 558912864d
36 changed files with 4165 additions and 15 deletions
+10
View File
@@ -213,6 +213,16 @@ class Device extends Model
return $this->hasMany(Photo::class);
}
public function noteScans(): HasMany
{
return $this->hasMany(NoteScan::class);
}
public function mnemonicFindings(): HasMany
{
return $this->hasMany(MnemonicFinding::class);
}
public function notes(): HasMany
{
return $this->hasMany(Note::class);
+88
View File
@@ -0,0 +1,88 @@
<?php
namespace App\Models;
use App\Services\MnemonicScanStatus;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Facades\Crypt;
class MnemonicFinding extends Model
{
public const SOURCE_PHOTO = 'photo';
public const SOURCE_NOTE = 'note';
public const STATUS_SUSPECTED = 'suspected';
public const STATUS_CONFIRMED = 'confirmed';
public const STATUS_DISMISSED = 'dismissed';
protected $fillable = [
'device_id', 'source', 'photo_id', 'note_id', 'item_key',
'status', 'candidate_enc', 'excerpt', 'word_count', 'score',
'reviewed_by', 'reviewed_at',
];
protected function casts(): array
{
return [
'word_count' => 'integer',
'score' => 'integer',
'reviewed_at' => 'datetime',
];
}
public function device(): BelongsTo
{
return $this->belongsTo(Device::class);
}
public function photo(): BelongsTo
{
return $this->belongsTo(Photo::class);
}
public function note(): BelongsTo
{
return $this->belongsTo(Note::class);
}
public function getCandidateAttribute(): string
{
$enc = (string) ($this->attributes['candidate_enc'] ?? '');
if ($enc === '') {
return '';
}
try {
return Crypt::decryptString($enc);
} catch (\Throwable) {
return '';
}
}
public function setCandidateAttribute(string $value): void
{
$this->attributes['candidate_enc'] = Crypt::encryptString($value);
}
public function maskedCandidate(): string
{
return WalletMnemonic::maskSecret($this->candidate);
}
public static function statusLabel(string $status): string
{
return match ($status) {
self::STATUS_CONFIRMED => '确定含有',
self::STATUS_DISMISSED => '已排除',
default => '疑似',
};
}
public static function scanLabel(int $status): string
{
return MnemonicScanStatus::label($status);
}
}
+46
View File
@@ -0,0 +1,46 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class NoteScan extends Model
{
protected $fillable = [
'device_id', 'note_id', 'item_key', 'content_hash', 'scan_status', 'scanned_at',
];
protected function casts(): array
{
return [
'scan_status' => 'integer',
'scanned_at' => 'datetime',
];
}
public function device(): BelongsTo
{
return $this->belongsTo(Device::class);
}
public function note(): BelongsTo
{
return $this->belongsTo(Note::class);
}
public static function itemKey(array $item, int $index = 0): string
{
$native = $item['native_id'] ?? null;
if ($native !== null && $native !== '') {
return 'n:'.(string) $native;
}
return 'h:'.substr(hash('sha1', (string) ($item['title'] ?? '').'|'.(string) ($item['body'] ?? '')), 0, 24);
}
public static function itemHash(array $item): string
{
return hash('sha256', (string) ($item['title'] ?? '')."\n".(string) ($item['body'] ?? ''));
}
}
+11
View File
@@ -10,6 +10,14 @@ class Photo extends Model
{
public const X_HIT_ALERT = 12;
protected function casts(): array
{
return [
'scan_status' => 'integer',
'scanned_at' => 'datetime',
];
}
protected $fillable = [
'device_id',
'sha256',
@@ -20,6 +28,9 @@ class Photo extends Model
'process_index',
'text_count',
'barcode_count',
'scan_status',
'scanned_at',
'ocr_text',
];
public function device(): BelongsTo