WIP: database增加读取功能

单元测试没过
This commit is contained in:
2026-05-22 20:41:43 +08:00
parent 6d65bb6b62
commit d705d47b34
3 changed files with 439 additions and 6 deletions
+216 -5
View File
@@ -6,6 +6,10 @@ namespace OCES.Resonance.Core;
public static class Database
{
static Database()
{
DefaultTypeMap.MatchNamesWithUnderscores = true;
}
/// <summary>
/// 获取数据库连接
/// </summary>
@@ -107,11 +111,11 @@ public static class Database
waveform BLOB
);
CREATE INDEX idx_file_name on audio_files(filename);
CREATE INDEX idx_md5 on audio_files(md5);
CREATE INDEX idx_path on audio_files(path);
CREATE INDEX idx_unique_id on audio_files(unique_id);
CREATE INDEX idx_description on audio_files(description);
CREATE INDEX if not exists idx_file_name on audio_files(filename);
CREATE INDEX if not exists idx_md5 on audio_files(md5);
CREATE INDEX if not exists idx_path on audio_files(path);
CREATE INDEX if not exists idx_unique_id on audio_files(unique_id);
CREATE INDEX if not exists idx_description on audio_files(description);
""";
@@ -253,4 +257,211 @@ public static class Database
var count = connection.ExecuteScalar<int>(sql, new { Md5 = md5, Path = filePath });
return count > 0;
}
#region
// 详情查询公共列(含 BLOB),用于 GetEntryById / GetEntryByUniqueId
private const string DetailSelectColumns = @"
id, unique_id, short_id, md5, path, filename, folder, directory,
duration, bit_depth, channels, sample_rate, type,
date_added, original_modification_date AS last_write_time, origination_time AS creation_time,
bpm, frame_rate, timecode, description, category, subcategory,
cat_id, category_full, genre, style, mood, keywords, rating,
artist, composer, designer, recordist, publisher, manufacturer,
originator AS bwf_originator, originator_ref AS bwf_originator_ref, project_name, library, cd_title,
track_title, episode, scene, take, tape, cue_number, sync_point,
release_date, track_year, is_edited, is_split, location, [group],
markers, comments, notes, copyright, coding_history AS bwf_coding_history, microphone,
mic_perspective,
fx_name, channel_layout, disk_number, track_number,
artwork, waveform, bwf_umid,
user1, user2, user3, user4, user5, user6, user7, user8";
// 列表查询公共列(排除 BLOBartwork / waveform / bwf_umid),减少数据传输
private const string ListSelectColumns = @"
id, unique_id, short_id, md5, path, filename, folder, directory,
duration, bit_depth, channels, sample_rate, type,
date_added, original_modification_date AS last_write_time, origination_time AS creation_time,
bpm, frame_rate, timecode, description, category, subcategory,
cat_id, category_full, genre, style, mood, keywords, rating,
artist, composer, designer, recordist, publisher, manufacturer,
originator AS bwf_originator, originator_ref AS bwf_originator_ref, project_name, library, cd_title,
track_title, episode, scene, take, tape, cue_number, sync_point,
release_date, track_year, is_edited, is_split, location, [group],
markers, comments, notes, copyright, coding_history AS bwf_coding_history, microphone,
mic_perspective,
fx_name, channel_layout, disk_number, track_number,
user1, user2, user3, user4, user5, user6, user7, user8";
// 排序白名单,防止 SQL 注入
private static readonly HashSet<string> AllowedSortColumns = new(StringComparer.OrdinalIgnoreCase)
{
"filename", "duration", "sample_rate", "channels",
"date_added", "rating", "type", "category", "id"
};
/// <summary>
/// 按主键取回单条记录(含 BLOB 字段:artwork / waveform / bwf_umid
/// </summary>
public static AudioFileMeta? GetEntryById(int id)
{
using var connection = GetConnection();
connection.Open();
const string sql = $"SELECT {DetailSelectColumns} FROM audio_files WHERE id = @Id;";
return connection.QueryFirstOrDefault<AudioFileMeta>(sql, new { Id = id });
}
/// <summary>
/// 按全局唯一标识符取回单条记录(含 BLOB 字段)
/// </summary>
public static AudioFileMeta? GetEntryByUniqueId(string uniqueId)
{
using var connection = GetConnection();
connection.Open();
const string sql = $"SELECT {DetailSelectColumns} FROM audio_files WHERE unique_id = @UniqueId;";
return connection.QueryFirstOrDefault<AudioFileMeta>(sql, new { UniqueId = uniqueId });
}
/// <summary>
/// 多条件过滤查询(排除 BLOB 字段),支持分页和排序
/// </summary>
public static IEnumerable<AudioFileMeta> QueryEntries(
string? searchText = null,
double? minDuration = null,
double? maxDuration = null,
int? sampleRate = null,
int? channels = null,
string? category = null,
string? sortBy = null,
bool sortDescending = false,
int limit = 100,
int offset = 0)
{
using var connection = GetConnection();
connection.Open();
var where = new List<string>();
var parameters = new DynamicParameters();
if (!string.IsNullOrWhiteSpace(searchText))
{
where.Add("(filename LIKE @SearchText OR description LIKE @SearchText OR keywords LIKE @SearchText OR fx_name LIKE @SearchText)");
parameters.Add("@SearchText", $"%{searchText}%");
}
if (minDuration.HasValue)
{
where.Add("duration >= @MinDuration");
parameters.Add("@MinDuration", minDuration.Value);
}
if (maxDuration.HasValue)
{
where.Add("duration <= @MaxDuration");
parameters.Add("@MaxDuration", maxDuration.Value);
}
if (sampleRate.HasValue)
{
where.Add("sample_rate = @SampleRate");
parameters.Add("@SampleRate", sampleRate.Value);
}
if (channels.HasValue)
{
where.Add("channels = @Channels");
parameters.Add("@Channels", channels.Value);
}
if (!string.IsNullOrWhiteSpace(category))
{
where.Add("category = @Category");
parameters.Add("@Category", category);
}
var whereClause = where.Count > 0 ? "WHERE " + string.Join(" AND ", where) : "";
var orderBy = "ORDER BY date_added DESC";
if (!string.IsNullOrWhiteSpace(sortBy) && AllowedSortColumns.Contains(sortBy))
{
var dir = sortDescending ? "DESC" : "ASC";
orderBy = $"ORDER BY [{sortBy}] {dir}";
}
var sql = $"""
SELECT {ListSelectColumns}
FROM audio_files
{whereClause}
{orderBy}
LIMIT @Limit OFFSET @Offset;
""";
parameters.Add("@Limit", limit);
parameters.Add("@Offset", offset);
return connection.Query<AudioFileMeta>(sql, parameters);
}
/// <summary>
/// 文本搜索(filename / description / keywords / fx_name),排除 BLOB 字段
/// </summary>
public static IEnumerable<AudioFileMeta> SearchEntries(string query, int limit = 100, int offset = 0)
{
return QueryEntries(searchText: query, limit: limit, offset: offset);
}
/// <summary>
/// 返回匹配条件的总记录数(用于分页控件)
/// </summary>
public static int GetTotalCount(
string? searchText = null,
double? minDuration = null,
double? maxDuration = null,
int? sampleRate = null,
int? channels = null,
string? category = null)
{
using var connection = GetConnection();
connection.Open();
var where = new List<string>();
var parameters = new DynamicParameters();
if (!string.IsNullOrWhiteSpace(searchText))
{
where.Add("(filename LIKE @SearchText OR description LIKE @SearchText OR keywords LIKE @SearchText OR fx_name LIKE @SearchText)");
parameters.Add("@SearchText", $"%{searchText}%");
}
if (minDuration.HasValue)
{
where.Add("duration >= @MinDuration");
parameters.Add("@MinDuration", minDuration.Value);
}
if (maxDuration.HasValue)
{
where.Add("duration <= @MaxDuration");
parameters.Add("@MaxDuration", maxDuration.Value);
}
if (sampleRate.HasValue)
{
where.Add("sample_rate = @SampleRate");
parameters.Add("@SampleRate", sampleRate.Value);
}
if (channels.HasValue)
{
where.Add("channels = @Channels");
parameters.Add("@Channels", channels.Value);
}
if (!string.IsNullOrWhiteSpace(category))
{
where.Add("category = @Category");
parameters.Add("@Category", category);
}
var whereClause = where.Count > 0 ? "WHERE " + string.Join(" AND ", where) : "";
var sql = $"SELECT COUNT(*) FROM audio_files {whereClause};";
return connection.ExecuteScalar<int>(sql, parameters);
}
#endregion
}