perf(host): store avatars as content-addressed files and serve them from disk
Space info now carries avatar_version instead of the inlined thumbnail; the worker fetches the thumbnail once per version. Legacy base64 rows are migrated to files on startup, and the daily backup uploads avatar files. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> [CI]
This commit is contained in:
1 parent
8af4f284a0
commit
7274390aa1
21 files changed
+339
-48
No files matched your search
@@ -50,6 +50,20 @@ public sealed class BackupWorker(WebDropOptions options, SqliteChannelStore stor
|
||||
foreach (var path in new[] { db, gz }) try { File.Delete(path); } catch (IOException) { }
|
||||
}
|
||||
|
||||
// 头像是按内容哈希命名的磁盘文件(不在数据库里):内容不会变,已经上传过的跳过
|
||||
foreach (var kind in new[] { "space", "account" })
|
||||
{
|
||||
string directory = Path.Combine(options.DataDir, "v2", "avatars", kind);
|
||||
if (!Directory.Exists(directory)) continue;
|
||||
var uploaded = (await s3.ListAsync($"chat/avatars/{kind}", ct).ConfigureAwait(false)).ToHashSet();
|
||||
foreach (var file in Directory.EnumerateFiles(directory))
|
||||
{
|
||||
if (file.EndsWith(".tmp", StringComparison.Ordinal) || uploaded.Contains(Path.GetFileName(file))) continue;
|
||||
await using var content = File.OpenRead(file);
|
||||
await s3.PutAsync($"chat/avatars/{kind}/{Path.GetFileName(file)}", content, ct).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
// 保留期:按文件名里的日期清理更早的
|
||||
int purged = 0;
|
||||
var cutoff = now.Date.AddDays(-Math.Max(0, backup.RetainDays));
|
||||
|
||||
Reference in new issue
Block a user