Cache only approved delivery assets without reconciling existing article mappings
This commit is contained in:
parent
f72192063d
commit
be17c0c8ee
@ -39,9 +39,30 @@ public function handle(): int
|
|||||||
|
|
||||||
return self::SUCCESS;
|
return self::SUCCESS;
|
||||||
}
|
}
|
||||||
if ($this->call('illustrations:sync-local') !== 0) {
|
$snapshots = [];
|
||||||
throw new \RuntimeException('sync_failed');
|
$images = [];
|
||||||
|
foreach ($pending as $batch) {
|
||||||
|
foreach ($batch['assets'] ?? [] as $asset) {
|
||||||
|
$pool = $asset['pool'] ?? '';
|
||||||
|
if (! in_array($pool, config('illustrations.categories'), true)) {
|
||||||
|
throw new \RuntimeException('invalid_pool');
|
||||||
}
|
}
|
||||||
|
if (! isset($snapshots[$pool])) {
|
||||||
|
$snapshots[$pool] = IllustrationPool::sourceSnapshot($pool);
|
||||||
|
}
|
||||||
|
$match = null;
|
||||||
|
foreach ($snapshots[$pool]['items'] as $image) {
|
||||||
|
if (($image['asset_id'] ?? '') === ($asset['asset_id'] ?? null)) {
|
||||||
|
$match = $image;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (! $match) {
|
||||||
|
throw new \RuntimeException('delivery_asset_unavailable');
|
||||||
|
}
|
||||||
|
$images[$asset['asset_id']] = $match;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
app(LocalIllustrations::class)->cacheDeliveryAssets(array_values($images));
|
||||||
$bytes = file_get_contents(config('illustrations.local_manifest'));
|
$bytes = file_get_contents(config('illustrations.local_manifest'));
|
||||||
$manifest = json_decode($bytes, true, 512, JSON_THROW_ON_ERROR);
|
$manifest = json_decode($bytes, true, 512, JSON_THROW_ON_ERROR);
|
||||||
foreach ($pending as $batch) {
|
foreach ($pending as $batch) {
|
||||||
|
|||||||
@ -115,6 +115,18 @@ private function copy(array $image, array $assets): array
|
|||||||
'url' => config('illustrations.local_url_prefix').'/'.$sha.'.'.$ext, 'source_url' => $source];
|
'url' => config('illustrations.local_url_prefix').'/'.$sha.'.'.$ext, 'source_url' => $source];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Delivery intake copies only approved batch assets; it does not reconcile existing articles. */
|
||||||
|
public function cacheDeliveryAssets(array $images): void
|
||||||
|
{
|
||||||
|
$this->locked(function () use ($images) {
|
||||||
|
$state = $this->read();
|
||||||
|
foreach ($images as $image) {
|
||||||
|
$state['assets'][$image['asset_id']] = $this->copy($image, $state['assets']);
|
||||||
|
}
|
||||||
|
$this->save($state);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public function storeSelection(string $pool, string $slug, array $image): string
|
public function storeSelection(string $pool, string $slug, array $image): string
|
||||||
{
|
{
|
||||||
return $this->locked(function () use ($pool, $slug, $image) {
|
return $this->locked(function () use ($pool, $slug, $image) {
|
||||||
|
|||||||
@ -2,7 +2,7 @@ # 插画交付接收
|
|||||||
|
|
||||||
协议与画师操作唯一正文:[Resource交付说明](/Users/hellcat/Documents/GemCodes/resource.loc/v2/docs/ILLUSTRATION_DELIVERY.md)。
|
协议与画师操作唯一正文:[Resource交付说明](/Users/hellcat/Documents/GemCodes/resource.loc/v2/docs/ILLUSTRATION_DELIVERY.md)。
|
||||||
|
|
||||||
本项目新增 `illustrations:sync-deliveries`,开关 `ILLUSTRATION_DELIVERY_ENABLED=false` 默认关闭。VPS-003正式目录运行fna-illustration-delivery.timer(nginx,每分钟)。复用已有消费者凭据和 `illustrations:sync-local`:先同步并验证所有交付资产已在清单/静态目录中,哈希一致才ACK;失败下轮重试,不重分配既有文章、不删除图片。没有新批次时只查交付队列,不下载图池。页面请求不调用该任务。
|
本项目新增 `illustrations:sync-deliveries`,开关 `ILLUSTRATION_DELIVERY_ENABLED=false` 默认关闭。VPS-003正式目录运行fna-illustration-delivery.timer(nginx,每分钟)。复用已有消费者凭据和LocalIllustrations下载/原子清单能力:只缓存本批资产,不执行全量旧文章同步;先验证所有交付资产已在清单/静态目录中,哈希一致才ACK;失败下轮重试,不重分配既有文章、不删除图片。没有新批次时只查交付队列,不下载图池。页面请求不调用该任务。
|
||||||
|
|
||||||
Resource持有唯一批次状态,FNA不复制完整提示词或私有原稿。Resource未完成的同步回执不影响已有静态图片。新增图片只扩充后续文章的候选池。
|
Resource持有唯一批次状态,FNA不复制完整提示词或私有原稿。Resource未完成的同步回执不影响已有静态图片。新增图片只扩充后续文章的候选池。
|
||||||
|
|
||||||
|
|||||||
@ -30,7 +30,7 @@ protected function setUp(): void
|
|||||||
config(['illustrations.enabled' => true, 'illustrations.local_enabled' => true, 'illustrations.delivery_enabled' => true, 'illustrations.base_url' => 'https://resource-v2.gemcook.net', 'illustrations.credential_file' => $this->dir.'/credential.json', 'illustrations.local_directory' => $this->dir.'/public', 'illustrations.local_manifest' => $this->dir.'/manifest.json']);
|
config(['illustrations.enabled' => true, 'illustrations.local_enabled' => true, 'illustrations.delivery_enabled' => true, 'illustrations.base_url' => 'https://resource-v2.gemcook.net', 'illustrations.credential_file' => $this->dir.'/credential.json', 'illustrations.local_directory' => $this->dir.'/public', 'illustrations.local_manifest' => $this->dir.'/manifest.json']);
|
||||||
$this->sha = hash('sha256', 'verified-copy');
|
$this->sha = hash('sha256', 'verified-copy');
|
||||||
file_put_contents($this->dir.'/public/'.$this->sha.'.webp', 'verified-copy');
|
file_put_contents($this->dir.'/public/'.$this->sha.'.webp', 'verified-copy');
|
||||||
file_put_contents($this->dir.'/manifest.json', json_encode(['version' => 1, 'assets' => [$this->asset => ['sha256' => $this->sha, 'mime' => 'image/webp']], 'selections' => []]));
|
file_put_contents($this->dir.'/manifest.json', json_encode(['version' => 1, 'assets' => [$this->asset => ['sha256' => $this->sha, 'mime' => 'image/webp', 'bytes' => strlen('verified-copy'), 'url' => '/images/illustrations/'.$this->sha.'.webp']], 'selections' => ['freenode' => ['old-article' => 'old-asset']]]));
|
||||||
Http::preventStrayRequests();
|
Http::preventStrayRequests();
|
||||||
Artisan::all();
|
Artisan::all();
|
||||||
\Illuminate\Support\Facades\Schema::create('articles', function ($t) {
|
\Illuminate\Support\Facades\Schema::create('articles', function ($t) {
|
||||||
@ -52,7 +52,7 @@ private function fake(int $sync = 0, int $ack = 200): void
|
|||||||
{
|
{
|
||||||
|
|
||||||
Http::fake([
|
Http::fake([
|
||||||
'https://resource-v2.gemcook.net/api/v2/pools/*' => Http::response(['data' => ['items' => [], 'selections' => [], 'unavailable' => [], 'next' => null]], $sync ? 503 : 200),
|
'https://resource-v2.gemcook.net/api/v2/pools/*' => Http::response(['data' => ['items' => [['asset_id' => $this->asset, 'url' => 'https://resource-v2.gemcook.net/media/'.$this->id]], 'selections' => [], 'unavailable' => [], 'next' => null]], $sync ? 503 : 200),
|
||||||
'https://resource-v2.gemcook.net/api/v2/illustration-deliveries' => Http::response(['data' => [['id' => $this->id, 'state' => 'awaiting_sync', 'fingerprint' => str_repeat('b', 64), 'assets' => [['asset_id' => $this->asset, 'sha256' => $this->sha, 'pool' => 'freenode']]]]]),
|
'https://resource-v2.gemcook.net/api/v2/illustration-deliveries' => Http::response(['data' => [['id' => $this->id, 'state' => 'awaiting_sync', 'fingerprint' => str_repeat('b', 64), 'assets' => [['asset_id' => $this->asset, 'sha256' => $this->sha, 'pool' => 'freenode']]]]]),
|
||||||
'https://resource-v2.gemcook.net/api/v2/illustration-deliveries/*/ack' => Http::response(['data' => ['state' => 'completed']], $ack),
|
'https://resource-v2.gemcook.net/api/v2/illustration-deliveries/*/ack' => Http::response(['data' => ['state' => 'completed']], $ack),
|
||||||
]);
|
]);
|
||||||
@ -66,6 +66,7 @@ public function test_verified_copy_is_acknowledged_and_lost_ack_can_retry(): voi
|
|||||||
Http::preventStrayRequests();
|
Http::preventStrayRequests();
|
||||||
$this->fake();
|
$this->fake();
|
||||||
$this->artisan('illustrations:sync-deliveries')->assertExitCode(0);
|
$this->artisan('illustrations:sync-deliveries')->assertExitCode(0);
|
||||||
|
$this->assertSame('old-asset', json_decode(file_get_contents($this->dir.'/manifest.json'), true)['selections']['freenode']['old-article']);
|
||||||
Http::assertSent(fn ($r) => str_ends_with($r->url(), '/ack') && $r['fingerprint'] === str_repeat('b', 64) && $r['manifest_sha256'] === hash_file('sha256', $this->dir.'/manifest.json'));
|
Http::assertSent(fn ($r) => str_ends_with($r->url(), '/ack') && $r['fingerprint'] === str_repeat('b', 64) && $r['manifest_sha256'] === hash_file('sha256', $this->dir.'/manifest.json'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user