feat: 视频缩略图生成、最近文件面板与前端视图重构
- 新增 VideoThumbnailService,基于 ffmpeg 截取视频缩略图,ffprobe 提取时长
- 新增 ManagedThumbnailMap 模型及多数据库迁移,存储缩略图元数据
- 新增 /api/thumbnails/{id} 缩略图流端点
- 新增最近添加/最近播放 API 与前端面板,支持列表/网格双视图切换
- FileRecordDto 扩展 thumbnailUrl、videoDuration、lastPlayedAt 字段
- 前端新增文件库 Tab 导航、卡片网格视图、视频海报与时长信息栏
- 添加文件库目录不再同步全量扫描,改为后台异步自动扫描
This commit is contained in:
parent
6acc92ca27
commit
2c20f9bb54
1
.gitignore
vendored
1
.gitignore
vendored
@ -32,3 +32,4 @@
|
||||
/bin
|
||||
/obj
|
||||
/.claude
|
||||
/.codex-build
|
||||
|
||||
@ -40,6 +40,14 @@ namespace FileShare_API.Configuration
|
||||
|
||||
// ---- 业务服务 ----
|
||||
services.AddScoped<WeatherForecastService>();
|
||||
var thumbnailOptions = configuration
|
||||
.GetSection(nameof(ThumbnailStorageOptions))
|
||||
.Get<ThumbnailStorageOptions>()
|
||||
?? new ThumbnailStorageOptions();
|
||||
services.AddSingleton(thumbnailOptions);
|
||||
services.AddSingleton<IVideoThumbnailService>(sp =>
|
||||
new VideoThumbnailService(sp.GetRequiredService<ThumbnailStorageOptions>()));
|
||||
services.AddScoped<IThumbnailStreamService, ThumbnailStreamService>();
|
||||
services.AddScoped<IFileLibraryService, FileLibraryService>();
|
||||
services.AddScoped<IFileLibraryEndpointService, FileLibraryEndpointService>();
|
||||
services.AddScoped<IFileStreamService, FileStreamService>();
|
||||
|
||||
@ -43,6 +43,35 @@ namespace FileShare_API.Extensions
|
||||
.WithName("StreamManagedFileById")
|
||||
.WithTags("FileLibrary");
|
||||
|
||||
app.MapMethods(
|
||||
"/api/thumbnails/{id:int}",
|
||||
["GET", "HEAD"],
|
||||
async (int id, IThumbnailStreamService thumbnailStreamService, HttpContext httpContext) =>
|
||||
{
|
||||
var thumbnail = await thumbnailStreamService.GetThumbnailAsync(id);
|
||||
if (thumbnail is null)
|
||||
{
|
||||
return Results.NotFound();
|
||||
}
|
||||
|
||||
var stream = System.IO.File.Open(
|
||||
thumbnail.FilePath,
|
||||
FileMode.Open,
|
||||
FileAccess.Read,
|
||||
FileShare.ReadWrite);
|
||||
|
||||
httpContext.Response.Headers.ContentDisposition =
|
||||
$"inline; filename=\"{Uri.EscapeDataString(thumbnail.FileName)}\"";
|
||||
httpContext.Response.Headers.CacheControl = "public, max-age=3600";
|
||||
|
||||
return Results.File(
|
||||
stream,
|
||||
contentType: thumbnail.ContentType,
|
||||
lastModified: thumbnail.LastModified);
|
||||
})
|
||||
.WithName("StreamManagedThumbnailById")
|
||||
.WithTags("FileLibrary");
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,6 +26,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Controllers\" />
|
||||
<Content Include="..\tools\ffmpeg\bin\ffmpeg.exe" Link="tools\ffmpeg\bin\ffmpeg.exe" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" />
|
||||
<Content Include="..\tools\ffmpeg\bin\ffprobe.exe" Link="tools\ffmpeg\bin\ffprobe.exe" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="RestoreFrontendPackages" BeforeTargets="Build" Condition="'$(SkipFrontendBuild)' != 'true' And Exists('..\FileShare-Web-VUE\package.json') And !Exists('..\FileShare-Web-VUE\node_modules')">
|
||||
|
||||
@ -24,5 +24,10 @@
|
||||
"RecreateDatabase": false,
|
||||
"EnableDetailedLog": false,
|
||||
"Timeout": 30
|
||||
},
|
||||
"ThumbnailStorageOptions": {
|
||||
"RootPath": "thumbnails",
|
||||
"FfmpegPath": "tools/ffmpeg/bin/ffmpeg.exe",
|
||||
"FfprobePath": "tools/ffmpeg/bin/ffprobe.exe"
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,6 +25,8 @@ namespace FileShare_EFCore.Database
|
||||
/// <summary>文件库文件记录数据</summary>
|
||||
public DbSet<ManagedFileRecord> ManagedFileRecords => Set<ManagedFileRecord>();
|
||||
|
||||
public DbSet<ManagedThumbnailMap> ManagedThumbnailMaps => Set<ManagedThumbnailMap>();
|
||||
|
||||
/// <summary>
|
||||
/// 配置实体映射,包括主键、索引和属性约束。
|
||||
/// </summary>
|
||||
@ -66,8 +68,10 @@ namespace FileShare_EFCore.Database
|
||||
{
|
||||
entity.HasKey(e => e.Id).HasName("pk-managed-file-record");
|
||||
entity.HasIndex(e => e.LibraryRootId).HasDatabaseName("idx-managed-file-record-root-id");
|
||||
entity.HasIndex(e => e.ThumbnailId).HasDatabaseName("idx-managed-file-record-thumbnail-id");
|
||||
entity.HasIndex(e => e.AbsolutePath).IsUnique().HasDatabaseName("idx-managed-file-record-absolute-path");
|
||||
entity.HasIndex(e => new { e.MediaType, e.Exists }).HasDatabaseName("idx-managed-file-record-media-type-exists");
|
||||
entity.HasIndex(e => e.LastPlayedAt).HasDatabaseName("idx-managed-file-record-last-played-at");
|
||||
entity.Property(e => e.FileName).HasMaxLength(260);
|
||||
entity.Property(e => e.RelativePath).HasMaxLength(1024);
|
||||
entity.Property(e => e.AbsolutePath).HasMaxLength(2048);
|
||||
@ -78,6 +82,23 @@ namespace FileShare_EFCore.Database
|
||||
.WithMany(e => e.Files)
|
||||
.HasForeignKey(e => e.LibraryRootId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
entity.HasOne(e => e.Thumbnail)
|
||||
.WithMany(e => e.Files)
|
||||
.HasForeignKey(e => e.ThumbnailId)
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<ManagedThumbnailMap>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id).HasName("pk-managed-thumbnail-map");
|
||||
entity.HasIndex(e => e.LibraryRootId).HasDatabaseName("idx-managed-thumbnail-map-root-id");
|
||||
entity.HasIndex(e => e.RelativePath).IsUnique().HasDatabaseName("idx-managed-thumbnail-map-relative-path");
|
||||
entity.Property(e => e.RelativePath).HasMaxLength(1024);
|
||||
entity.Property(e => e.ContentType).HasMaxLength(100);
|
||||
entity.HasOne(e => e.LibraryRoot)
|
||||
.WithMany(e => e.Thumbnails)
|
||||
.HasForeignKey(e => e.LibraryRootId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
370
FileShare-EFCore/Migrations/MySQL/20260522082856_AutoMigration_20260522162758.Designer.cs
generated
Normal file
370
FileShare-EFCore/Migrations/MySQL/20260522082856_AutoMigration_20260522162758.Designer.cs
generated
Normal file
@ -0,0 +1,370 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using FileShare_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FileShare_EFCore.Migrations.MySQL
|
||||
{
|
||||
[DbContext(typeof(MySqlAppDataContext))]
|
||||
[Migration("20260522082856_AutoMigration_20260522162758")]
|
||||
partial class AutoMigration_20260522162758
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<string>("Device")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("varchar(200)")
|
||||
.HasColumnName("device");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("expires-at");
|
||||
|
||||
b.Property<string>("IpAddress")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("varchar(64)")
|
||||
.HasColumnName("ip-address");
|
||||
|
||||
b.Property<string>("ReplacedByTokenHash")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("varchar(128)")
|
||||
.HasColumnName("replaced-by-token-hash");
|
||||
|
||||
b.Property<DateTime?>("RevokedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("revoked-at");
|
||||
|
||||
b.Property<string>("TokenHash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("varchar(128)")
|
||||
.HasColumnName("token-hash");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("user-id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-api-refresh-token");
|
||||
|
||||
b.HasIndex("TokenHash")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-api-refresh-token-hash");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.HasDatabaseName("idx-api-refresh-token-user-id");
|
||||
|
||||
b.ToTable("api-refresh-token", t =>
|
||||
{
|
||||
t.HasComment("API refresh token");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("AbsolutePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("varchar(2048)")
|
||||
.HasColumnName("absolute-path");
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar(100)")
|
||||
.HasColumnName("content-type");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<bool>("Exists")
|
||||
.HasColumnType("tinyint(1)")
|
||||
.HasColumnName("exists");
|
||||
|
||||
b.Property<string>("Extension")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("varchar(32)")
|
||||
.HasColumnName("extension");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(260)
|
||||
.HasColumnType("varchar(260)")
|
||||
.HasColumnName("file-name");
|
||||
|
||||
b.Property<DateTime?>("LastPlayedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("last-played-at");
|
||||
|
||||
b.Property<DateTime>("LastSeenAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("last-seen-at");
|
||||
|
||||
b.Property<DateTime>("LastWriteTimeUtc")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("last-write-time-utc");
|
||||
|
||||
b.Property<int>("LibraryRootId")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("library-root-id");
|
||||
|
||||
b.Property<string>("MediaType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("varchar(20)")
|
||||
.HasColumnName("media-type");
|
||||
|
||||
b.Property<string>("RelativePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("varchar(1024)")
|
||||
.HasColumnName("relative-path");
|
||||
|
||||
b.Property<long>("SizeBytes")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("size-bytes");
|
||||
|
||||
b.Property<string>("ThumbnailPath")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("varchar(512)")
|
||||
.HasColumnName("thumbnail-path");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.Property<double?>("VideoDuration")
|
||||
.HasColumnType("double")
|
||||
.HasColumnName("video-duration");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-file-record");
|
||||
|
||||
b.HasIndex("AbsolutePath")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-file-record-absolute-path");
|
||||
|
||||
b.HasIndex("LastPlayedAt")
|
||||
.HasDatabaseName("idx-managed-file-record-last-played-at");
|
||||
|
||||
b.HasIndex("LibraryRootId")
|
||||
.HasDatabaseName("idx-managed-file-record-root-id");
|
||||
|
||||
b.HasIndex("MediaType", "Exists")
|
||||
.HasDatabaseName("idx-managed-file-record-media-type-exists");
|
||||
|
||||
b.ToTable("managed-file-record", t =>
|
||||
{
|
||||
t.HasComment("文件库文件记录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("varchar(200)")
|
||||
.HasColumnName("display-name");
|
||||
|
||||
b.Property<bool>("IsAvailable")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("tinyint(1)")
|
||||
.HasDefaultValue(true)
|
||||
.HasColumnName("is-available");
|
||||
|
||||
b.Property<bool>("IsEnabled")
|
||||
.HasColumnType("tinyint(1)")
|
||||
.HasColumnName("is-enabled");
|
||||
|
||||
b.Property<DateTime?>("LastScanCompletedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("last-scan-completed-at");
|
||||
|
||||
b.Property<string>("LastScanError")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("varchar(2000)")
|
||||
.HasColumnName("last-scan-error");
|
||||
|
||||
b.Property<DateTime?>("LastScanStartedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("last-scan-started-at");
|
||||
|
||||
b.Property<string>("Path")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("varchar(1024)")
|
||||
.HasColumnName("path");
|
||||
|
||||
b.Property<int>("ScanIntervalMinutes")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("scan-interval-minutes");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-library-root");
|
||||
|
||||
b.HasIndex("Path")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-library-root-path");
|
||||
|
||||
b.ToTable("managed-library-root", t =>
|
||||
{
|
||||
t.HasComment("文件库根目录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id")
|
||||
.HasComment("用户主键");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("created-at")
|
||||
.HasComment("创建时间");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("varchar(200)")
|
||||
.HasColumnName("email")
|
||||
.HasComment("用户邮箱");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar(100)")
|
||||
.HasColumnName("name")
|
||||
.HasComment("用户名称");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("varchar(200)")
|
||||
.HasColumnName("password-hash")
|
||||
.HasComment("密码哈希值");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("varchar(50)")
|
||||
.HasColumnName("phone-number")
|
||||
.HasComment("电话号码");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("updated-at")
|
||||
.HasComment("更新时间");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-user");
|
||||
|
||||
b.ToTable("user", t =>
|
||||
{
|
||||
t.HasComment("用户实体,演示数据库 CRUD 操作");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.WeatherForecastEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id")
|
||||
.HasComment("天气预报主键");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("created-at")
|
||||
.HasComment("创建时间");
|
||||
|
||||
b.Property<DateOnly>("Date")
|
||||
.HasColumnType("date")
|
||||
.HasColumnName("date")
|
||||
.HasComment("预报日期");
|
||||
|
||||
b.Property<string>("Summary")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("varchar(200)")
|
||||
.HasColumnName("summary")
|
||||
.HasComment("天气摘要");
|
||||
|
||||
b.Property<int>("TemperatureC")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("temperature-c")
|
||||
.HasComment("摄氏温度");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("updated-at")
|
||||
.HasComment("更新时间");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-weather-forecast");
|
||||
|
||||
b.ToTable("weather-forecast", t =>
|
||||
{
|
||||
t.HasComment("天气预报数据实体");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||
{
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("LibraryRootId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("LibraryRoot");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
{
|
||||
b.Navigation("Files");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using MySql.EntityFrameworkCore.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FileShare_EFCore.Migrations.MySQL
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AutoMigration_20260522162758 : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "managed-library-root",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn),
|
||||
path = table.Column<string>(type: "varchar(1024)", maxLength: 1024, nullable: false),
|
||||
displayname = table.Column<string>(name: "display-name", type: "varchar(200)", maxLength: 200, nullable: false),
|
||||
isenabled = table.Column<bool>(name: "is-enabled", type: "tinyint(1)", nullable: false),
|
||||
isavailable = table.Column<bool>(name: "is-available", type: "tinyint(1)", nullable: false, defaultValue: true),
|
||||
scanintervalminutes = table.Column<int>(name: "scan-interval-minutes", type: "int", nullable: false),
|
||||
lastscanstartedat = table.Column<DateTime>(name: "last-scan-started-at", type: "datetime(6)", nullable: true),
|
||||
lastscancompletedat = table.Column<DateTime>(name: "last-scan-completed-at", type: "datetime(6)", nullable: true),
|
||||
lastscanerror = table.Column<string>(name: "last-scan-error", type: "varchar(2000)", maxLength: 2000, nullable: true),
|
||||
createdat = table.Column<DateTime>(name: "created-at", type: "datetime(6)", nullable: false),
|
||||
updatedat = table.Column<DateTime>(name: "updated-at", type: "datetime(6)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk-managed-library-root", x => x.id);
|
||||
},
|
||||
comment: "文件库根目录")
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "managed-file-record",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn),
|
||||
libraryrootid = table.Column<int>(name: "library-root-id", type: "int", nullable: false),
|
||||
filename = table.Column<string>(name: "file-name", type: "varchar(260)", maxLength: 260, nullable: false),
|
||||
relativepath = table.Column<string>(name: "relative-path", type: "varchar(1024)", maxLength: 1024, nullable: false),
|
||||
absolutepath = table.Column<string>(name: "absolute-path", type: "varchar(2048)", maxLength: 2048, nullable: false),
|
||||
extension = table.Column<string>(type: "varchar(32)", maxLength: 32, nullable: false),
|
||||
sizebytes = table.Column<long>(name: "size-bytes", type: "bigint", nullable: false),
|
||||
lastwritetimeutc = table.Column<DateTime>(name: "last-write-time-utc", type: "datetime(6)", nullable: false),
|
||||
mediatype = table.Column<string>(name: "media-type", type: "varchar(20)", maxLength: 20, nullable: false),
|
||||
contenttype = table.Column<string>(name: "content-type", type: "varchar(100)", maxLength: 100, nullable: false),
|
||||
exists = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||
lastseenat = table.Column<DateTime>(name: "last-seen-at", type: "datetime(6)", nullable: false),
|
||||
thumbnailpath = table.Column<string>(name: "thumbnail-path", type: "varchar(512)", maxLength: 512, nullable: true),
|
||||
videoduration = table.Column<double>(name: "video-duration", type: "double", nullable: true),
|
||||
lastplayedat = table.Column<DateTime>(name: "last-played-at", type: "datetime(6)", nullable: true),
|
||||
createdat = table.Column<DateTime>(name: "created-at", type: "datetime(6)", nullable: false),
|
||||
updatedat = table.Column<DateTime>(name: "updated-at", type: "datetime(6)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk-managed-file-record", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "FK_managed-file-record_managed-library-root_library-root-id",
|
||||
column: x => x.libraryrootid,
|
||||
principalTable: "managed-library-root",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
},
|
||||
comment: "文件库文件记录")
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-file-record-absolute-path",
|
||||
table: "managed-file-record",
|
||||
column: "absolute-path",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-file-record-last-played-at",
|
||||
table: "managed-file-record",
|
||||
column: "last-played-at");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-file-record-media-type-exists",
|
||||
table: "managed-file-record",
|
||||
columns: new[] { "media-type", "exists" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-file-record-root-id",
|
||||
table: "managed-file-record",
|
||||
column: "library-root-id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-library-root-path",
|
||||
table: "managed-library-root",
|
||||
column: "path",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "managed-file-record");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "managed-library-root");
|
||||
}
|
||||
}
|
||||
}
|
||||
444
FileShare-EFCore/Migrations/MySQL/20260522084325_AddThumbnailMap.Designer.cs
generated
Normal file
444
FileShare-EFCore/Migrations/MySQL/20260522084325_AddThumbnailMap.Designer.cs
generated
Normal file
@ -0,0 +1,444 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using FileShare_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FileShare_EFCore.Migrations.MySQL
|
||||
{
|
||||
[DbContext(typeof(MySqlAppDataContext))]
|
||||
[Migration("20260522084325_AddThumbnailMap")]
|
||||
partial class AddThumbnailMap
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<string>("Device")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("varchar(200)")
|
||||
.HasColumnName("device");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("expires-at");
|
||||
|
||||
b.Property<string>("IpAddress")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("varchar(64)")
|
||||
.HasColumnName("ip-address");
|
||||
|
||||
b.Property<string>("ReplacedByTokenHash")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("varchar(128)")
|
||||
.HasColumnName("replaced-by-token-hash");
|
||||
|
||||
b.Property<DateTime?>("RevokedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("revoked-at");
|
||||
|
||||
b.Property<string>("TokenHash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("varchar(128)")
|
||||
.HasColumnName("token-hash");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("user-id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-api-refresh-token");
|
||||
|
||||
b.HasIndex("TokenHash")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-api-refresh-token-hash");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.HasDatabaseName("idx-api-refresh-token-user-id");
|
||||
|
||||
b.ToTable("api-refresh-token", t =>
|
||||
{
|
||||
t.HasComment("API refresh token");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("AbsolutePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("varchar(2048)")
|
||||
.HasColumnName("absolute-path");
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar(100)")
|
||||
.HasColumnName("content-type");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<bool>("Exists")
|
||||
.HasColumnType("tinyint(1)")
|
||||
.HasColumnName("exists");
|
||||
|
||||
b.Property<string>("Extension")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("varchar(32)")
|
||||
.HasColumnName("extension");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(260)
|
||||
.HasColumnType("varchar(260)")
|
||||
.HasColumnName("file-name");
|
||||
|
||||
b.Property<DateTime?>("LastPlayedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("last-played-at");
|
||||
|
||||
b.Property<DateTime>("LastSeenAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("last-seen-at");
|
||||
|
||||
b.Property<DateTime>("LastWriteTimeUtc")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("last-write-time-utc");
|
||||
|
||||
b.Property<int>("LibraryRootId")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("library-root-id");
|
||||
|
||||
b.Property<string>("MediaType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("varchar(20)")
|
||||
.HasColumnName("media-type");
|
||||
|
||||
b.Property<string>("RelativePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("varchar(1024)")
|
||||
.HasColumnName("relative-path");
|
||||
|
||||
b.Property<long>("SizeBytes")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("size-bytes");
|
||||
|
||||
b.Property<int?>("ThumbnailId")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("thumbnail-id");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.Property<double?>("VideoDuration")
|
||||
.HasColumnType("double")
|
||||
.HasColumnName("video-duration");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-file-record");
|
||||
|
||||
b.HasIndex("AbsolutePath")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-file-record-absolute-path");
|
||||
|
||||
b.HasIndex("LastPlayedAt")
|
||||
.HasDatabaseName("idx-managed-file-record-last-played-at");
|
||||
|
||||
b.HasIndex("LibraryRootId")
|
||||
.HasDatabaseName("idx-managed-file-record-root-id");
|
||||
|
||||
b.HasIndex("ThumbnailId")
|
||||
.HasDatabaseName("idx-managed-file-record-thumbnail-id");
|
||||
|
||||
b.HasIndex("MediaType", "Exists")
|
||||
.HasDatabaseName("idx-managed-file-record-media-type-exists");
|
||||
|
||||
b.ToTable("managed-file-record", t =>
|
||||
{
|
||||
t.HasComment("文件库文件记录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("varchar(200)")
|
||||
.HasColumnName("display-name");
|
||||
|
||||
b.Property<bool>("IsAvailable")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("tinyint(1)")
|
||||
.HasDefaultValue(true)
|
||||
.HasColumnName("is-available");
|
||||
|
||||
b.Property<bool>("IsEnabled")
|
||||
.HasColumnType("tinyint(1)")
|
||||
.HasColumnName("is-enabled");
|
||||
|
||||
b.Property<DateTime?>("LastScanCompletedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("last-scan-completed-at");
|
||||
|
||||
b.Property<string>("LastScanError")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("varchar(2000)")
|
||||
.HasColumnName("last-scan-error");
|
||||
|
||||
b.Property<DateTime?>("LastScanStartedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("last-scan-started-at");
|
||||
|
||||
b.Property<string>("Path")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("varchar(1024)")
|
||||
.HasColumnName("path");
|
||||
|
||||
b.Property<int>("ScanIntervalMinutes")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("scan-interval-minutes");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-library-root");
|
||||
|
||||
b.HasIndex("Path")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-library-root-path");
|
||||
|
||||
b.ToTable("managed-library-root", t =>
|
||||
{
|
||||
t.HasComment("文件库根目录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar(100)")
|
||||
.HasColumnName("content-type");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<int>("LibraryRootId")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("library-root-id");
|
||||
|
||||
b.Property<string>("RelativePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("varchar(1024)")
|
||||
.HasColumnName("relative-path");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-thumbnail-map");
|
||||
|
||||
b.HasIndex("LibraryRootId")
|
||||
.HasDatabaseName("idx-managed-thumbnail-map-root-id");
|
||||
|
||||
b.HasIndex("RelativePath")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-thumbnail-map-relative-path");
|
||||
|
||||
b.ToTable("managed-thumbnail-map", t =>
|
||||
{
|
||||
t.HasComment("文件缩略图映射记录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id")
|
||||
.HasComment("用户主键");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("created-at")
|
||||
.HasComment("创建时间");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("varchar(200)")
|
||||
.HasColumnName("email")
|
||||
.HasComment("用户邮箱");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar(100)")
|
||||
.HasColumnName("name")
|
||||
.HasComment("用户名称");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("varchar(200)")
|
||||
.HasColumnName("password-hash")
|
||||
.HasComment("密码哈希值");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("varchar(50)")
|
||||
.HasColumnName("phone-number")
|
||||
.HasComment("电话号码");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("updated-at")
|
||||
.HasComment("更新时间");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-user");
|
||||
|
||||
b.ToTable("user", t =>
|
||||
{
|
||||
t.HasComment("用户实体,演示数据库 CRUD 操作");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.WeatherForecastEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id")
|
||||
.HasComment("天气预报主键");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("created-at")
|
||||
.HasComment("创建时间");
|
||||
|
||||
b.Property<DateOnly>("Date")
|
||||
.HasColumnType("date")
|
||||
.HasColumnName("date")
|
||||
.HasComment("预报日期");
|
||||
|
||||
b.Property<string>("Summary")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("varchar(200)")
|
||||
.HasColumnName("summary")
|
||||
.HasComment("天气摘要");
|
||||
|
||||
b.Property<int>("TemperatureC")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("temperature-c")
|
||||
.HasComment("摄氏温度");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("updated-at")
|
||||
.HasComment("更新时间");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-weather-forecast");
|
||||
|
||||
b.ToTable("weather-forecast", t =>
|
||||
{
|
||||
t.HasComment("天气预报数据实体");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||
{
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("LibraryRootId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedThumbnailMap", "Thumbnail")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("ThumbnailId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("LibraryRoot");
|
||||
|
||||
b.Navigation("Thumbnail");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||
{
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||
.WithMany("Thumbnails")
|
||||
.HasForeignKey("LibraryRootId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("LibraryRoot");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
{
|
||||
b.Navigation("Files");
|
||||
|
||||
b.Navigation("Thumbnails");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||
{
|
||||
b.Navigation("Files");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using MySql.EntityFrameworkCore.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FileShare_EFCore.Migrations.MySQL
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddThumbnailMap : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "thumbnail-path",
|
||||
table: "managed-file-record");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "thumbnail-id",
|
||||
table: "managed-file-record",
|
||||
type: "int",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "managed-thumbnail-map",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn),
|
||||
libraryrootid = table.Column<int>(name: "library-root-id", type: "int", nullable: false),
|
||||
relativepath = table.Column<string>(name: "relative-path", type: "varchar(1024)", maxLength: 1024, nullable: false),
|
||||
contenttype = table.Column<string>(name: "content-type", type: "varchar(100)", maxLength: 100, nullable: false),
|
||||
createdat = table.Column<DateTime>(name: "created-at", type: "datetime(6)", nullable: false),
|
||||
updatedat = table.Column<DateTime>(name: "updated-at", type: "datetime(6)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk-managed-thumbnail-map", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "FK_managed-thumbnail-map_managed-library-root_library-root-id",
|
||||
column: x => x.libraryrootid,
|
||||
principalTable: "managed-library-root",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
},
|
||||
comment: "文件缩略图映射记录")
|
||||
.Annotation("MySQL:Charset", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-file-record-thumbnail-id",
|
||||
table: "managed-file-record",
|
||||
column: "thumbnail-id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-thumbnail-map-relative-path",
|
||||
table: "managed-thumbnail-map",
|
||||
column: "relative-path",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-thumbnail-map-root-id",
|
||||
table: "managed-thumbnail-map",
|
||||
column: "library-root-id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_managed-file-record_managed-thumbnail-map_thumbnail-id",
|
||||
table: "managed-file-record",
|
||||
column: "thumbnail-id",
|
||||
principalTable: "managed-thumbnail-map",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_managed-file-record_managed-thumbnail-map_thumbnail-id",
|
||||
table: "managed-file-record");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "managed-thumbnail-map");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "idx-managed-file-record-thumbnail-id",
|
||||
table: "managed-file-record");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "thumbnail-id",
|
||||
table: "managed-file-record");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "thumbnail-path",
|
||||
table: "managed-file-record",
|
||||
type: "varchar(512)",
|
||||
maxLength: 512,
|
||||
nullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
// <auto-generated />
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using FileShare_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@ -79,6 +79,228 @@ namespace FileShare_EFCore.Migrations.MySQL
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("AbsolutePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("varchar(2048)")
|
||||
.HasColumnName("absolute-path");
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar(100)")
|
||||
.HasColumnName("content-type");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<bool>("Exists")
|
||||
.HasColumnType("tinyint(1)")
|
||||
.HasColumnName("exists");
|
||||
|
||||
b.Property<string>("Extension")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("varchar(32)")
|
||||
.HasColumnName("extension");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(260)
|
||||
.HasColumnType("varchar(260)")
|
||||
.HasColumnName("file-name");
|
||||
|
||||
b.Property<DateTime?>("LastPlayedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("last-played-at");
|
||||
|
||||
b.Property<DateTime>("LastSeenAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("last-seen-at");
|
||||
|
||||
b.Property<DateTime>("LastWriteTimeUtc")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("last-write-time-utc");
|
||||
|
||||
b.Property<int>("LibraryRootId")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("library-root-id");
|
||||
|
||||
b.Property<string>("MediaType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("varchar(20)")
|
||||
.HasColumnName("media-type");
|
||||
|
||||
b.Property<string>("RelativePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("varchar(1024)")
|
||||
.HasColumnName("relative-path");
|
||||
|
||||
b.Property<long>("SizeBytes")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("size-bytes");
|
||||
|
||||
b.Property<int?>("ThumbnailId")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("thumbnail-id");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.Property<double?>("VideoDuration")
|
||||
.HasColumnType("double")
|
||||
.HasColumnName("video-duration");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-file-record");
|
||||
|
||||
b.HasIndex("AbsolutePath")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-file-record-absolute-path");
|
||||
|
||||
b.HasIndex("LastPlayedAt")
|
||||
.HasDatabaseName("idx-managed-file-record-last-played-at");
|
||||
|
||||
b.HasIndex("LibraryRootId")
|
||||
.HasDatabaseName("idx-managed-file-record-root-id");
|
||||
|
||||
b.HasIndex("ThumbnailId")
|
||||
.HasDatabaseName("idx-managed-file-record-thumbnail-id");
|
||||
|
||||
b.HasIndex("MediaType", "Exists")
|
||||
.HasDatabaseName("idx-managed-file-record-media-type-exists");
|
||||
|
||||
b.ToTable("managed-file-record", t =>
|
||||
{
|
||||
t.HasComment("文件库文件记录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("varchar(200)")
|
||||
.HasColumnName("display-name");
|
||||
|
||||
b.Property<bool>("IsAvailable")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("tinyint(1)")
|
||||
.HasDefaultValue(true)
|
||||
.HasColumnName("is-available");
|
||||
|
||||
b.Property<bool>("IsEnabled")
|
||||
.HasColumnType("tinyint(1)")
|
||||
.HasColumnName("is-enabled");
|
||||
|
||||
b.Property<DateTime?>("LastScanCompletedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("last-scan-completed-at");
|
||||
|
||||
b.Property<string>("LastScanError")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("varchar(2000)")
|
||||
.HasColumnName("last-scan-error");
|
||||
|
||||
b.Property<DateTime?>("LastScanStartedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("last-scan-started-at");
|
||||
|
||||
b.Property<string>("Path")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("varchar(1024)")
|
||||
.HasColumnName("path");
|
||||
|
||||
b.Property<int>("ScanIntervalMinutes")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("scan-interval-minutes");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-library-root");
|
||||
|
||||
b.HasIndex("Path")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-library-root-path");
|
||||
|
||||
b.ToTable("managed-library-root", t =>
|
||||
{
|
||||
t.HasComment("文件库根目录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("varchar(100)")
|
||||
.HasColumnName("content-type");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<int>("LibraryRootId")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("library-root-id");
|
||||
|
||||
b.Property<string>("RelativePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("varchar(1024)")
|
||||
.HasColumnName("relative-path");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-thumbnail-map");
|
||||
|
||||
b.HasIndex("LibraryRootId")
|
||||
.HasDatabaseName("idx-managed-thumbnail-map-root-id");
|
||||
|
||||
b.HasIndex("RelativePath")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-thumbnail-map-relative-path");
|
||||
|
||||
b.ToTable("managed-thumbnail-map", t =>
|
||||
{
|
||||
t.HasComment("文件缩略图映射记录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@ -172,6 +394,47 @@ namespace FileShare_EFCore.Migrations.MySQL
|
||||
t.HasComment("天气预报数据实体");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||
{
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("LibraryRootId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedThumbnailMap", "Thumbnail")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("ThumbnailId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("LibraryRoot");
|
||||
|
||||
b.Navigation("Thumbnail");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||
{
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||
.WithMany("Thumbnails")
|
||||
.HasForeignKey("LibraryRootId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("LibraryRoot");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
{
|
||||
b.Navigation("Files");
|
||||
|
||||
b.Navigation("Thumbnails");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||
{
|
||||
b.Navigation("Files");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
|
||||
383
FileShare-EFCore/Migrations/PostgreSQL/20260522082843_AutoMigration_20260522162758.Designer.cs
generated
Normal file
383
FileShare-EFCore/Migrations/PostgreSQL/20260522082843_AutoMigration_20260522162758.Designer.cs
generated
Normal file
@ -0,0 +1,383 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using FileShare_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FileShare_EFCore.Migrations.PostgreSQL
|
||||
{
|
||||
[DbContext(typeof(PostgreSqlAppDataContext))]
|
||||
[Migration("20260522082843_AutoMigration_20260522162758")]
|
||||
partial class AutoMigration_20260522162758
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<string>("Device")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)")
|
||||
.HasColumnName("device");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("expires-at");
|
||||
|
||||
b.Property<string>("IpAddress")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasColumnName("ip-address");
|
||||
|
||||
b.Property<string>("ReplacedByTokenHash")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasColumnName("replaced-by-token-hash");
|
||||
|
||||
b.Property<DateTime?>("RevokedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("revoked-at");
|
||||
|
||||
b.Property<string>("TokenHash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasColumnName("token-hash");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("user-id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-api-refresh-token");
|
||||
|
||||
b.HasIndex("TokenHash")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-api-refresh-token-hash");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.HasDatabaseName("idx-api-refresh-token-user-id");
|
||||
|
||||
b.ToTable("api-refresh-token", t =>
|
||||
{
|
||||
t.HasComment("API refresh token");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("AbsolutePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)")
|
||||
.HasColumnName("absolute-path");
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)")
|
||||
.HasColumnName("content-type");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<bool>("Exists")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("exists");
|
||||
|
||||
b.Property<string>("Extension")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("extension");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(260)
|
||||
.HasColumnType("character varying(260)")
|
||||
.HasColumnName("file-name");
|
||||
|
||||
b.Property<DateTime?>("LastPlayedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last-played-at");
|
||||
|
||||
b.Property<DateTime>("LastSeenAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last-seen-at");
|
||||
|
||||
b.Property<DateTime>("LastWriteTimeUtc")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last-write-time-utc");
|
||||
|
||||
b.Property<int>("LibraryRootId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("library-root-id");
|
||||
|
||||
b.Property<string>("MediaType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)")
|
||||
.HasColumnName("media-type");
|
||||
|
||||
b.Property<string>("RelativePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("relative-path");
|
||||
|
||||
b.Property<long>("SizeBytes")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("size-bytes");
|
||||
|
||||
b.Property<string>("ThumbnailPath")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)")
|
||||
.HasColumnName("thumbnail-path");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.Property<double?>("VideoDuration")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("video-duration");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-file-record");
|
||||
|
||||
b.HasIndex("AbsolutePath")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-file-record-absolute-path");
|
||||
|
||||
b.HasIndex("LastPlayedAt")
|
||||
.HasDatabaseName("idx-managed-file-record-last-played-at");
|
||||
|
||||
b.HasIndex("LibraryRootId")
|
||||
.HasDatabaseName("idx-managed-file-record-root-id");
|
||||
|
||||
b.HasIndex("MediaType", "Exists")
|
||||
.HasDatabaseName("idx-managed-file-record-media-type-exists");
|
||||
|
||||
b.ToTable("managed-file-record", t =>
|
||||
{
|
||||
t.HasComment("文件库文件记录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)")
|
||||
.HasColumnName("display-name");
|
||||
|
||||
b.Property<bool>("IsAvailable")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("boolean")
|
||||
.HasDefaultValue(true)
|
||||
.HasColumnName("is-available");
|
||||
|
||||
b.Property<bool>("IsEnabled")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is-enabled");
|
||||
|
||||
b.Property<DateTime?>("LastScanCompletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last-scan-completed-at");
|
||||
|
||||
b.Property<string>("LastScanError")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)")
|
||||
.HasColumnName("last-scan-error");
|
||||
|
||||
b.Property<DateTime?>("LastScanStartedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last-scan-started-at");
|
||||
|
||||
b.Property<string>("Path")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("path");
|
||||
|
||||
b.Property<int>("ScanIntervalMinutes")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("scan-interval-minutes");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-library-root");
|
||||
|
||||
b.HasIndex("Path")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-library-root-path");
|
||||
|
||||
b.ToTable("managed-library-root", t =>
|
||||
{
|
||||
t.HasComment("文件库根目录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id")
|
||||
.HasComment("用户主键");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created-at")
|
||||
.HasComment("创建时间");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)")
|
||||
.HasColumnName("email")
|
||||
.HasComment("用户邮箱");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)")
|
||||
.HasColumnName("name")
|
||||
.HasComment("用户名称");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)")
|
||||
.HasColumnName("password-hash")
|
||||
.HasComment("密码哈希值");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)")
|
||||
.HasColumnName("phone-number")
|
||||
.HasComment("电话号码");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated-at")
|
||||
.HasComment("更新时间");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-user");
|
||||
|
||||
b.ToTable("user", t =>
|
||||
{
|
||||
t.HasComment("用户实体,演示数据库 CRUD 操作");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.WeatherForecastEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id")
|
||||
.HasComment("天气预报主键");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created-at")
|
||||
.HasComment("创建时间");
|
||||
|
||||
b.Property<DateOnly>("Date")
|
||||
.HasColumnType("date")
|
||||
.HasColumnName("date")
|
||||
.HasComment("预报日期");
|
||||
|
||||
b.Property<string>("Summary")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)")
|
||||
.HasColumnName("summary")
|
||||
.HasComment("天气摘要");
|
||||
|
||||
b.Property<int>("TemperatureC")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("temperature-c")
|
||||
.HasComment("摄氏温度");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated-at")
|
||||
.HasComment("更新时间");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-weather-forecast");
|
||||
|
||||
b.ToTable("weather-forecast", t =>
|
||||
{
|
||||
t.HasComment("天气预报数据实体");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||
{
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("LibraryRootId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("LibraryRoot");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
{
|
||||
b.Navigation("Files");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FileShare_EFCore.Migrations.PostgreSQL
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AutoMigration_20260522162758 : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "managed-library-root",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
path = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
displayname = table.Column<string>(name: "display-name", type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
isenabled = table.Column<bool>(name: "is-enabled", type: "boolean", nullable: false),
|
||||
isavailable = table.Column<bool>(name: "is-available", type: "boolean", nullable: false, defaultValue: true),
|
||||
scanintervalminutes = table.Column<int>(name: "scan-interval-minutes", type: "integer", nullable: false),
|
||||
lastscanstartedat = table.Column<DateTime>(name: "last-scan-started-at", type: "timestamp with time zone", nullable: true),
|
||||
lastscancompletedat = table.Column<DateTime>(name: "last-scan-completed-at", type: "timestamp with time zone", nullable: true),
|
||||
lastscanerror = table.Column<string>(name: "last-scan-error", type: "character varying(2000)", maxLength: 2000, nullable: true),
|
||||
createdat = table.Column<DateTime>(name: "created-at", type: "timestamp with time zone", nullable: false),
|
||||
updatedat = table.Column<DateTime>(name: "updated-at", type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk-managed-library-root", x => x.id);
|
||||
},
|
||||
comment: "文件库根目录");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "managed-file-record",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
libraryrootid = table.Column<int>(name: "library-root-id", type: "integer", nullable: false),
|
||||
filename = table.Column<string>(name: "file-name", type: "character varying(260)", maxLength: 260, nullable: false),
|
||||
relativepath = table.Column<string>(name: "relative-path", type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
absolutepath = table.Column<string>(name: "absolute-path", type: "character varying(2048)", maxLength: 2048, nullable: false),
|
||||
extension = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
||||
sizebytes = table.Column<long>(name: "size-bytes", type: "bigint", nullable: false),
|
||||
lastwritetimeutc = table.Column<DateTime>(name: "last-write-time-utc", type: "timestamp with time zone", nullable: false),
|
||||
mediatype = table.Column<string>(name: "media-type", type: "character varying(20)", maxLength: 20, nullable: false),
|
||||
contenttype = table.Column<string>(name: "content-type", type: "character varying(100)", maxLength: 100, nullable: false),
|
||||
exists = table.Column<bool>(type: "boolean", nullable: false),
|
||||
lastseenat = table.Column<DateTime>(name: "last-seen-at", type: "timestamp with time zone", nullable: false),
|
||||
thumbnailpath = table.Column<string>(name: "thumbnail-path", type: "character varying(512)", maxLength: 512, nullable: true),
|
||||
videoduration = table.Column<double>(name: "video-duration", type: "double precision", nullable: true),
|
||||
lastplayedat = table.Column<DateTime>(name: "last-played-at", type: "timestamp with time zone", nullable: true),
|
||||
createdat = table.Column<DateTime>(name: "created-at", type: "timestamp with time zone", nullable: false),
|
||||
updatedat = table.Column<DateTime>(name: "updated-at", type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk-managed-file-record", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "FK_managed-file-record_managed-library-root_library-root-id",
|
||||
column: x => x.libraryrootid,
|
||||
principalTable: "managed-library-root",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
},
|
||||
comment: "文件库文件记录");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-file-record-absolute-path",
|
||||
table: "managed-file-record",
|
||||
column: "absolute-path",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-file-record-last-played-at",
|
||||
table: "managed-file-record",
|
||||
column: "last-played-at");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-file-record-media-type-exists",
|
||||
table: "managed-file-record",
|
||||
columns: new[] { "media-type", "exists" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-file-record-root-id",
|
||||
table: "managed-file-record",
|
||||
column: "library-root-id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-library-root-path",
|
||||
table: "managed-library-root",
|
||||
column: "path",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "managed-file-record");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "managed-library-root");
|
||||
}
|
||||
}
|
||||
}
|
||||
459
FileShare-EFCore/Migrations/PostgreSQL/20260522084325_AddThumbnailMap.Designer.cs
generated
Normal file
459
FileShare-EFCore/Migrations/PostgreSQL/20260522084325_AddThumbnailMap.Designer.cs
generated
Normal file
@ -0,0 +1,459 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using FileShare_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FileShare_EFCore.Migrations.PostgreSQL
|
||||
{
|
||||
[DbContext(typeof(PostgreSqlAppDataContext))]
|
||||
[Migration("20260522084325_AddThumbnailMap")]
|
||||
partial class AddThumbnailMap
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<string>("Device")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)")
|
||||
.HasColumnName("device");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("expires-at");
|
||||
|
||||
b.Property<string>("IpAddress")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasColumnName("ip-address");
|
||||
|
||||
b.Property<string>("ReplacedByTokenHash")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasColumnName("replaced-by-token-hash");
|
||||
|
||||
b.Property<DateTime?>("RevokedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("revoked-at");
|
||||
|
||||
b.Property<string>("TokenHash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasColumnName("token-hash");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("user-id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-api-refresh-token");
|
||||
|
||||
b.HasIndex("TokenHash")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-api-refresh-token-hash");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.HasDatabaseName("idx-api-refresh-token-user-id");
|
||||
|
||||
b.ToTable("api-refresh-token", t =>
|
||||
{
|
||||
t.HasComment("API refresh token");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("AbsolutePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)")
|
||||
.HasColumnName("absolute-path");
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)")
|
||||
.HasColumnName("content-type");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<bool>("Exists")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("exists");
|
||||
|
||||
b.Property<string>("Extension")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("extension");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(260)
|
||||
.HasColumnType("character varying(260)")
|
||||
.HasColumnName("file-name");
|
||||
|
||||
b.Property<DateTime?>("LastPlayedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last-played-at");
|
||||
|
||||
b.Property<DateTime>("LastSeenAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last-seen-at");
|
||||
|
||||
b.Property<DateTime>("LastWriteTimeUtc")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last-write-time-utc");
|
||||
|
||||
b.Property<int>("LibraryRootId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("library-root-id");
|
||||
|
||||
b.Property<string>("MediaType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)")
|
||||
.HasColumnName("media-type");
|
||||
|
||||
b.Property<string>("RelativePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("relative-path");
|
||||
|
||||
b.Property<long>("SizeBytes")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("size-bytes");
|
||||
|
||||
b.Property<int?>("ThumbnailId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("thumbnail-id");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.Property<double?>("VideoDuration")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("video-duration");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-file-record");
|
||||
|
||||
b.HasIndex("AbsolutePath")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-file-record-absolute-path");
|
||||
|
||||
b.HasIndex("LastPlayedAt")
|
||||
.HasDatabaseName("idx-managed-file-record-last-played-at");
|
||||
|
||||
b.HasIndex("LibraryRootId")
|
||||
.HasDatabaseName("idx-managed-file-record-root-id");
|
||||
|
||||
b.HasIndex("ThumbnailId")
|
||||
.HasDatabaseName("idx-managed-file-record-thumbnail-id");
|
||||
|
||||
b.HasIndex("MediaType", "Exists")
|
||||
.HasDatabaseName("idx-managed-file-record-media-type-exists");
|
||||
|
||||
b.ToTable("managed-file-record", t =>
|
||||
{
|
||||
t.HasComment("文件库文件记录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)")
|
||||
.HasColumnName("display-name");
|
||||
|
||||
b.Property<bool>("IsAvailable")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("boolean")
|
||||
.HasDefaultValue(true)
|
||||
.HasColumnName("is-available");
|
||||
|
||||
b.Property<bool>("IsEnabled")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is-enabled");
|
||||
|
||||
b.Property<DateTime?>("LastScanCompletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last-scan-completed-at");
|
||||
|
||||
b.Property<string>("LastScanError")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)")
|
||||
.HasColumnName("last-scan-error");
|
||||
|
||||
b.Property<DateTime?>("LastScanStartedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last-scan-started-at");
|
||||
|
||||
b.Property<string>("Path")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("path");
|
||||
|
||||
b.Property<int>("ScanIntervalMinutes")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("scan-interval-minutes");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-library-root");
|
||||
|
||||
b.HasIndex("Path")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-library-root-path");
|
||||
|
||||
b.ToTable("managed-library-root", t =>
|
||||
{
|
||||
t.HasComment("文件库根目录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)")
|
||||
.HasColumnName("content-type");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<int>("LibraryRootId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("library-root-id");
|
||||
|
||||
b.Property<string>("RelativePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("relative-path");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-thumbnail-map");
|
||||
|
||||
b.HasIndex("LibraryRootId")
|
||||
.HasDatabaseName("idx-managed-thumbnail-map-root-id");
|
||||
|
||||
b.HasIndex("RelativePath")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-thumbnail-map-relative-path");
|
||||
|
||||
b.ToTable("managed-thumbnail-map", t =>
|
||||
{
|
||||
t.HasComment("文件缩略图映射记录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id")
|
||||
.HasComment("用户主键");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created-at")
|
||||
.HasComment("创建时间");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)")
|
||||
.HasColumnName("email")
|
||||
.HasComment("用户邮箱");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)")
|
||||
.HasColumnName("name")
|
||||
.HasComment("用户名称");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)")
|
||||
.HasColumnName("password-hash")
|
||||
.HasComment("密码哈希值");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)")
|
||||
.HasColumnName("phone-number")
|
||||
.HasComment("电话号码");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated-at")
|
||||
.HasComment("更新时间");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-user");
|
||||
|
||||
b.ToTable("user", t =>
|
||||
{
|
||||
t.HasComment("用户实体,演示数据库 CRUD 操作");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.WeatherForecastEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id")
|
||||
.HasComment("天气预报主键");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created-at")
|
||||
.HasComment("创建时间");
|
||||
|
||||
b.Property<DateOnly>("Date")
|
||||
.HasColumnType("date")
|
||||
.HasColumnName("date")
|
||||
.HasComment("预报日期");
|
||||
|
||||
b.Property<string>("Summary")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)")
|
||||
.HasColumnName("summary")
|
||||
.HasComment("天气摘要");
|
||||
|
||||
b.Property<int>("TemperatureC")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("temperature-c")
|
||||
.HasComment("摄氏温度");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated-at")
|
||||
.HasComment("更新时间");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-weather-forecast");
|
||||
|
||||
b.ToTable("weather-forecast", t =>
|
||||
{
|
||||
t.HasComment("天气预报数据实体");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||
{
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("LibraryRootId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedThumbnailMap", "Thumbnail")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("ThumbnailId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("LibraryRoot");
|
||||
|
||||
b.Navigation("Thumbnail");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||
{
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||
.WithMany("Thumbnails")
|
||||
.HasForeignKey("LibraryRootId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("LibraryRoot");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
{
|
||||
b.Navigation("Files");
|
||||
|
||||
b.Navigation("Thumbnails");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||
{
|
||||
b.Navigation("Files");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FileShare_EFCore.Migrations.PostgreSQL
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddThumbnailMap : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "thumbnail-path",
|
||||
table: "managed-file-record");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "thumbnail-id",
|
||||
table: "managed-file-record",
|
||||
type: "integer",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "managed-thumbnail-map",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
libraryrootid = table.Column<int>(name: "library-root-id", type: "integer", nullable: false),
|
||||
relativepath = table.Column<string>(name: "relative-path", type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
contenttype = table.Column<string>(name: "content-type", type: "character varying(100)", maxLength: 100, nullable: false),
|
||||
createdat = table.Column<DateTime>(name: "created-at", type: "timestamp with time zone", nullable: false),
|
||||
updatedat = table.Column<DateTime>(name: "updated-at", type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk-managed-thumbnail-map", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "FK_managed-thumbnail-map_managed-library-root_library-root-id",
|
||||
column: x => x.libraryrootid,
|
||||
principalTable: "managed-library-root",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
},
|
||||
comment: "文件缩略图映射记录");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-file-record-thumbnail-id",
|
||||
table: "managed-file-record",
|
||||
column: "thumbnail-id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-thumbnail-map-relative-path",
|
||||
table: "managed-thumbnail-map",
|
||||
column: "relative-path",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-thumbnail-map-root-id",
|
||||
table: "managed-thumbnail-map",
|
||||
column: "library-root-id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_managed-file-record_managed-thumbnail-map_thumbnail-id",
|
||||
table: "managed-file-record",
|
||||
column: "thumbnail-id",
|
||||
principalTable: "managed-thumbnail-map",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_managed-file-record_managed-thumbnail-map_thumbnail-id",
|
||||
table: "managed-file-record");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "managed-thumbnail-map");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "idx-managed-file-record-thumbnail-id",
|
||||
table: "managed-file-record");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "thumbnail-id",
|
||||
table: "managed-file-record");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "thumbnail-path",
|
||||
table: "managed-file-record",
|
||||
type: "character varying(512)",
|
||||
maxLength: 512,
|
||||
nullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
// <auto-generated />
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using FileShare_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@ -84,6 +84,234 @@ namespace FileShare_EFCore.Migrations.PostgreSQL
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("AbsolutePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)")
|
||||
.HasColumnName("absolute-path");
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)")
|
||||
.HasColumnName("content-type");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<bool>("Exists")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("exists");
|
||||
|
||||
b.Property<string>("Extension")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("extension");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(260)
|
||||
.HasColumnType("character varying(260)")
|
||||
.HasColumnName("file-name");
|
||||
|
||||
b.Property<DateTime?>("LastPlayedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last-played-at");
|
||||
|
||||
b.Property<DateTime>("LastSeenAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last-seen-at");
|
||||
|
||||
b.Property<DateTime>("LastWriteTimeUtc")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last-write-time-utc");
|
||||
|
||||
b.Property<int>("LibraryRootId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("library-root-id");
|
||||
|
||||
b.Property<string>("MediaType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)")
|
||||
.HasColumnName("media-type");
|
||||
|
||||
b.Property<string>("RelativePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("relative-path");
|
||||
|
||||
b.Property<long>("SizeBytes")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("size-bytes");
|
||||
|
||||
b.Property<int?>("ThumbnailId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("thumbnail-id");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.Property<double?>("VideoDuration")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("video-duration");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-file-record");
|
||||
|
||||
b.HasIndex("AbsolutePath")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-file-record-absolute-path");
|
||||
|
||||
b.HasIndex("LastPlayedAt")
|
||||
.HasDatabaseName("idx-managed-file-record-last-played-at");
|
||||
|
||||
b.HasIndex("LibraryRootId")
|
||||
.HasDatabaseName("idx-managed-file-record-root-id");
|
||||
|
||||
b.HasIndex("ThumbnailId")
|
||||
.HasDatabaseName("idx-managed-file-record-thumbnail-id");
|
||||
|
||||
b.HasIndex("MediaType", "Exists")
|
||||
.HasDatabaseName("idx-managed-file-record-media-type-exists");
|
||||
|
||||
b.ToTable("managed-file-record", t =>
|
||||
{
|
||||
t.HasComment("文件库文件记录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)")
|
||||
.HasColumnName("display-name");
|
||||
|
||||
b.Property<bool>("IsAvailable")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("boolean")
|
||||
.HasDefaultValue(true)
|
||||
.HasColumnName("is-available");
|
||||
|
||||
b.Property<bool>("IsEnabled")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is-enabled");
|
||||
|
||||
b.Property<DateTime?>("LastScanCompletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last-scan-completed-at");
|
||||
|
||||
b.Property<string>("LastScanError")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)")
|
||||
.HasColumnName("last-scan-error");
|
||||
|
||||
b.Property<DateTime?>("LastScanStartedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last-scan-started-at");
|
||||
|
||||
b.Property<string>("Path")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("path");
|
||||
|
||||
b.Property<int>("ScanIntervalMinutes")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("scan-interval-minutes");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-library-root");
|
||||
|
||||
b.HasIndex("Path")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-library-root-path");
|
||||
|
||||
b.ToTable("managed-library-root", t =>
|
||||
{
|
||||
t.HasComment("文件库根目录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("id");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)")
|
||||
.HasColumnName("content-type");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<int>("LibraryRootId")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("library-root-id");
|
||||
|
||||
b.Property<string>("RelativePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("relative-path");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-thumbnail-map");
|
||||
|
||||
b.HasIndex("LibraryRootId")
|
||||
.HasDatabaseName("idx-managed-thumbnail-map-root-id");
|
||||
|
||||
b.HasIndex("RelativePath")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-thumbnail-map-relative-path");
|
||||
|
||||
b.ToTable("managed-thumbnail-map", t =>
|
||||
{
|
||||
t.HasComment("文件缩略图映射记录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@ -181,6 +409,47 @@ namespace FileShare_EFCore.Migrations.PostgreSQL
|
||||
t.HasComment("天气预报数据实体");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||
{
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("LibraryRootId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedThumbnailMap", "Thumbnail")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("ThumbnailId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("LibraryRoot");
|
||||
|
||||
b.Navigation("Thumbnail");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||
{
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||
.WithMany("Thumbnails")
|
||||
.HasForeignKey("LibraryRootId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("LibraryRoot");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
{
|
||||
b.Navigation("Files");
|
||||
|
||||
b.Navigation("Thumbnails");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||
{
|
||||
b.Navigation("Files");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
|
||||
368
FileShare-EFCore/Migrations/SQLite/20260522082814_AutoMigration_20260522162758.Designer.cs
generated
Normal file
368
FileShare-EFCore/Migrations/SQLite/20260522082814_AutoMigration_20260522162758.Designer.cs
generated
Normal file
@ -0,0 +1,368 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using FileShare_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FileShare_EFCore.Migrations.SQLite
|
||||
{
|
||||
[DbContext(typeof(SqliteAppDataContext))]
|
||||
[Migration("20260522082814_AutoMigration_20260522162758")]
|
||||
partial class AutoMigration_20260522162758
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.7");
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<string>("Device")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("device");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("expires-at");
|
||||
|
||||
b.Property<string>("IpAddress")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("ip-address");
|
||||
|
||||
b.Property<string>("ReplacedByTokenHash")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("replaced-by-token-hash");
|
||||
|
||||
b.Property<DateTime?>("RevokedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("revoked-at");
|
||||
|
||||
b.Property<string>("TokenHash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("token-hash");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("user-id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-api-refresh-token");
|
||||
|
||||
b.HasIndex("TokenHash")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-api-refresh-token-hash");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.HasDatabaseName("idx-api-refresh-token-user-id");
|
||||
|
||||
b.ToTable("api-refresh-token", t =>
|
||||
{
|
||||
t.HasComment("API refresh token");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("AbsolutePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("absolute-path");
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("content-type");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<bool>("Exists")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("exists");
|
||||
|
||||
b.Property<string>("Extension")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("extension");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(260)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("file-name");
|
||||
|
||||
b.Property<DateTime?>("LastPlayedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("last-played-at");
|
||||
|
||||
b.Property<DateTime>("LastSeenAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("last-seen-at");
|
||||
|
||||
b.Property<DateTime>("LastWriteTimeUtc")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("last-write-time-utc");
|
||||
|
||||
b.Property<int>("LibraryRootId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("library-root-id");
|
||||
|
||||
b.Property<string>("MediaType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("media-type");
|
||||
|
||||
b.Property<string>("RelativePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("relative-path");
|
||||
|
||||
b.Property<long>("SizeBytes")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("size-bytes");
|
||||
|
||||
b.Property<string>("ThumbnailPath")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("thumbnail-path");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.Property<double?>("VideoDuration")
|
||||
.HasColumnType("REAL")
|
||||
.HasColumnName("video-duration");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-file-record");
|
||||
|
||||
b.HasIndex("AbsolutePath")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-file-record-absolute-path");
|
||||
|
||||
b.HasIndex("LastPlayedAt")
|
||||
.HasDatabaseName("idx-managed-file-record-last-played-at");
|
||||
|
||||
b.HasIndex("LibraryRootId")
|
||||
.HasDatabaseName("idx-managed-file-record-root-id");
|
||||
|
||||
b.HasIndex("MediaType", "Exists")
|
||||
.HasDatabaseName("idx-managed-file-record-media-type-exists");
|
||||
|
||||
b.ToTable("managed-file-record", t =>
|
||||
{
|
||||
t.HasComment("文件库文件记录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("display-name");
|
||||
|
||||
b.Property<bool>("IsAvailable")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(true)
|
||||
.HasColumnName("is-available");
|
||||
|
||||
b.Property<bool>("IsEnabled")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("is-enabled");
|
||||
|
||||
b.Property<DateTime?>("LastScanCompletedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("last-scan-completed-at");
|
||||
|
||||
b.Property<string>("LastScanError")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("last-scan-error");
|
||||
|
||||
b.Property<DateTime?>("LastScanStartedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("last-scan-started-at");
|
||||
|
||||
b.Property<string>("Path")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("path");
|
||||
|
||||
b.Property<int>("ScanIntervalMinutes")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("scan-interval-minutes");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-library-root");
|
||||
|
||||
b.HasIndex("Path")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-library-root-path");
|
||||
|
||||
b.ToTable("managed-library-root", t =>
|
||||
{
|
||||
t.HasComment("文件库根目录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("id")
|
||||
.HasComment("用户主键");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("created-at")
|
||||
.HasComment("创建时间");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("email")
|
||||
.HasComment("用户邮箱");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("name")
|
||||
.HasComment("用户名称");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("password-hash")
|
||||
.HasComment("密码哈希值");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("phone-number")
|
||||
.HasComment("电话号码");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("updated-at")
|
||||
.HasComment("更新时间");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-user");
|
||||
|
||||
b.ToTable("user", t =>
|
||||
{
|
||||
t.HasComment("用户实体,演示数据库 CRUD 操作");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.WeatherForecastEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("id")
|
||||
.HasComment("天气预报主键");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("created-at")
|
||||
.HasComment("创建时间");
|
||||
|
||||
b.Property<DateOnly>("Date")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("date")
|
||||
.HasComment("预报日期");
|
||||
|
||||
b.Property<string>("Summary")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("summary")
|
||||
.HasComment("天气摘要");
|
||||
|
||||
b.Property<int>("TemperatureC")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("temperature-c")
|
||||
.HasComment("摄氏温度");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("updated-at")
|
||||
.HasComment("更新时间");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-weather-forecast");
|
||||
|
||||
b.ToTable("weather-forecast", t =>
|
||||
{
|
||||
t.HasComment("天气预报数据实体");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||
{
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("LibraryRootId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("LibraryRoot");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
{
|
||||
b.Navigation("Files");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FileShare_EFCore.Migrations.SQLite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AutoMigration_20260522162758 : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "last-played-at",
|
||||
table: "managed-file-record",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "thumbnail-path",
|
||||
table: "managed-file-record",
|
||||
type: "TEXT",
|
||||
maxLength: 512,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<double>(
|
||||
name: "video-duration",
|
||||
table: "managed-file-record",
|
||||
type: "REAL",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-file-record-last-played-at",
|
||||
table: "managed-file-record",
|
||||
column: "last-played-at");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "idx-managed-file-record-last-played-at",
|
||||
table: "managed-file-record");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "last-played-at",
|
||||
table: "managed-file-record");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "thumbnail-path",
|
||||
table: "managed-file-record");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "video-duration",
|
||||
table: "managed-file-record");
|
||||
}
|
||||
}
|
||||
}
|
||||
442
FileShare-EFCore/Migrations/SQLite/20260522084259_AddThumbnailMap.Designer.cs
generated
Normal file
442
FileShare-EFCore/Migrations/SQLite/20260522084259_AddThumbnailMap.Designer.cs
generated
Normal file
@ -0,0 +1,442 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using FileShare_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FileShare_EFCore.Migrations.SQLite
|
||||
{
|
||||
[DbContext(typeof(SqliteAppDataContext))]
|
||||
[Migration("20260522084259_AddThumbnailMap")]
|
||||
partial class AddThumbnailMap
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.7");
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<string>("Device")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("device");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("expires-at");
|
||||
|
||||
b.Property<string>("IpAddress")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("ip-address");
|
||||
|
||||
b.Property<string>("ReplacedByTokenHash")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("replaced-by-token-hash");
|
||||
|
||||
b.Property<DateTime?>("RevokedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("revoked-at");
|
||||
|
||||
b.Property<string>("TokenHash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("token-hash");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("user-id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-api-refresh-token");
|
||||
|
||||
b.HasIndex("TokenHash")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-api-refresh-token-hash");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.HasDatabaseName("idx-api-refresh-token-user-id");
|
||||
|
||||
b.ToTable("api-refresh-token", t =>
|
||||
{
|
||||
t.HasComment("API refresh token");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("AbsolutePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("absolute-path");
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("content-type");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<bool>("Exists")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("exists");
|
||||
|
||||
b.Property<string>("Extension")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("extension");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(260)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("file-name");
|
||||
|
||||
b.Property<DateTime?>("LastPlayedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("last-played-at");
|
||||
|
||||
b.Property<DateTime>("LastSeenAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("last-seen-at");
|
||||
|
||||
b.Property<DateTime>("LastWriteTimeUtc")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("last-write-time-utc");
|
||||
|
||||
b.Property<int>("LibraryRootId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("library-root-id");
|
||||
|
||||
b.Property<string>("MediaType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("media-type");
|
||||
|
||||
b.Property<string>("RelativePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("relative-path");
|
||||
|
||||
b.Property<long>("SizeBytes")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("size-bytes");
|
||||
|
||||
b.Property<int?>("ThumbnailId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("thumbnail-id");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.Property<double?>("VideoDuration")
|
||||
.HasColumnType("REAL")
|
||||
.HasColumnName("video-duration");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-file-record");
|
||||
|
||||
b.HasIndex("AbsolutePath")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-file-record-absolute-path");
|
||||
|
||||
b.HasIndex("LastPlayedAt")
|
||||
.HasDatabaseName("idx-managed-file-record-last-played-at");
|
||||
|
||||
b.HasIndex("LibraryRootId")
|
||||
.HasDatabaseName("idx-managed-file-record-root-id");
|
||||
|
||||
b.HasIndex("ThumbnailId")
|
||||
.HasDatabaseName("idx-managed-file-record-thumbnail-id");
|
||||
|
||||
b.HasIndex("MediaType", "Exists")
|
||||
.HasDatabaseName("idx-managed-file-record-media-type-exists");
|
||||
|
||||
b.ToTable("managed-file-record", t =>
|
||||
{
|
||||
t.HasComment("文件库文件记录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("display-name");
|
||||
|
||||
b.Property<bool>("IsAvailable")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(true)
|
||||
.HasColumnName("is-available");
|
||||
|
||||
b.Property<bool>("IsEnabled")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("is-enabled");
|
||||
|
||||
b.Property<DateTime?>("LastScanCompletedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("last-scan-completed-at");
|
||||
|
||||
b.Property<string>("LastScanError")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("last-scan-error");
|
||||
|
||||
b.Property<DateTime?>("LastScanStartedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("last-scan-started-at");
|
||||
|
||||
b.Property<string>("Path")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("path");
|
||||
|
||||
b.Property<int>("ScanIntervalMinutes")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("scan-interval-minutes");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-library-root");
|
||||
|
||||
b.HasIndex("Path")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-library-root-path");
|
||||
|
||||
b.ToTable("managed-library-root", t =>
|
||||
{
|
||||
t.HasComment("文件库根目录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("content-type");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<int>("LibraryRootId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("library-root-id");
|
||||
|
||||
b.Property<string>("RelativePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("relative-path");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-thumbnail-map");
|
||||
|
||||
b.HasIndex("LibraryRootId")
|
||||
.HasDatabaseName("idx-managed-thumbnail-map-root-id");
|
||||
|
||||
b.HasIndex("RelativePath")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-thumbnail-map-relative-path");
|
||||
|
||||
b.ToTable("managed-thumbnail-map", t =>
|
||||
{
|
||||
t.HasComment("文件缩略图映射记录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("id")
|
||||
.HasComment("用户主键");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("created-at")
|
||||
.HasComment("创建时间");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("email")
|
||||
.HasComment("用户邮箱");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("name")
|
||||
.HasComment("用户名称");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("password-hash")
|
||||
.HasComment("密码哈希值");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("phone-number")
|
||||
.HasComment("电话号码");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("updated-at")
|
||||
.HasComment("更新时间");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-user");
|
||||
|
||||
b.ToTable("user", t =>
|
||||
{
|
||||
t.HasComment("用户实体,演示数据库 CRUD 操作");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.WeatherForecastEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("id")
|
||||
.HasComment("天气预报主键");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("created-at")
|
||||
.HasComment("创建时间");
|
||||
|
||||
b.Property<DateOnly>("Date")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("date")
|
||||
.HasComment("预报日期");
|
||||
|
||||
b.Property<string>("Summary")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("summary")
|
||||
.HasComment("天气摘要");
|
||||
|
||||
b.Property<int>("TemperatureC")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("temperature-c")
|
||||
.HasComment("摄氏温度");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("updated-at")
|
||||
.HasComment("更新时间");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-weather-forecast");
|
||||
|
||||
b.ToTable("weather-forecast", t =>
|
||||
{
|
||||
t.HasComment("天气预报数据实体");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||
{
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("LibraryRootId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedThumbnailMap", "Thumbnail")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("ThumbnailId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("LibraryRoot");
|
||||
|
||||
b.Navigation("Thumbnail");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||
{
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||
.WithMany("Thumbnails")
|
||||
.HasForeignKey("LibraryRootId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("LibraryRoot");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
{
|
||||
b.Navigation("Files");
|
||||
|
||||
b.Navigation("Thumbnails");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||
{
|
||||
b.Navigation("Files");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FileShare_EFCore.Migrations.SQLite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddThumbnailMap : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "thumbnail-path",
|
||||
table: "managed-file-record");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "thumbnail-id",
|
||||
table: "managed-file-record",
|
||||
type: "INTEGER",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "managed-thumbnail-map",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
libraryrootid = table.Column<int>(name: "library-root-id", type: "INTEGER", nullable: false),
|
||||
relativepath = table.Column<string>(name: "relative-path", type: "TEXT", maxLength: 1024, nullable: false),
|
||||
contenttype = table.Column<string>(name: "content-type", type: "TEXT", maxLength: 100, nullable: false),
|
||||
createdat = table.Column<DateTime>(name: "created-at", type: "TEXT", nullable: false),
|
||||
updatedat = table.Column<DateTime>(name: "updated-at", type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk-managed-thumbnail-map", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "FK_managed-thumbnail-map_managed-library-root_library-root-id",
|
||||
column: x => x.libraryrootid,
|
||||
principalTable: "managed-library-root",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
},
|
||||
comment: "文件缩略图映射记录");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-file-record-thumbnail-id",
|
||||
table: "managed-file-record",
|
||||
column: "thumbnail-id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-thumbnail-map-relative-path",
|
||||
table: "managed-thumbnail-map",
|
||||
column: "relative-path",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-thumbnail-map-root-id",
|
||||
table: "managed-thumbnail-map",
|
||||
column: "library-root-id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_managed-file-record_managed-thumbnail-map_thumbnail-id",
|
||||
table: "managed-file-record",
|
||||
column: "thumbnail-id",
|
||||
principalTable: "managed-thumbnail-map",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_managed-file-record_managed-thumbnail-map_thumbnail-id",
|
||||
table: "managed-file-record");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "managed-thumbnail-map");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "idx-managed-file-record-thumbnail-id",
|
||||
table: "managed-file-record");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "thumbnail-id",
|
||||
table: "managed-file-record");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "thumbnail-path",
|
||||
table: "managed-file-record",
|
||||
type: "TEXT",
|
||||
maxLength: 512,
|
||||
nullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
// <auto-generated />
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using FileShare_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@ -116,6 +116,10 @@ namespace FileShare_EFCore.Migrations.SQLite
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("file-name");
|
||||
|
||||
b.Property<DateTime?>("LastPlayedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("last-played-at");
|
||||
|
||||
b.Property<DateTime>("LastSeenAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("last-seen-at");
|
||||
@ -144,10 +148,18 @@ namespace FileShare_EFCore.Migrations.SQLite
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("size-bytes");
|
||||
|
||||
b.Property<int?>("ThumbnailId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("thumbnail-id");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.Property<double?>("VideoDuration")
|
||||
.HasColumnType("REAL")
|
||||
.HasColumnName("video-duration");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-file-record");
|
||||
|
||||
@ -155,9 +167,15 @@ namespace FileShare_EFCore.Migrations.SQLite
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-file-record-absolute-path");
|
||||
|
||||
b.HasIndex("LastPlayedAt")
|
||||
.HasDatabaseName("idx-managed-file-record-last-played-at");
|
||||
|
||||
b.HasIndex("LibraryRootId")
|
||||
.HasDatabaseName("idx-managed-file-record-root-id");
|
||||
|
||||
b.HasIndex("ThumbnailId")
|
||||
.HasDatabaseName("idx-managed-file-record-thumbnail-id");
|
||||
|
||||
b.HasIndex("MediaType", "Exists")
|
||||
.HasDatabaseName("idx-managed-file-record-media-type-exists");
|
||||
|
||||
@ -234,6 +252,53 @@ namespace FileShare_EFCore.Migrations.SQLite
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("content-type");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<int>("LibraryRootId")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("library-root-id");
|
||||
|
||||
b.Property<string>("RelativePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("relative-path");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-thumbnail-map");
|
||||
|
||||
b.HasIndex("LibraryRootId")
|
||||
.HasDatabaseName("idx-managed-thumbnail-map-root-id");
|
||||
|
||||
b.HasIndex("RelativePath")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-thumbnail-map-relative-path");
|
||||
|
||||
b.ToTable("managed-thumbnail-map", t =>
|
||||
{
|
||||
t.HasComment("文件缩略图映射记录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@ -336,10 +401,35 @@ namespace FileShare_EFCore.Migrations.SQLite
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedThumbnailMap", "Thumbnail")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("ThumbnailId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("LibraryRoot");
|
||||
|
||||
b.Navigation("Thumbnail");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||
{
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||
.WithMany("Thumbnails")
|
||||
.HasForeignKey("LibraryRootId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("LibraryRoot");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
{
|
||||
b.Navigation("Files");
|
||||
|
||||
b.Navigation("Thumbnails");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||
{
|
||||
b.Navigation("Files");
|
||||
});
|
||||
|
||||
383
FileShare-EFCore/Migrations/SqlServer/20260522082829_AutoMigration_20260522162758.Designer.cs
generated
Normal file
383
FileShare-EFCore/Migrations/SqlServer/20260522082829_AutoMigration_20260522162758.Designer.cs
generated
Normal file
@ -0,0 +1,383 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using FileShare_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FileShare_EFCore.Migrations.SqlServer
|
||||
{
|
||||
[DbContext(typeof(SqlServerAppDataContext))]
|
||||
[Migration("20260522082829_AutoMigration_20260522162758")]
|
||||
partial class AutoMigration_20260522162758
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<string>("Device")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)")
|
||||
.HasColumnName("device");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("expires-at");
|
||||
|
||||
b.Property<string>("IpAddress")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)")
|
||||
.HasColumnName("ip-address");
|
||||
|
||||
b.Property<string>("ReplacedByTokenHash")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)")
|
||||
.HasColumnName("replaced-by-token-hash");
|
||||
|
||||
b.Property<DateTime?>("RevokedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("revoked-at");
|
||||
|
||||
b.Property<string>("TokenHash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)")
|
||||
.HasColumnName("token-hash");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("user-id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-api-refresh-token");
|
||||
|
||||
b.HasIndex("TokenHash")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-api-refresh-token-hash");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.HasDatabaseName("idx-api-refresh-token-user-id");
|
||||
|
||||
b.ToTable("api-refresh-token", t =>
|
||||
{
|
||||
t.HasComment("API refresh token");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("AbsolutePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("nvarchar(2048)")
|
||||
.HasColumnName("absolute-path");
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)")
|
||||
.HasColumnName("content-type");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<bool>("Exists")
|
||||
.HasColumnType("bit")
|
||||
.HasColumnName("exists");
|
||||
|
||||
b.Property<string>("Extension")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("nvarchar(32)")
|
||||
.HasColumnName("extension");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(260)
|
||||
.HasColumnType("nvarchar(260)")
|
||||
.HasColumnName("file-name");
|
||||
|
||||
b.Property<DateTime?>("LastPlayedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("last-played-at");
|
||||
|
||||
b.Property<DateTime>("LastSeenAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("last-seen-at");
|
||||
|
||||
b.Property<DateTime>("LastWriteTimeUtc")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("last-write-time-utc");
|
||||
|
||||
b.Property<int>("LibraryRootId")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("library-root-id");
|
||||
|
||||
b.Property<string>("MediaType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("nvarchar(20)")
|
||||
.HasColumnName("media-type");
|
||||
|
||||
b.Property<string>("RelativePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("nvarchar(1024)")
|
||||
.HasColumnName("relative-path");
|
||||
|
||||
b.Property<long>("SizeBytes")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("size-bytes");
|
||||
|
||||
b.Property<string>("ThumbnailPath")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)")
|
||||
.HasColumnName("thumbnail-path");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.Property<double?>("VideoDuration")
|
||||
.HasColumnType("float")
|
||||
.HasColumnName("video-duration");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-file-record");
|
||||
|
||||
b.HasIndex("AbsolutePath")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-file-record-absolute-path");
|
||||
|
||||
b.HasIndex("LastPlayedAt")
|
||||
.HasDatabaseName("idx-managed-file-record-last-played-at");
|
||||
|
||||
b.HasIndex("LibraryRootId")
|
||||
.HasDatabaseName("idx-managed-file-record-root-id");
|
||||
|
||||
b.HasIndex("MediaType", "Exists")
|
||||
.HasDatabaseName("idx-managed-file-record-media-type-exists");
|
||||
|
||||
b.ToTable("managed-file-record", t =>
|
||||
{
|
||||
t.HasComment("文件库文件记录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)")
|
||||
.HasColumnName("display-name");
|
||||
|
||||
b.Property<bool>("IsAvailable")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(true)
|
||||
.HasColumnName("is-available");
|
||||
|
||||
b.Property<bool>("IsEnabled")
|
||||
.HasColumnType("bit")
|
||||
.HasColumnName("is-enabled");
|
||||
|
||||
b.Property<DateTime?>("LastScanCompletedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("last-scan-completed-at");
|
||||
|
||||
b.Property<string>("LastScanError")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("nvarchar(2000)")
|
||||
.HasColumnName("last-scan-error");
|
||||
|
||||
b.Property<DateTime?>("LastScanStartedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("last-scan-started-at");
|
||||
|
||||
b.Property<string>("Path")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("nvarchar(1024)")
|
||||
.HasColumnName("path");
|
||||
|
||||
b.Property<int>("ScanIntervalMinutes")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("scan-interval-minutes");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-library-root");
|
||||
|
||||
b.HasIndex("Path")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-library-root-path");
|
||||
|
||||
b.ToTable("managed-library-root", t =>
|
||||
{
|
||||
t.HasComment("文件库根目录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id")
|
||||
.HasComment("用户主键");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("created-at")
|
||||
.HasComment("创建时间");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)")
|
||||
.HasColumnName("email")
|
||||
.HasComment("用户邮箱");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)")
|
||||
.HasColumnName("name")
|
||||
.HasComment("用户名称");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)")
|
||||
.HasColumnName("password-hash")
|
||||
.HasComment("密码哈希值");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)")
|
||||
.HasColumnName("phone-number")
|
||||
.HasComment("电话号码");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("updated-at")
|
||||
.HasComment("更新时间");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-user");
|
||||
|
||||
b.ToTable("user", t =>
|
||||
{
|
||||
t.HasComment("用户实体,演示数据库 CRUD 操作");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.WeatherForecastEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id")
|
||||
.HasComment("天气预报主键");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("created-at")
|
||||
.HasComment("创建时间");
|
||||
|
||||
b.Property<DateOnly>("Date")
|
||||
.HasColumnType("date")
|
||||
.HasColumnName("date")
|
||||
.HasComment("预报日期");
|
||||
|
||||
b.Property<string>("Summary")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)")
|
||||
.HasColumnName("summary")
|
||||
.HasComment("天气摘要");
|
||||
|
||||
b.Property<int>("TemperatureC")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("temperature-c")
|
||||
.HasComment("摄氏温度");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("updated-at")
|
||||
.HasComment("更新时间");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-weather-forecast");
|
||||
|
||||
b.ToTable("weather-forecast", t =>
|
||||
{
|
||||
t.HasComment("天气预报数据实体");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||
{
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("LibraryRootId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("LibraryRoot");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
{
|
||||
b.Navigation("Files");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FileShare_EFCore.Migrations.SqlServer
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AutoMigration_20260522162758 : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "managed-library-root",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
path = table.Column<string>(type: "nvarchar(1024)", maxLength: 1024, nullable: false),
|
||||
displayname = table.Column<string>(name: "display-name", type: "nvarchar(200)", maxLength: 200, nullable: false),
|
||||
isenabled = table.Column<bool>(name: "is-enabled", type: "bit", nullable: false),
|
||||
isavailable = table.Column<bool>(name: "is-available", type: "bit", nullable: false, defaultValue: true),
|
||||
scanintervalminutes = table.Column<int>(name: "scan-interval-minutes", type: "int", nullable: false),
|
||||
lastscanstartedat = table.Column<DateTime>(name: "last-scan-started-at", type: "datetime2", nullable: true),
|
||||
lastscancompletedat = table.Column<DateTime>(name: "last-scan-completed-at", type: "datetime2", nullable: true),
|
||||
lastscanerror = table.Column<string>(name: "last-scan-error", type: "nvarchar(2000)", maxLength: 2000, nullable: true),
|
||||
createdat = table.Column<DateTime>(name: "created-at", type: "datetime2", nullable: false),
|
||||
updatedat = table.Column<DateTime>(name: "updated-at", type: "datetime2", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk-managed-library-root", x => x.id);
|
||||
},
|
||||
comment: "文件库根目录");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "managed-file-record",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
libraryrootid = table.Column<int>(name: "library-root-id", type: "int", nullable: false),
|
||||
filename = table.Column<string>(name: "file-name", type: "nvarchar(260)", maxLength: 260, nullable: false),
|
||||
relativepath = table.Column<string>(name: "relative-path", type: "nvarchar(1024)", maxLength: 1024, nullable: false),
|
||||
absolutepath = table.Column<string>(name: "absolute-path", type: "nvarchar(2048)", maxLength: 2048, nullable: false),
|
||||
extension = table.Column<string>(type: "nvarchar(32)", maxLength: 32, nullable: false),
|
||||
sizebytes = table.Column<long>(name: "size-bytes", type: "bigint", nullable: false),
|
||||
lastwritetimeutc = table.Column<DateTime>(name: "last-write-time-utc", type: "datetime2", nullable: false),
|
||||
mediatype = table.Column<string>(name: "media-type", type: "nvarchar(20)", maxLength: 20, nullable: false),
|
||||
contenttype = table.Column<string>(name: "content-type", type: "nvarchar(100)", maxLength: 100, nullable: false),
|
||||
exists = table.Column<bool>(type: "bit", nullable: false),
|
||||
lastseenat = table.Column<DateTime>(name: "last-seen-at", type: "datetime2", nullable: false),
|
||||
thumbnailpath = table.Column<string>(name: "thumbnail-path", type: "nvarchar(512)", maxLength: 512, nullable: true),
|
||||
videoduration = table.Column<double>(name: "video-duration", type: "float", nullable: true),
|
||||
lastplayedat = table.Column<DateTime>(name: "last-played-at", type: "datetime2", nullable: true),
|
||||
createdat = table.Column<DateTime>(name: "created-at", type: "datetime2", nullable: false),
|
||||
updatedat = table.Column<DateTime>(name: "updated-at", type: "datetime2", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk-managed-file-record", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "FK_managed-file-record_managed-library-root_library-root-id",
|
||||
column: x => x.libraryrootid,
|
||||
principalTable: "managed-library-root",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
},
|
||||
comment: "文件库文件记录");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-file-record-absolute-path",
|
||||
table: "managed-file-record",
|
||||
column: "absolute-path",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-file-record-last-played-at",
|
||||
table: "managed-file-record",
|
||||
column: "last-played-at");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-file-record-media-type-exists",
|
||||
table: "managed-file-record",
|
||||
columns: new[] { "media-type", "exists" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-file-record-root-id",
|
||||
table: "managed-file-record",
|
||||
column: "library-root-id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-library-root-path",
|
||||
table: "managed-library-root",
|
||||
column: "path",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "managed-file-record");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "managed-library-root");
|
||||
}
|
||||
}
|
||||
}
|
||||
459
FileShare-EFCore/Migrations/SqlServer/20260522084325_AddThumbnailMap.Designer.cs
generated
Normal file
459
FileShare-EFCore/Migrations/SqlServer/20260522084325_AddThumbnailMap.Designer.cs
generated
Normal file
@ -0,0 +1,459 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using FileShare_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FileShare_EFCore.Migrations.SqlServer
|
||||
{
|
||||
[DbContext(typeof(SqlServerAppDataContext))]
|
||||
[Migration("20260522084325_AddThumbnailMap")]
|
||||
partial class AddThumbnailMap
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ApiRefreshTokenEntity", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("id");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<string>("Device")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)")
|
||||
.HasColumnName("device");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("expires-at");
|
||||
|
||||
b.Property<string>("IpAddress")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)")
|
||||
.HasColumnName("ip-address");
|
||||
|
||||
b.Property<string>("ReplacedByTokenHash")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)")
|
||||
.HasColumnName("replaced-by-token-hash");
|
||||
|
||||
b.Property<DateTime?>("RevokedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("revoked-at");
|
||||
|
||||
b.Property<string>("TokenHash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)")
|
||||
.HasColumnName("token-hash");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("user-id");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-api-refresh-token");
|
||||
|
||||
b.HasIndex("TokenHash")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-api-refresh-token-hash");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.HasDatabaseName("idx-api-refresh-token-user-id");
|
||||
|
||||
b.ToTable("api-refresh-token", t =>
|
||||
{
|
||||
t.HasComment("API refresh token");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("AbsolutePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("nvarchar(2048)")
|
||||
.HasColumnName("absolute-path");
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)")
|
||||
.HasColumnName("content-type");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<bool>("Exists")
|
||||
.HasColumnType("bit")
|
||||
.HasColumnName("exists");
|
||||
|
||||
b.Property<string>("Extension")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("nvarchar(32)")
|
||||
.HasColumnName("extension");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(260)
|
||||
.HasColumnType("nvarchar(260)")
|
||||
.HasColumnName("file-name");
|
||||
|
||||
b.Property<DateTime?>("LastPlayedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("last-played-at");
|
||||
|
||||
b.Property<DateTime>("LastSeenAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("last-seen-at");
|
||||
|
||||
b.Property<DateTime>("LastWriteTimeUtc")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("last-write-time-utc");
|
||||
|
||||
b.Property<int>("LibraryRootId")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("library-root-id");
|
||||
|
||||
b.Property<string>("MediaType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("nvarchar(20)")
|
||||
.HasColumnName("media-type");
|
||||
|
||||
b.Property<string>("RelativePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("nvarchar(1024)")
|
||||
.HasColumnName("relative-path");
|
||||
|
||||
b.Property<long>("SizeBytes")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("size-bytes");
|
||||
|
||||
b.Property<int?>("ThumbnailId")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("thumbnail-id");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.Property<double?>("VideoDuration")
|
||||
.HasColumnType("float")
|
||||
.HasColumnName("video-duration");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-file-record");
|
||||
|
||||
b.HasIndex("AbsolutePath")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-file-record-absolute-path");
|
||||
|
||||
b.HasIndex("LastPlayedAt")
|
||||
.HasDatabaseName("idx-managed-file-record-last-played-at");
|
||||
|
||||
b.HasIndex("LibraryRootId")
|
||||
.HasDatabaseName("idx-managed-file-record-root-id");
|
||||
|
||||
b.HasIndex("ThumbnailId")
|
||||
.HasDatabaseName("idx-managed-file-record-thumbnail-id");
|
||||
|
||||
b.HasIndex("MediaType", "Exists")
|
||||
.HasDatabaseName("idx-managed-file-record-media-type-exists");
|
||||
|
||||
b.ToTable("managed-file-record", t =>
|
||||
{
|
||||
t.HasComment("文件库文件记录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)")
|
||||
.HasColumnName("display-name");
|
||||
|
||||
b.Property<bool>("IsAvailable")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(true)
|
||||
.HasColumnName("is-available");
|
||||
|
||||
b.Property<bool>("IsEnabled")
|
||||
.HasColumnType("bit")
|
||||
.HasColumnName("is-enabled");
|
||||
|
||||
b.Property<DateTime?>("LastScanCompletedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("last-scan-completed-at");
|
||||
|
||||
b.Property<string>("LastScanError")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("nvarchar(2000)")
|
||||
.HasColumnName("last-scan-error");
|
||||
|
||||
b.Property<DateTime?>("LastScanStartedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("last-scan-started-at");
|
||||
|
||||
b.Property<string>("Path")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("nvarchar(1024)")
|
||||
.HasColumnName("path");
|
||||
|
||||
b.Property<int>("ScanIntervalMinutes")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("scan-interval-minutes");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-library-root");
|
||||
|
||||
b.HasIndex("Path")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-library-root-path");
|
||||
|
||||
b.ToTable("managed-library-root", t =>
|
||||
{
|
||||
t.HasComment("文件库根目录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)")
|
||||
.HasColumnName("content-type");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<int>("LibraryRootId")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("library-root-id");
|
||||
|
||||
b.Property<string>("RelativePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("nvarchar(1024)")
|
||||
.HasColumnName("relative-path");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-thumbnail-map");
|
||||
|
||||
b.HasIndex("LibraryRootId")
|
||||
.HasDatabaseName("idx-managed-thumbnail-map-root-id");
|
||||
|
||||
b.HasIndex("RelativePath")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-thumbnail-map-relative-path");
|
||||
|
||||
b.ToTable("managed-thumbnail-map", t =>
|
||||
{
|
||||
t.HasComment("文件缩略图映射记录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id")
|
||||
.HasComment("用户主键");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("created-at")
|
||||
.HasComment("创建时间");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)")
|
||||
.HasColumnName("email")
|
||||
.HasComment("用户邮箱");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)")
|
||||
.HasColumnName("name")
|
||||
.HasComment("用户名称");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)")
|
||||
.HasColumnName("password-hash")
|
||||
.HasComment("密码哈希值");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)")
|
||||
.HasColumnName("phone-number")
|
||||
.HasComment("电话号码");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("updated-at")
|
||||
.HasComment("更新时间");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-user");
|
||||
|
||||
b.ToTable("user", t =>
|
||||
{
|
||||
t.HasComment("用户实体,演示数据库 CRUD 操作");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.WeatherForecastEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id")
|
||||
.HasComment("天气预报主键");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("created-at")
|
||||
.HasComment("创建时间");
|
||||
|
||||
b.Property<DateOnly>("Date")
|
||||
.HasColumnType("date")
|
||||
.HasColumnName("date")
|
||||
.HasComment("预报日期");
|
||||
|
||||
b.Property<string>("Summary")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)")
|
||||
.HasColumnName("summary")
|
||||
.HasComment("天气摘要");
|
||||
|
||||
b.Property<int>("TemperatureC")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("temperature-c")
|
||||
.HasComment("摄氏温度");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("updated-at")
|
||||
.HasComment("更新时间");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-weather-forecast");
|
||||
|
||||
b.ToTable("weather-forecast", t =>
|
||||
{
|
||||
t.HasComment("天气预报数据实体");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||
{
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("LibraryRootId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedThumbnailMap", "Thumbnail")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("ThumbnailId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("LibraryRoot");
|
||||
|
||||
b.Navigation("Thumbnail");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||
{
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||
.WithMany("Thumbnails")
|
||||
.HasForeignKey("LibraryRootId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("LibraryRoot");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
{
|
||||
b.Navigation("Files");
|
||||
|
||||
b.Navigation("Thumbnails");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||
{
|
||||
b.Navigation("Files");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FileShare_EFCore.Migrations.SqlServer
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddThumbnailMap : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "thumbnail-path",
|
||||
table: "managed-file-record");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "thumbnail-id",
|
||||
table: "managed-file-record",
|
||||
type: "int",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "managed-thumbnail-map",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
libraryrootid = table.Column<int>(name: "library-root-id", type: "int", nullable: false),
|
||||
relativepath = table.Column<string>(name: "relative-path", type: "nvarchar(1024)", maxLength: 1024, nullable: false),
|
||||
contenttype = table.Column<string>(name: "content-type", type: "nvarchar(100)", maxLength: 100, nullable: false),
|
||||
createdat = table.Column<DateTime>(name: "created-at", type: "datetime2", nullable: false),
|
||||
updatedat = table.Column<DateTime>(name: "updated-at", type: "datetime2", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk-managed-thumbnail-map", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "FK_managed-thumbnail-map_managed-library-root_library-root-id",
|
||||
column: x => x.libraryrootid,
|
||||
principalTable: "managed-library-root",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
},
|
||||
comment: "文件缩略图映射记录");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-file-record-thumbnail-id",
|
||||
table: "managed-file-record",
|
||||
column: "thumbnail-id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-thumbnail-map-relative-path",
|
||||
table: "managed-thumbnail-map",
|
||||
column: "relative-path",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "idx-managed-thumbnail-map-root-id",
|
||||
table: "managed-thumbnail-map",
|
||||
column: "library-root-id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_managed-file-record_managed-thumbnail-map_thumbnail-id",
|
||||
table: "managed-file-record",
|
||||
column: "thumbnail-id",
|
||||
principalTable: "managed-thumbnail-map",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_managed-file-record_managed-thumbnail-map_thumbnail-id",
|
||||
table: "managed-file-record");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "managed-thumbnail-map");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "idx-managed-file-record-thumbnail-id",
|
||||
table: "managed-file-record");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "thumbnail-id",
|
||||
table: "managed-file-record");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "thumbnail-path",
|
||||
table: "managed-file-record",
|
||||
type: "nvarchar(512)",
|
||||
maxLength: 512,
|
||||
nullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
// <auto-generated />
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using FileShare_EFCore.Database;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@ -84,6 +84,234 @@ namespace FileShare_EFCore.Migrations.SqlServer
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("AbsolutePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("nvarchar(2048)")
|
||||
.HasColumnName("absolute-path");
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)")
|
||||
.HasColumnName("content-type");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<bool>("Exists")
|
||||
.HasColumnType("bit")
|
||||
.HasColumnName("exists");
|
||||
|
||||
b.Property<string>("Extension")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("nvarchar(32)")
|
||||
.HasColumnName("extension");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(260)
|
||||
.HasColumnType("nvarchar(260)")
|
||||
.HasColumnName("file-name");
|
||||
|
||||
b.Property<DateTime?>("LastPlayedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("last-played-at");
|
||||
|
||||
b.Property<DateTime>("LastSeenAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("last-seen-at");
|
||||
|
||||
b.Property<DateTime>("LastWriteTimeUtc")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("last-write-time-utc");
|
||||
|
||||
b.Property<int>("LibraryRootId")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("library-root-id");
|
||||
|
||||
b.Property<string>("MediaType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("nvarchar(20)")
|
||||
.HasColumnName("media-type");
|
||||
|
||||
b.Property<string>("RelativePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("nvarchar(1024)")
|
||||
.HasColumnName("relative-path");
|
||||
|
||||
b.Property<long>("SizeBytes")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("size-bytes");
|
||||
|
||||
b.Property<int?>("ThumbnailId")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("thumbnail-id");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.Property<double?>("VideoDuration")
|
||||
.HasColumnType("float")
|
||||
.HasColumnName("video-duration");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-file-record");
|
||||
|
||||
b.HasIndex("AbsolutePath")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-file-record-absolute-path");
|
||||
|
||||
b.HasIndex("LastPlayedAt")
|
||||
.HasDatabaseName("idx-managed-file-record-last-played-at");
|
||||
|
||||
b.HasIndex("LibraryRootId")
|
||||
.HasDatabaseName("idx-managed-file-record-root-id");
|
||||
|
||||
b.HasIndex("ThumbnailId")
|
||||
.HasDatabaseName("idx-managed-file-record-thumbnail-id");
|
||||
|
||||
b.HasIndex("MediaType", "Exists")
|
||||
.HasDatabaseName("idx-managed-file-record-media-type-exists");
|
||||
|
||||
b.ToTable("managed-file-record", t =>
|
||||
{
|
||||
t.HasComment("文件库文件记录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)")
|
||||
.HasColumnName("display-name");
|
||||
|
||||
b.Property<bool>("IsAvailable")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bit")
|
||||
.HasDefaultValue(true)
|
||||
.HasColumnName("is-available");
|
||||
|
||||
b.Property<bool>("IsEnabled")
|
||||
.HasColumnType("bit")
|
||||
.HasColumnName("is-enabled");
|
||||
|
||||
b.Property<DateTime?>("LastScanCompletedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("last-scan-completed-at");
|
||||
|
||||
b.Property<string>("LastScanError")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("nvarchar(2000)")
|
||||
.HasColumnName("last-scan-error");
|
||||
|
||||
b.Property<DateTime?>("LastScanStartedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("last-scan-started-at");
|
||||
|
||||
b.Property<string>("Path")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("nvarchar(1024)")
|
||||
.HasColumnName("path");
|
||||
|
||||
b.Property<int>("ScanIntervalMinutes")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("scan-interval-minutes");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-library-root");
|
||||
|
||||
b.HasIndex("Path")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-library-root-path");
|
||||
|
||||
b.ToTable("managed-library-root", t =>
|
||||
{
|
||||
t.HasComment("文件库根目录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("id");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)")
|
||||
.HasColumnName("content-type");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("created-at");
|
||||
|
||||
b.Property<int>("LibraryRootId")
|
||||
.HasColumnType("int")
|
||||
.HasColumnName("library-root-id");
|
||||
|
||||
b.Property<string>("RelativePath")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("nvarchar(1024)")
|
||||
.HasColumnName("relative-path");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("datetime2")
|
||||
.HasColumnName("updated-at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk-managed-thumbnail-map");
|
||||
|
||||
b.HasIndex("LibraryRootId")
|
||||
.HasDatabaseName("idx-managed-thumbnail-map-root-id");
|
||||
|
||||
b.HasIndex("RelativePath")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("idx-managed-thumbnail-map-relative-path");
|
||||
|
||||
b.ToTable("managed-thumbnail-map", t =>
|
||||
{
|
||||
t.HasComment("文件缩略图映射记录");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.UserEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@ -181,6 +409,47 @@ namespace FileShare_EFCore.Migrations.SqlServer
|
||||
t.HasComment("天气预报数据实体");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedFileRecord", b =>
|
||||
{
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("LibraryRootId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedThumbnailMap", "Thumbnail")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("ThumbnailId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("LibraryRoot");
|
||||
|
||||
b.Navigation("Thumbnail");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||
{
|
||||
b.HasOne("FileShare_EFCore.Models.ManagedLibraryRoot", "LibraryRoot")
|
||||
.WithMany("Thumbnails")
|
||||
.HasForeignKey("LibraryRootId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("LibraryRoot");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedLibraryRoot", b =>
|
||||
{
|
||||
b.Navigation("Files");
|
||||
|
||||
b.Navigation("Thumbnails");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FileShare_EFCore.Models.ManagedThumbnailMap", b =>
|
||||
{
|
||||
b.Navigation("Files");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,6 +67,18 @@ namespace FileShare_EFCore.Models
|
||||
[Column("last-seen-at")]
|
||||
public DateTime LastSeenAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
/// <summary>视频缩略图路径(相对于 wwwroot)。</summary>
|
||||
[Column("thumbnail-id")]
|
||||
public int? ThumbnailId { get; set; }
|
||||
|
||||
/// <summary>视频时长(秒)。</summary>
|
||||
[Column("video-duration")]
|
||||
public double? VideoDuration { get; set; }
|
||||
|
||||
/// <summary>最近一次播放时间 UTC。</summary>
|
||||
[Column("last-played-at")]
|
||||
public DateTime? LastPlayedAt { get; set; }
|
||||
|
||||
/// <summary>创建时间。</summary>
|
||||
[Column("created-at")]
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
@ -77,5 +89,7 @@ namespace FileShare_EFCore.Models
|
||||
|
||||
/// <summary>所属根目录。</summary>
|
||||
public ManagedLibraryRoot? LibraryRoot { get; set; }
|
||||
|
||||
public ManagedThumbnailMap? Thumbnail { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,5 +62,7 @@ namespace FileShare_EFCore.Models
|
||||
|
||||
/// <summary>文件记录。</summary>
|
||||
public List<ManagedFileRecord> Files { get; set; } = new();
|
||||
|
||||
public List<ManagedThumbnailMap> Thumbnails { get; set; } = new();
|
||||
}
|
||||
}
|
||||
|
||||
37
FileShare-EFCore/Models/ManagedThumbnailMap.cs
Normal file
37
FileShare-EFCore/Models/ManagedThumbnailMap.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace FileShare_EFCore.Models
|
||||
{
|
||||
[Comment("文件缩略图映射记录")]
|
||||
[Table("managed-thumbnail-map")]
|
||||
public class ManagedThumbnailMap
|
||||
{
|
||||
[Key]
|
||||
[Column("id")]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Column("library-root-id")]
|
||||
public int LibraryRootId { get; set; }
|
||||
|
||||
[Column("relative-path")]
|
||||
[MaxLength(1024)]
|
||||
public string RelativePath { get; set; } = string.Empty;
|
||||
|
||||
[Column("content-type")]
|
||||
[MaxLength(100)]
|
||||
public string ContentType { get; set; } = "image/jpeg";
|
||||
|
||||
[Column("created-at")]
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
[Column("updated-at")]
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public ManagedLibraryRoot? LibraryRoot { get; set; }
|
||||
|
||||
public List<ManagedFileRecord> Files { get; set; } = new();
|
||||
}
|
||||
}
|
||||
@ -13,6 +13,8 @@
|
||||
<Content Include="www\**\*">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="..\tools\ffmpeg\bin\ffmpeg.exe" Link="tools\ffmpeg\bin\ffmpeg.exe" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" />
|
||||
<Content Include="..\tools\ffmpeg\bin\ffprobe.exe" Link="tools\ffmpeg\bin\ffprobe.exe" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@ -12,6 +12,7 @@ using FileShare_Services.Services.FileLibrary;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace FileShare_PC
|
||||
{
|
||||
@ -71,9 +72,13 @@ namespace FileShare_PC
|
||||
services.AddSingleton<PcGlobalTokenService>();
|
||||
services.AddSingleton<IAuthService, PcAuthService>();
|
||||
services.AddSingleton<IPcAuthEndpointService, PcAuthEndpointService>();
|
||||
services.AddSingleton(new ThumbnailStorageOptions());
|
||||
services.AddSingleton<IVideoThumbnailService>(sp =>
|
||||
new VideoThumbnailService(sp.GetRequiredService<ThumbnailStorageOptions>()));
|
||||
services.AddScoped<IFileLibraryService, FileLibraryService>();
|
||||
services.AddScoped<IFileLibraryEndpointService, FileLibraryEndpointService>();
|
||||
services.AddScoped<IFileStreamService, FileStreamService>();
|
||||
services.AddScoped<IThumbnailStreamService, ThumbnailStreamService>();
|
||||
|
||||
// ---- 端点注册 ----
|
||||
var endpointBuilder = new ServiceEndpointBuilder();
|
||||
|
||||
@ -554,7 +554,8 @@ namespace FileShare_PC.Views
|
||||
{
|
||||
try
|
||||
{
|
||||
if (await TryHandleLocalMediaStreamAsync(context))
|
||||
if (await TryHandleLocalMediaStreamAsync(context)
|
||||
|| await TryHandleLocalThumbnailAsync(context))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -691,6 +692,69 @@ namespace FileShare_PC.Views
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> TryHandleLocalThumbnailAsync(HttpListenerContext context)
|
||||
{
|
||||
var request = context.Request;
|
||||
var response = context.Response;
|
||||
var segments = (request.Url?.AbsolutePath ?? string.Empty)
|
||||
.Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
|
||||
if (segments.Length != 3
|
||||
|| !string.Equals(segments[0], "api", StringComparison.OrdinalIgnoreCase)
|
||||
|| !string.Equals(segments[1], "thumbnails", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AddLocalMediaHeaders(response);
|
||||
|
||||
if (!string.Equals(request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.Equals(request.HttpMethod, "HEAD", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
response.StatusCode = (int)HttpStatusCode.MethodNotAllowed;
|
||||
response.Close();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!int.TryParse(segments[2], out var id) || id <= 0)
|
||||
{
|
||||
response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
response.Close();
|
||||
return true;
|
||||
}
|
||||
|
||||
using var scope = _services.CreateScope();
|
||||
var thumbnailService = scope.ServiceProvider.GetService<IThumbnailStreamService>();
|
||||
var thumbnail = thumbnailService is null ? null : await thumbnailService.GetThumbnailAsync(id);
|
||||
if (thumbnail is null || !File.Exists(thumbnail.FilePath))
|
||||
{
|
||||
response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
response.Close();
|
||||
return true;
|
||||
}
|
||||
|
||||
var fileInfo = new FileInfo(thumbnail.FilePath);
|
||||
response.StatusCode = (int)HttpStatusCode.OK;
|
||||
response.ContentType = thumbnail.ContentType;
|
||||
response.ContentLength64 = fileInfo.Length;
|
||||
response.Headers["Cache-Control"] = "public, max-age=3600";
|
||||
response.Headers["Content-Disposition"] =
|
||||
$"inline; filename=\"{Uri.EscapeDataString(thumbnail.FileName)}\"";
|
||||
response.Headers["Last-Modified"] = thumbnail.LastModified.ToUniversalTime().ToString("R");
|
||||
|
||||
if (string.Equals(request.HttpMethod, "HEAD", StringComparison.OrdinalIgnoreCase)
|
||||
|| fileInfo.Length == 0)
|
||||
{
|
||||
response.Close();
|
||||
return true;
|
||||
}
|
||||
|
||||
await using var input = File.Open(thumbnail.FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
await input.CopyToAsync(response.OutputStream);
|
||||
response.OutputStream.Close();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为媒体流响应添加 CORS 和 Range 请求头,允许浏览器跨域访问和分段请求。
|
||||
/// </summary>
|
||||
|
||||
@ -69,6 +69,14 @@ namespace FileShare_Services.Endpoints
|
||||
.WithOpenApi("FileLibrary", "浏览文件库目录结构。")
|
||||
.WithName("BrowseDirectory");
|
||||
|
||||
endpoints.MapGet<IFileLibraryEndpointService, RecentFilesRequest>("api/files/recent", (service, request, _) => service.GetRecentFilesAsync(request))
|
||||
.WithOpenApi("FileLibrary", "获取最近添加或最近播放的文件。")
|
||||
.WithName("GetRecentFiles");
|
||||
|
||||
endpoints.MapPost<IFileLibraryEndpointService, MarkFilePlayedRequest>("api/files/played", (service, request, _) => service.MarkFilePlayedAsync(request))
|
||||
.WithOpenApi("FileLibrary", "标记文件已播放。")
|
||||
.WithName("MarkFilePlayed");
|
||||
|
||||
endpoints.MapGet<IFileLibraryEndpointService, FileQueryRequest>("api/files/detail", (service, request, _) => service.GetFileAsync(request))
|
||||
.WithOpenApi("FileLibrary", "查询文件详情。")
|
||||
.WithName("GetFileDetail");
|
||||
|
||||
@ -46,6 +46,19 @@ namespace FileShare_Services.Services.FileLibrary
|
||||
public sealed record FileQueryRequest(
|
||||
[property: JsonPropertyName("id")] int Id);
|
||||
|
||||
/// <summary>
|
||||
/// 获取最近文件的请求。
|
||||
/// </summary>
|
||||
public sealed record RecentFilesRequest(
|
||||
[property: JsonPropertyName("type")] string Type = "added",
|
||||
[property: JsonPropertyName("count")] int Count = 12);
|
||||
|
||||
/// <summary>
|
||||
/// 标记文件已播放的请求。
|
||||
/// </summary>
|
||||
public sealed record MarkFilePlayedRequest(
|
||||
[property: JsonPropertyName("id")] int Id);
|
||||
|
||||
/// <summary>
|
||||
/// 分页搜索已扫描文件的请求。
|
||||
/// </summary>
|
||||
@ -105,7 +118,10 @@ namespace FileShare_Services.Services.FileLibrary
|
||||
string ContentType,
|
||||
string StreamUrl,
|
||||
string? TextUrl,
|
||||
bool BrowserPlayable);
|
||||
bool BrowserPlayable,
|
||||
string? ThumbnailUrl = null,
|
||||
double? VideoDuration = null,
|
||||
DateTime? LastPlayedAt = null);
|
||||
|
||||
/// <summary>
|
||||
/// 浏览文件库目录结构的请求。
|
||||
|
||||
@ -29,7 +29,7 @@ namespace FileShare_Services.Services.FileLibrary
|
||||
/// <inheritdoc />
|
||||
public async Task<IApiResponse> AddRootAsync(AddLibraryRootRequest request)
|
||||
{
|
||||
return ResponseHelper.Ok(await fileLibrary.AddRootAsync(request), "文件库目录已添加并完成扫描。");
|
||||
return ResponseHelper.Ok(await fileLibrary.AddRootAsync(request), "文件库目录已添加,后续扫描将自动入库。");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -87,6 +87,20 @@ namespace FileShare_Services.Services.FileLibrary
|
||||
return ResponseHelper.Ok(result);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IApiResponse> GetRecentFilesAsync(RecentFilesRequest request)
|
||||
{
|
||||
var items = await fileLibrary.GetRecentFilesAsync(request.Type, request.Count);
|
||||
return ResponseHelper.Ok(items);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IApiResponse> MarkFilePlayedAsync(MarkFilePlayedRequest request)
|
||||
{
|
||||
await fileLibrary.MarkFilePlayedAsync(request.Id);
|
||||
return ResponseHelper.Succeed();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证文件 ID 是否有效,无效时抛出 <see cref="ArgumentException"/>。
|
||||
/// </summary>
|
||||
|
||||
@ -10,7 +10,7 @@ namespace FileShare_Services.Services.FileLibrary
|
||||
/// <summary>
|
||||
/// 文件库核心业务服务,实现磁盘枚举、目录管理、文件扫描与检索。
|
||||
/// </summary>
|
||||
public sealed class FileLibraryService(AppDataContext db) : IFileLibraryService
|
||||
public sealed class FileLibraryService(AppDataContext db, IVideoThumbnailService thumbnailService) : IFileLibraryService
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认扫描间隔(分钟),当请求未指定间隔时使用。
|
||||
@ -80,7 +80,9 @@ namespace FileShare_Services.Services.FileLibrary
|
||||
existing.DisplayName = ResolveDisplayName(normalized, request.DisplayName);
|
||||
existing.ScanIntervalMinutes = NormalizeInterval(request.ScanIntervalMinutes);
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
return await ScanRootAsync(existing.Id, cancellationToken);
|
||||
var existingCount = await db.ManagedFileRecords
|
||||
.CountAsync(file => file.LibraryRootId == existing.Id && file.Exists, cancellationToken);
|
||||
return ToRootDto(existing, existingCount);
|
||||
}
|
||||
|
||||
var root = new ManagedLibraryRoot
|
||||
@ -95,7 +97,7 @@ namespace FileShare_Services.Services.FileLibrary
|
||||
db.ManagedLibraryRoots.Add(root);
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return await ScanRootAsync(root.Id, cancellationToken);
|
||||
return ToRootDto(root, 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -178,6 +180,24 @@ namespace FileShare_Services.Services.FileLibrary
|
||||
record.ContentType = contentType;
|
||||
record.Exists = true;
|
||||
record.LastSeenAt = DateTime.UtcNow;
|
||||
|
||||
if (mediaType == "video" && record.ThumbnailId is null)
|
||||
{
|
||||
var thumbnail = await thumbnailService.GenerateThumbnailAsync(root.Id, absolutePath, cancellationToken);
|
||||
if (thumbnail is not null)
|
||||
{
|
||||
var map = new ManagedThumbnailMap
|
||||
{
|
||||
LibraryRootId = root.Id,
|
||||
RelativePath = thumbnail.RelativePath,
|
||||
ContentType = thumbnail.ContentType,
|
||||
};
|
||||
db.ManagedThumbnailMaps.Add(map);
|
||||
record.Thumbnail = map;
|
||||
}
|
||||
|
||||
record.VideoDuration ??= thumbnailService.GetVideoDuration(absolutePath);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var stale in existing.Values.Where(file => !seen.Contains(file.AbsolutePath)))
|
||||
@ -356,6 +376,34 @@ namespace FileShare_Services.Services.FileLibrary
|
||||
return new TextPreviewDto(file.Id, file.FileName, content, stream.Length > MaxTextPreviewBytes);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<FileRecordDto>> GetRecentFilesAsync(string type, int count = 12, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var query = db.ManagedFileRecords
|
||||
.AsNoTracking()
|
||||
.Where(file => file.Exists && file.LibraryRoot != null && file.LibraryRoot.IsAvailable);
|
||||
|
||||
query = type == "played"
|
||||
? query.Where(file => file.LastPlayedAt != null).OrderByDescending(file => file.LastPlayedAt)
|
||||
: query.OrderByDescending(file => file.CreatedAt);
|
||||
|
||||
return await query
|
||||
.Take(Math.Clamp(count, 1, 48))
|
||||
.Select(file => ToFileDto(file))
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task MarkFilePlayedAsync(int id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var record = await db.ManagedFileRecords.FindAsync([id], cancellationToken);
|
||||
if (record is not null)
|
||||
{
|
||||
record.LastPlayedAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 深度优先遍历目录树,枚举所有被 <see cref="MediaFileTypes"/> 支持的媒体文件路径。
|
||||
/// 遇到无权限的目录时跳过该分支继续遍历。
|
||||
@ -508,7 +556,10 @@ namespace FileShare_Services.Services.FileLibrary
|
||||
file.ContentType,
|
||||
$"/api/files/{file.Id}/stream",
|
||||
file.MediaType == "text" ? $"/api/files/text?id={file.Id}" : null,
|
||||
MediaFileTypes.IsBrowserPlayable(file.Extension));
|
||||
MediaFileTypes.IsBrowserPlayable(file.Extension),
|
||||
file.ThumbnailId is null ? null : $"/api/thumbnails/{file.ThumbnailId}",
|
||||
file.VideoDuration,
|
||||
file.LastPlayedAt);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -84,5 +84,9 @@ namespace FileShare_Services.Services.FileLibrary
|
||||
/// <param name="request">包含根目录 ID 和路径的请求。</param>
|
||||
/// <returns>API 响应。</returns>
|
||||
Task<IApiResponse> BrowseDirectoryAsync(BrowseDirectoryRequest request);
|
||||
|
||||
Task<IApiResponse> GetRecentFilesAsync(RecentFilesRequest request);
|
||||
|
||||
Task<IApiResponse> MarkFilePlayedAsync(MarkFilePlayedRequest request);
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,5 +97,9 @@ namespace FileShare_Services.Services.FileLibrary
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>目录浏览响应,包含子目录和文件列表。</returns>
|
||||
Task<BrowseDirectoryResponse> BrowseDirectoryAsync(BrowseDirectoryRequest request, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<List<FileRecordDto>> GetRecentFilesAsync(string type, int count = 12, CancellationToken cancellationToken = default);
|
||||
|
||||
Task MarkFilePlayedAsync(int id, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
namespace FileShare_Services.Services.FileLibrary
|
||||
{
|
||||
public interface IVideoThumbnailService
|
||||
{
|
||||
Task<GeneratedThumbnail?> GenerateThumbnailAsync(int libraryRootId, string videoPath, CancellationToken ct = default);
|
||||
string GetAbsolutePath(string relativePath);
|
||||
double? GetVideoDuration(string videoPath);
|
||||
}
|
||||
|
||||
public sealed record GeneratedThumbnail(string RelativePath, string ContentType);
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
namespace FileShare_Services.Services.FileLibrary
|
||||
{
|
||||
public sealed class ThumbnailStorageOptions
|
||||
{
|
||||
public string RootPath { get; set; } = Path.Combine(AppContext.BaseDirectory, "thumbnails");
|
||||
public string FfmpegPath { get; set; } = Path.Combine("tools", "ffmpeg", "bin", "ffmpeg.exe");
|
||||
public string FfprobePath { get; set; } = Path.Combine("tools", "ffmpeg", "bin", "ffprobe.exe");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
using FileShare_EFCore.Database;
|
||||
using FileShare_Services.Core;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FileShare_Services.Services.FileLibrary
|
||||
{
|
||||
public interface IThumbnailStreamService
|
||||
{
|
||||
Task<FileStreamResponse?> GetThumbnailAsync(int id, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public sealed class ThumbnailStreamService(AppDataContext db, IVideoThumbnailService thumbnails) : IThumbnailStreamService
|
||||
{
|
||||
public async Task<FileStreamResponse?> GetThumbnailAsync(int id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var thumbnail = await db.ManagedThumbnailMaps
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(item => item.Id == id, cancellationToken);
|
||||
|
||||
if (thumbnail is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var absolutePath = thumbnails.GetAbsolutePath(thumbnail.RelativePath);
|
||||
if (!File.Exists(absolutePath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new FileStreamResponse(
|
||||
absolutePath,
|
||||
Path.GetFileName(absolutePath),
|
||||
thumbnail.ContentType,
|
||||
File.GetLastWriteTimeUtc(absolutePath));
|
||||
}
|
||||
}
|
||||
}
|
||||
136
FileShare-Services/Services/FileLibrary/VideoThumbnailService.cs
Normal file
136
FileShare-Services/Services/FileLibrary/VideoThumbnailService.cs
Normal file
@ -0,0 +1,136 @@
|
||||
using System.Diagnostics;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace FileShare_Services.Services.FileLibrary
|
||||
{
|
||||
public sealed class VideoThumbnailService : IVideoThumbnailService
|
||||
{
|
||||
private readonly string _thumbnailDir;
|
||||
private readonly string _ffmpegPath;
|
||||
private readonly string _ffprobePath;
|
||||
|
||||
public VideoThumbnailService(ThumbnailStorageOptions options)
|
||||
{
|
||||
_thumbnailDir = Path.IsPathRooted(options.RootPath)
|
||||
? options.RootPath
|
||||
: Path.Combine(AppContext.BaseDirectory, options.RootPath);
|
||||
_ffmpegPath = ResolveExecutablePath(options.FfmpegPath);
|
||||
_ffprobePath = ResolveExecutablePath(options.FfprobePath);
|
||||
Directory.CreateDirectory(_thumbnailDir);
|
||||
}
|
||||
|
||||
public Task<GeneratedThumbnail?> GenerateThumbnailAsync(int libraryRootId, string videoPath, CancellationToken ct = default)
|
||||
{
|
||||
var hash = ComputeHash(videoPath);
|
||||
var relativePath = Path.Combine($"root-{libraryRootId}", $"{hash}.jpg");
|
||||
var outputPath = GetAbsolutePath(relativePath);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outputPath)!);
|
||||
if (File.Exists(outputPath))
|
||||
return Task.FromResult<GeneratedThumbnail?>(new GeneratedThumbnail(relativePath, "image/jpeg"));
|
||||
|
||||
try
|
||||
{
|
||||
var args = $"-ss 00:00:01 -i \"{videoPath}\" -vframes 1 -q:v 5 -vf \"scale=320:-2\" \"{outputPath}\" -y";
|
||||
|
||||
using var process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = _ffmpegPath,
|
||||
Arguments = args,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
},
|
||||
EnableRaisingEvents = true,
|
||||
};
|
||||
|
||||
process.Start();
|
||||
|
||||
using (ct.Register(() =>
|
||||
{
|
||||
try { process.Kill(); } catch { /* 进程可能已退出 */ }
|
||||
}))
|
||||
{
|
||||
process.WaitForExit(10000);
|
||||
}
|
||||
|
||||
if (File.Exists(outputPath) && new FileInfo(outputPath).Length > 0)
|
||||
return Task.FromResult<GeneratedThumbnail?>(new GeneratedThumbnail(relativePath, "image/jpeg"));
|
||||
|
||||
return Task.FromResult<GeneratedThumbnail?>(null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Warning(ex, "生成视频缩略图失败 {VideoPath}", videoPath);
|
||||
return Task.FromResult<GeneratedThumbnail?>(null);
|
||||
}
|
||||
}
|
||||
|
||||
public string GetAbsolutePath(string relativePath)
|
||||
{
|
||||
var root = Path.GetFullPath(_thumbnailDir);
|
||||
var fullPath = Path.GetFullPath(Path.Combine(root, relativePath));
|
||||
if (!fullPath.StartsWith(root + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.Equals(fullPath, root, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException("Thumbnail path is outside of the configured storage root.");
|
||||
}
|
||||
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
private static string ResolveExecutablePath(string executablePath)
|
||||
{
|
||||
if (Path.IsPathRooted(executablePath))
|
||||
{
|
||||
return executablePath;
|
||||
}
|
||||
|
||||
return executablePath.Contains(Path.DirectorySeparatorChar)
|
||||
|| executablePath.Contains(Path.AltDirectorySeparatorChar)
|
||||
? Path.Combine(AppContext.BaseDirectory, executablePath)
|
||||
: executablePath;
|
||||
}
|
||||
|
||||
public double? GetVideoDuration(string videoPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
var args = $"-v error -show_entries format=duration -of csv=p=0 \"{videoPath}\"";
|
||||
|
||||
using var process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = _ffprobePath,
|
||||
Arguments = args,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
},
|
||||
};
|
||||
|
||||
process.Start();
|
||||
var output = process.StandardOutput.ReadToEnd().Trim();
|
||||
process.WaitForExit(5000);
|
||||
|
||||
if (double.TryParse(output, out var duration) && duration > 0)
|
||||
return Math.Round(duration, 1);
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Warning(ex, "获取视频时长失败 {VideoPath}", videoPath);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public string ComputeHash(string videoPath) =>
|
||||
Convert.ToHexString(MD5.HashData(Encoding.UTF8.GetBytes(videoPath))).ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
@ -43,6 +43,9 @@ export interface FileRecordDto {
|
||||
streamUrl: string
|
||||
textUrl: string | null
|
||||
browserPlayable: boolean
|
||||
thumbnailUrl: string | null
|
||||
videoDuration: number | null
|
||||
lastPlayedAt: string | null
|
||||
}
|
||||
|
||||
export interface BrowseDirectoryResponse {
|
||||
@ -98,5 +101,10 @@ export const api = {
|
||||
getTextPreview: (id: number) =>
|
||||
request<TextPreviewDto>(`files/text${qs({ id })}`),
|
||||
mediaUrl: (path: string) => apiUrl(path),
|
||||
thumbnailUrl: (path: string) => apiUrl(path),
|
||||
getRecentFiles: (type: string, count = 12) =>
|
||||
request<FileRecordDto[]>(`files/recent${qs({ type, count })}`),
|
||||
markFilePlayed: (id: number) =>
|
||||
request('files/played', { method: 'POST', body: { id } }),
|
||||
qrCode: () => request<{ url: string; qrCodeBase64: string }>('qrcode'),
|
||||
}
|
||||
|
||||
@ -498,19 +498,6 @@ a {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.player-panel video {
|
||||
width: 100%;
|
||||
max-height: 54vh;
|
||||
margin-top: 12px;
|
||||
border-radius: 10px;
|
||||
background: #111827;
|
||||
}
|
||||
|
||||
.player-panel audio {
|
||||
width: 100%;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.open-media-link {
|
||||
display: block;
|
||||
width: 100%;
|
||||
@ -524,19 +511,6 @@ a {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.player-panel pre {
|
||||
overflow: auto;
|
||||
max-height: 54vh;
|
||||
margin: 12px 0 0;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
background: #f8fafc;
|
||||
color: #111827;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.mobile-list {
|
||||
display: grid;
|
||||
gap: 9px;
|
||||
@ -724,6 +698,257 @@ a {
|
||||
background: var(--panel);
|
||||
}
|
||||
|
||||
/* Root tabs */
|
||||
.root-tabs {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
margin-bottom: 14px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
padding: 4px;
|
||||
background: var(--panel);
|
||||
}
|
||||
|
||||
.root-tabs button {
|
||||
flex: 1;
|
||||
min-height: 36px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
color: var(--muted);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.root-tabs button.active {
|
||||
color: #fff;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
/* View toggle */
|
||||
.view-toggle-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.view-toggle {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.view-toggle button {
|
||||
min-height: 30px;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
padding: 4px 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: var(--muted);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.view-toggle button.active {
|
||||
color: #fff;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.view-toggle button + button {
|
||||
border-left: 1px solid var(--line);
|
||||
}
|
||||
|
||||
/* Section header with view toggle inline */
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.section-header h3 {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
font-weight: 800;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
/* File grid / card view */
|
||||
.file-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
|
||||
gap: 10px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 14px;
|
||||
padding: 10px;
|
||||
background: var(--panel);
|
||||
}
|
||||
|
||||
.file-card {
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.file-card.active {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 2px var(--accent);
|
||||
}
|
||||
|
||||
.file-card:hover:not(:disabled) {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.card-thumb {
|
||||
display: block;
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 10;
|
||||
object-fit: contain;
|
||||
background: #111827;
|
||||
}
|
||||
|
||||
.card-thumb-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
aspect-ratio: 16 / 10;
|
||||
border-radius: 999px;
|
||||
margin: 12px auto 0;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
color: #fff;
|
||||
background: var(--accent-strong);
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
display: grid;
|
||||
align-content: start;
|
||||
gap: 3px;
|
||||
padding: 10px 10px 12px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.card-body strong {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.card-body small {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* File thumbnail in list view */
|
||||
.file-thumb {
|
||||
flex: 0 0 72px;
|
||||
width: 72px;
|
||||
height: 48px;
|
||||
border-radius: 6px;
|
||||
object-fit: contain;
|
||||
background: #111827;
|
||||
}
|
||||
|
||||
.file-list .mobile-file {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
min-height: 60px;
|
||||
padding: 8px 10px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.file-list .mobile-file > span:last-child {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Video info bar */
|
||||
.video-info-bar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-top: 10px;
|
||||
padding: 8px 0;
|
||||
font-size: 13px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.video-info-bar span {
|
||||
border-radius: 999px;
|
||||
padding: 3px 9px;
|
||||
background: #f2f4f7;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Player media wrapper */
|
||||
.player-media-wrapper {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.player-media-wrapper video {
|
||||
width: 100%;
|
||||
max-height: 54vh;
|
||||
border-radius: 10px;
|
||||
background: #111827;
|
||||
}
|
||||
|
||||
.player-media-wrapper audio {
|
||||
width: 100%;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.player-media-wrapper pre {
|
||||
overflow: auto;
|
||||
max-height: 54vh;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
background: #f8fafc;
|
||||
color: #111827;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.unsupported-player {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.unsupported-thumb {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
margin: 0 auto 12px;
|
||||
border-radius: 10px;
|
||||
aspect-ratio: 16 / 9;
|
||||
object-fit: contain;
|
||||
background: #111827;
|
||||
}
|
||||
|
||||
/* Recent files section */
|
||||
.recent-files {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.admin-layout,
|
||||
.admin-browser {
|
||||
|
||||
@ -15,6 +15,12 @@ const rootId = ref<number | undefined>()
|
||||
const browsePath = ref<string[]>([])
|
||||
const isBrowsingRoots = ref(true)
|
||||
|
||||
// Recent files & tabs
|
||||
const activeTab = ref<'recent-added' | 'recent-played' | 'libraries'>('libraries')
|
||||
const recentFiles = ref<FileRecordDto[]>([])
|
||||
const recentLoading = ref(false)
|
||||
const viewMode = ref<'list' | 'grid'>('list')
|
||||
|
||||
const qrModal = ref<InstanceType<typeof QrCodeModal> | null>(null)
|
||||
|
||||
const activeRoots = computed(() => roots.value.filter((root) => root.isEnabled && root.isAvailable))
|
||||
@ -34,8 +40,13 @@ const breadcrumbs = computed(() => {
|
||||
})
|
||||
|
||||
const selectedMediaUrl = computed(() => selectedFile.value ? api.mediaUrl(selectedFile.value.streamUrl) : '')
|
||||
const selectedThumbnailUrl = computed(() =>
|
||||
selectedFile.value?.thumbnailUrl ? api.thumbnailUrl(selectedFile.value.thumbnailUrl) : ''
|
||||
)
|
||||
|
||||
const clientTitle = computed(() => {
|
||||
if (activeTab.value === 'recent-added') return '最近添加'
|
||||
if (activeTab.value === 'recent-played') return '最近播放'
|
||||
if (isBrowsingRoots.value) return '文件库'
|
||||
const root = roots.value.find((r) => r.id === rootId.value)
|
||||
const dir = browsePath.value.length > 0 ? browsePath.value[browsePath.value.length - 1] : ''
|
||||
@ -54,6 +65,13 @@ function formatSize(bytes: number) {
|
||||
return `${value.toFixed(value >= 10 ? 1 : 2)} ${units[index]}`
|
||||
}
|
||||
|
||||
function formatDuration(seconds: number | null) {
|
||||
if (!seconds) return ''
|
||||
const m = Math.floor(seconds / 60)
|
||||
const s = Math.floor(seconds % 60)
|
||||
return `${m}:${s.toString().padStart(2, '0')}`
|
||||
}
|
||||
|
||||
function formatDate(value: string | null) {
|
||||
if (!value) return ''
|
||||
return new Date(value).toLocaleString()
|
||||
@ -80,12 +98,35 @@ async function browseDirectory() {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRecentFiles(type: string) {
|
||||
try {
|
||||
errorMessage.value = ''
|
||||
recentLoading.value = true
|
||||
recentFiles.value = await api.getRecentFiles(type, 24)
|
||||
} catch (error) {
|
||||
setError(error)
|
||||
} finally {
|
||||
recentLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function switchTab(tab: 'recent-added' | 'recent-played' | 'libraries') {
|
||||
activeTab.value = tab
|
||||
isBrowsingRoots.value = true
|
||||
browseData.value = null
|
||||
selectedFile.value = null
|
||||
textPreview.value = null
|
||||
if (tab === 'recent-added') loadRecentFiles('added')
|
||||
else if (tab === 'recent-played') loadRecentFiles('played')
|
||||
}
|
||||
|
||||
async function enterRoot(id: number) {
|
||||
rootId.value = id
|
||||
isBrowsingRoots.value = false
|
||||
browsePath.value = []
|
||||
selectedFile.value = null
|
||||
textPreview.value = null
|
||||
activeTab.value = 'libraries'
|
||||
await browseDirectory()
|
||||
}
|
||||
|
||||
@ -96,6 +137,7 @@ function backToRoots() {
|
||||
browsePath.value = []
|
||||
selectedFile.value = null
|
||||
textPreview.value = null
|
||||
activeTab.value = 'libraries'
|
||||
}
|
||||
|
||||
async function navigateTo(path: string) {
|
||||
@ -119,6 +161,11 @@ async function enterSubdirectory(name: string) {
|
||||
async function selectFile(file: FileRecordDto) {
|
||||
selectedFile.value = file
|
||||
textPreview.value = null
|
||||
|
||||
if (file.mediaType === 'video' || file.mediaType === 'audio') {
|
||||
api.markFilePlayed(file.id).catch(() => {})
|
||||
}
|
||||
|
||||
if (file.mediaType !== 'text') return
|
||||
|
||||
try {
|
||||
@ -146,7 +193,9 @@ onMounted(async () => {
|
||||
<div>
|
||||
<h1>{{ clientTitle }}</h1>
|
||||
<p>
|
||||
<template v-if="isBrowsingRoots">{{ activeRoots.length }} 个目录</template>
|
||||
<template v-if="activeTab === 'recent-added'">{{ recentFiles.length }} 个文件</template>
|
||||
<template v-else-if="activeTab === 'recent-played'">{{ recentFiles.length }} 个文件</template>
|
||||
<template v-else-if="isBrowsingRoots">{{ activeRoots.length }} 个目录</template>
|
||||
<template v-else-if="browseData">
|
||||
{{ browseData.subdirectories.length }} 个文件夹 · {{ browseData.files.length }} 个文件
|
||||
</template>
|
||||
@ -161,8 +210,99 @@ onMounted(async () => {
|
||||
|
||||
<p v-if="errorMessage" class="error-banner">{{ errorMessage }}</p>
|
||||
|
||||
<!-- Root tabs -->
|
||||
<nav v-if="isBrowsingRoots" class="root-tabs">
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: activeTab === 'recent-added' }"
|
||||
@click="switchTab('recent-added')"
|
||||
>最近添加</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: activeTab === 'recent-played' }"
|
||||
@click="switchTab('recent-played')"
|
||||
>最近播放</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: activeTab === 'libraries' }"
|
||||
@click="switchTab('libraries')"
|
||||
>文件库</button>
|
||||
</nav>
|
||||
|
||||
<!-- Recently added / played files -->
|
||||
<section v-if="isBrowsingRoots && activeTab !== 'libraries'" class="recent-files">
|
||||
<div class="view-toggle-bar">
|
||||
<div></div>
|
||||
<div class="view-toggle">
|
||||
<button type="button" :class="{ active: viewMode === 'list' }" @click="viewMode = 'list'">列表</button>
|
||||
<button type="button" :class="{ active: viewMode === 'grid' }" @click="viewMode = 'grid'">网格</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p v-if="recentLoading" class="empty-state">加载中...</p>
|
||||
<p v-else-if="recentFiles.length === 0" class="empty-state">
|
||||
{{ activeTab === 'recent-added' ? '暂无最近添加的文件' : '暂无播放记录' }}
|
||||
</p>
|
||||
|
||||
<!-- Grid view -->
|
||||
<div v-else-if="viewMode === 'grid'" class="file-grid">
|
||||
<button
|
||||
v-for="file in recentFiles"
|
||||
:key="file.id"
|
||||
class="file-card"
|
||||
type="button"
|
||||
@click="selectFile(file)"
|
||||
>
|
||||
<img
|
||||
v-if="file.thumbnailUrl"
|
||||
:src="api.thumbnailUrl(file.thumbnailUrl)"
|
||||
class="card-thumb"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
/>
|
||||
<div v-else class="card-thumb-placeholder">{{ file.mediaType }}</div>
|
||||
<div class="card-body">
|
||||
<strong>{{ file.fileName }}</strong>
|
||||
<small>
|
||||
{{ formatSize(file.sizeBytes) }}
|
||||
<template v-if="file.videoDuration"> · {{ formatDuration(file.videoDuration) }}</template>
|
||||
</small>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- List view -->
|
||||
<div v-else class="file-list">
|
||||
<button
|
||||
v-for="file in recentFiles"
|
||||
:key="file.id"
|
||||
class="mobile-file"
|
||||
@click="selectFile(file)"
|
||||
>
|
||||
<img
|
||||
v-if="file.thumbnailUrl"
|
||||
:src="api.thumbnailUrl(file.thumbnailUrl)"
|
||||
class="file-thumb"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
/>
|
||||
<span v-else class="type-badge">{{ file.mediaType }}</span>
|
||||
<span>
|
||||
<strong>{{ file.fileName }}</strong>
|
||||
<small>
|
||||
{{ formatSize(file.sizeBytes) }}
|
||||
<template v-if="file.videoDuration"> · {{ formatDuration(file.videoDuration) }}</template>
|
||||
<template v-if="file.lastPlayedAt && activeTab === 'recent-played'">
|
||||
· {{ formatDate(file.lastPlayedAt) }}
|
||||
</template>
|
||||
</small>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Library root tiles -->
|
||||
<section v-if="isBrowsingRoots" class="root-picker">
|
||||
<section v-if="isBrowsingRoots && activeTab === 'libraries'" class="root-picker">
|
||||
<button
|
||||
v-for="root in activeRoots"
|
||||
:key="root.id"
|
||||
@ -178,7 +318,7 @@ onMounted(async () => {
|
||||
</section>
|
||||
|
||||
<!-- Directory browser -->
|
||||
<template v-else>
|
||||
<template v-else-if="!isBrowsingRoots">
|
||||
<!-- Breadcrumb -->
|
||||
<nav class="breadcrumb-nav">
|
||||
<template v-for="(crumb, index) in breadcrumbs" :key="crumb.path">
|
||||
@ -222,26 +362,46 @@ onMounted(async () => {
|
||||
<span>{{ selectedFile.extension }}</span>
|
||||
</div>
|
||||
|
||||
<video
|
||||
v-if="selectedFile.mediaType === 'video' && selectedFile.browserPlayable"
|
||||
:key="selectedFile.id"
|
||||
controls
|
||||
playsinline
|
||||
webkit-playsinline
|
||||
preload="metadata"
|
||||
>
|
||||
<source :src="selectedMediaUrl" :type="selectedFile.contentType" />
|
||||
</video>
|
||||
<audio
|
||||
v-else-if="selectedFile.mediaType === 'audio' && selectedFile.browserPlayable"
|
||||
:key="selectedFile.id"
|
||||
controls
|
||||
preload="metadata"
|
||||
>
|
||||
<source :src="selectedMediaUrl" :type="selectedFile.contentType" />
|
||||
</audio>
|
||||
<pre v-else-if="selectedFile.mediaType === 'text'">{{ textPreview?.content ?? '加载中...' }}</pre>
|
||||
<p v-else class="unsupported">浏览器不支持在线播放此格式。</p>
|
||||
<div v-if="selectedFile.mediaType === 'video'" class="video-info-bar">
|
||||
<span v-if="selectedFile.videoDuration">时长 {{ formatDuration(selectedFile.videoDuration) }}</span>
|
||||
<span>{{ formatSize(selectedFile.sizeBytes) }}</span>
|
||||
<span>{{ selectedFile.contentType }}</span>
|
||||
</div>
|
||||
|
||||
<div class="player-media-wrapper">
|
||||
<video
|
||||
v-if="selectedFile.mediaType === 'video' && selectedFile.browserPlayable"
|
||||
:key="selectedFile.id"
|
||||
:poster="selectedThumbnailUrl || undefined"
|
||||
controls
|
||||
playsinline
|
||||
webkit-playsinline
|
||||
preload="metadata"
|
||||
>
|
||||
<source :src="selectedMediaUrl" :type="selectedFile.contentType" />
|
||||
</video>
|
||||
<div
|
||||
v-else-if="selectedFile.mediaType === 'video' && !selectedFile.browserPlayable"
|
||||
class="unsupported-player"
|
||||
>
|
||||
<img
|
||||
v-if="selectedThumbnailUrl"
|
||||
:src="selectedThumbnailUrl"
|
||||
class="unsupported-thumb"
|
||||
alt=""
|
||||
/>
|
||||
<p class="unsupported">浏览器不支持在线播放此格式。</p>
|
||||
</div>
|
||||
<audio
|
||||
v-else-if="selectedFile.mediaType === 'audio' && selectedFile.browserPlayable"
|
||||
:key="selectedFile.id"
|
||||
controls
|
||||
preload="metadata"
|
||||
>
|
||||
<source :src="selectedMediaUrl" :type="selectedFile.contentType" />
|
||||
</audio>
|
||||
<pre v-else-if="selectedFile.mediaType === 'text'">{{ textPreview?.content ?? '加载中...' }}</pre>
|
||||
</div>
|
||||
<a
|
||||
v-if="selectedFile.mediaType !== 'text'"
|
||||
class="open-media-link"
|
||||
@ -256,8 +416,44 @@ onMounted(async () => {
|
||||
|
||||
<!-- Files -->
|
||||
<section v-if="browseData.files.length > 0" class="browse-section">
|
||||
<h3>文件</h3>
|
||||
<div class="file-list">
|
||||
<div class="section-header">
|
||||
<h3>文件</h3>
|
||||
<div class="view-toggle">
|
||||
<button type="button" :class="{ active: viewMode === 'list' }" @click="viewMode = 'list'">列表</button>
|
||||
<button type="button" :class="{ active: viewMode === 'grid' }" @click="viewMode = 'grid'">网格</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Grid view -->
|
||||
<div v-if="viewMode === 'grid'" class="file-grid">
|
||||
<button
|
||||
v-for="file in browseData.files"
|
||||
:key="file.id"
|
||||
class="file-card"
|
||||
:class="{ active: selectedFile?.id === file.id }"
|
||||
type="button"
|
||||
@click="selectFile(file)"
|
||||
>
|
||||
<img
|
||||
v-if="file.thumbnailUrl"
|
||||
:src="api.thumbnailUrl(file.thumbnailUrl)"
|
||||
class="card-thumb"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
/>
|
||||
<div v-else class="card-thumb-placeholder">{{ file.mediaType }}</div>
|
||||
<div class="card-body">
|
||||
<strong>{{ file.fileName }}</strong>
|
||||
<small>
|
||||
{{ formatSize(file.sizeBytes) }}
|
||||
<template v-if="file.videoDuration"> · {{ formatDuration(file.videoDuration) }}</template>
|
||||
</small>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- List view -->
|
||||
<div v-else class="file-list">
|
||||
<button
|
||||
v-for="file in browseData.files"
|
||||
:key="file.id"
|
||||
@ -266,10 +462,20 @@ onMounted(async () => {
|
||||
type="button"
|
||||
@click="selectFile(file)"
|
||||
>
|
||||
<span class="type-badge">{{ file.mediaType }}</span>
|
||||
<img
|
||||
v-if="file.thumbnailUrl"
|
||||
:src="api.thumbnailUrl(file.thumbnailUrl)"
|
||||
class="file-thumb"
|
||||
alt=""
|
||||
loading="lazy"
|
||||
/>
|
||||
<span v-else class="type-badge">{{ file.mediaType }}</span>
|
||||
<span>
|
||||
<strong>{{ file.fileName }}</strong>
|
||||
<small>{{ formatSize(file.sizeBytes) }} · {{ formatDate(file.lastWriteTimeUtc) }}</small>
|
||||
<small>
|
||||
{{ formatSize(file.sizeBytes) }}
|
||||
<template v-if="file.videoDuration"> · {{ formatDuration(file.videoDuration) }}</template>
|
||||
</small>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
BIN
tools/ffmpeg/bin/ffmpeg.exe
Normal file
BIN
tools/ffmpeg/bin/ffmpeg.exe
Normal file
Binary file not shown.
BIN
tools/ffmpeg/bin/ffprobe.exe
Normal file
BIN
tools/ffmpeg/bin/ffprobe.exe
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user