feat: 统一资源管理

This commit is contained in:
alivender
2025-08-02 13:14:01 +08:00
parent e5f2be616c
commit 9904fecbee
15 changed files with 1178 additions and 838 deletions

View File

@@ -229,9 +229,9 @@ public class Exam
}
/// <summary>
/// 实验资源表(图片等)
/// 资源类,统一管理实验资源、用户比特流等各类资源
/// </summary>
public class ExamResource
public class Resource
{
/// <summary>
/// 资源的唯一标识符
@@ -240,17 +240,29 @@ public class ExamResource
public int ID { get; set; }
/// <summary>
/// 所属实验ID
/// 上传资源的用户ID
/// </summary>
[NotNull]
public required string ExamID { get; set; }
public required Guid UserID { get; set; }
/// <summary>
/// 资源类型images, markdown, bitstream, diagram, project
/// 所属实验ID可选如果不属于特定实验则为空
/// </summary>
[Nullable]
public string? ExamID { get; set; }
/// <summary>
/// 资源类型images, markdown, bitstream, diagram, project等
/// </summary>
[NotNull]
public required string ResourceType { get; set; }
/// <summary>
/// 资源用途template模板或 user用户上传
/// </summary>
[NotNull]
public required string ResourcePurpose { get; set; }
/// <summary>
/// 资源名称(包含文件扩展名)
/// </summary>
@@ -264,10 +276,10 @@ public class ExamResource
public required byte[] Data { get; set; }
/// <summary>
/// 资源创建时间
/// 资源创建/上传时间
/// </summary>
[NotNull]
public DateTime CreatedTime { get; set; } = DateTime.Now;
public DateTime UploadTime { get; set; } = DateTime.Now;
/// <summary>
/// 资源的MIME类型
@@ -305,6 +317,22 @@ public class ExamResource
/// </summary>
public const string Project = "project";
}
/// <summary>
/// 资源用途枚举
/// </summary>
public static class ResourcePurposes
{
/// <summary>
/// 模板资源,通常由管理员上传,供用户参考
/// </summary>
public const string Template = "template";
/// <summary>
/// 用户上传的资源
/// </summary>
public const string User = "user";
}
}
/// <summary>
@@ -355,7 +383,7 @@ public class AppDataConnection : DataConnection
this.CreateTable<User>();
this.CreateTable<Board>();
this.CreateTable<Exam>();
this.CreateTable<ExamResource>();
this.CreateTable<Resource>();
logger.Info("数据库表创建完成");
}
@@ -368,7 +396,7 @@ public class AppDataConnection : DataConnection
this.DropTable<User>();
this.DropTable<Board>();
this.DropTable<Exam>();
this.DropTable<ExamResource>();
this.DropTable<Resource>();
logger.Warn("所有数据库表已删除");
}
@@ -828,9 +856,9 @@ public class AppDataConnection : DataConnection
public ITable<Exam> ExamTable => this.GetTable<Exam>();
/// <summary>
/// 实验资源表
/// 资源表(统一管理实验资源、用户比特流等)
/// </summary>
public ITable<ExamResource> ExamResourceTable => this.GetTable<ExamResource>();
public ITable<Resource> ResourceTable => this.GetTable<Resource>();
/// <summary>
/// 创建新实验
@@ -933,34 +961,44 @@ public class AppDataConnection : DataConnection
}
/// <summary>
/// 添加实验资源
/// 添加资源
/// </summary>
/// <param name="examId">所属实验ID</param>
/// <param name="userId">上传用户ID</param>
/// <param name="resourceType">资源类型</param>
/// <param name="resourcePurpose">资源用途template 或 user</param>
/// <param name="resourceName">资源名称</param>
/// <param name="data">资源二进制数据</param>
/// <param name="examId">所属实验ID可选</param>
/// <param name="mimeType">MIME类型可选将根据文件扩展名自动确定</param>
/// <returns>创建的资源</returns>
public Result<ExamResource> AddExamResource(string examId, string resourceType, string resourceName, byte[] data, string? mimeType = null)
public Result<Resource> AddResource(Guid userId, string resourceType, string resourcePurpose, string resourceName, byte[] data, string? examId = null, string? mimeType = null)
{
try
{
// 验证实验是否存在
var exam = this.ExamTable.Where(e => e.ID == examId).FirstOrDefault();
if (exam == null)
// 验证用户是否存在
var user = this.UserTable.Where(u => u.ID == userId).FirstOrDefault();
if (user == null)
{
logger.Error($"实验不存在: {examId}");
return new(new Exception($"实验不存在: {examId}"));
logger.Error($"用户不存在: {userId}");
return new(new Exception($"用户不存在: {userId}"));
}
// 检查资源是否存在
var existingResource = this.ExamResourceTable
.Where(r => r.ExamID == examId && r.ResourceType == resourceType && r.ResourceName == resourceName)
.FirstOrDefault();
if (existingResource != null)
// 如果指定了实验ID验证实验是否存在
if (!string.IsNullOrEmpty(examId))
{
logger.Error($"资源已存在: {examId}/{resourceType}/{resourceName}");
return new(new Exception($"资源已存在: {examId}/{resourceType}/{resourceName}"));
var exam = this.ExamTable.Where(e => e.ID == examId).FirstOrDefault();
if (exam == null)
{
logger.Error($"实验不存在: {examId}");
return new(new Exception($"实验不存在: {examId}"));
}
}
// 验证资源用途
if (resourcePurpose != Resource.ResourcePurposes.Template && resourcePurpose != Resource.ResourcePurposes.User)
{
logger.Error($"无效的资源用途: {resourcePurpose}");
return new(new Exception($"无效的资源用途: {resourcePurpose}"));
}
// 如果未指定MIME类型根据文件扩展名自动确定
@@ -970,49 +1008,126 @@ public class AppDataConnection : DataConnection
mimeType = GetMimeTypeFromExtension(extension, resourceName);
}
var resource = new ExamResource
var resource = new Resource
{
UserID = userId,
ExamID = examId,
ResourceType = resourceType,
ResourcePurpose = resourcePurpose,
ResourceName = resourceName,
Data = data,
MimeType = mimeType,
CreatedTime = DateTime.Now
UploadTime = DateTime.Now
};
this.Insert(resource);
logger.Info($"新资源已添加: {examId}/{resourceType}/{resourceName} ({data.Length} bytes)");
var insertedId = this.InsertWithIdentity(resource);
resource.ID = Convert.ToInt32(insertedId);
logger.Info($"新资源已添加: {userId}/{resourceType}/{resourcePurpose}/{resourceName} ({data.Length} bytes)" +
(examId != null ? $" [实验: {examId}]" : "") + $" [ID: {resource.ID}]");
return new(resource);
}
catch (Exception ex)
{
logger.Error($"添加实验资源时出错: {ex.Message}");
logger.Error($"添加资源时出错: {ex.Message}");
return new(ex);
}
}
/// <summary>
/// 获取指定实验ID的指定资源类型的所有资源的ID和名称
/// </summary>
/// <param name="examId">实验ID</param>
/// 获取资源信息列表(返回ID和名称
/// <param name="resourceType">资源类型</param>
/// <param name="examId">实验ID可选</param>
/// <param name="resourcePurpose">资源用途(可选)</param>
/// <param name="userId">用户ID可选</param>
/// </summary>
/// <returns>资源信息列表</returns>
public Result<(int ID, string Name)[]> GetExamResourceList(string examId, string resourceType)
public Result<(int ID, string Name)[]> GetResourceList(string resourceType, string? examId = null, string? resourcePurpose = null, Guid? userId = null)
{
try
{
var resources = this.ExamResourceTable
.Where(r => r.ExamID == examId && r.ResourceType == resourceType)
var query = this.ResourceTable.Where(r => r.ResourceType == resourceType);
if (examId != null)
{
query = query.Where(r => r.ExamID == examId);
}
if (resourcePurpose != null)
{
query = query.Where(r => r.ResourcePurpose == resourcePurpose);
}
if (userId != null)
{
query = query.Where(r => r.UserID == userId);
}
var resources = query
.Select(r => new { r.ID, r.ResourceName })
.ToArray();
var result = resources.Select(r => (r.ID, r.ResourceName)).ToArray();
logger.Info($"获取实验资源列表: {examId}/{resourceType},共 {result.Length} 个资源");
logger.Info($"获取资源列表: {resourceType}" +
(examId != null ? $"/{examId}" : "") +
(resourcePurpose != null ? $"/{resourcePurpose}" : "") +
(userId != null ? $"/{userId}" : "") +
$",共 {result.Length} 个资源");
return new(result);
}
catch (Exception ex)
{
logger.Error($"获取实验资源列表时出错: {ex.Message}");
logger.Error($"获取资源列表时出错: {ex.Message}");
return new(ex);
}
}
/// <summary>
/// 获取完整的资源列表
/// </summary>
/// <param name="examId">实验ID可选</param>
/// <param name="resourceType">资源类型(可选)</param>
/// <param name="resourcePurpose">资源用途(可选)</param>
/// <param name="userId">用户ID可选</param>
/// <returns>完整的资源对象列表</returns>
public Result<List<Resource>> GetFullResourceList(string? examId = null, string? resourceType = null, string? resourcePurpose = null, Guid? userId = null)
{
try
{
var query = this.ResourceTable.AsQueryable();
if (examId != null)
{
query = query.Where(r => r.ExamID == examId);
}
if (resourceType != null)
{
query = query.Where(r => r.ResourceType == resourceType);
}
if (resourcePurpose != null)
{
query = query.Where(r => r.ResourcePurpose == resourcePurpose);
}
if (userId != null)
{
query = query.Where(r => r.UserID == userId);
}
var resources = query.OrderByDescending(r => r.UploadTime).ToList();
logger.Info($"获取完整资源列表" +
(examId != null ? $" [实验: {examId}]" : "") +
(resourceType != null ? $" [类型: {resourceType}]" : "") +
(resourcePurpose != null ? $" [用途: {resourcePurpose}]" : "") +
(userId != null ? $" [用户: {userId}]" : "") +
$",共 {resources.Count} 个资源");
return new(resources);
}
catch (Exception ex)
{
logger.Error($"获取完整资源列表时出错: {ex.Message}");
return new(ex);
}
}
@@ -1022,16 +1137,16 @@ public class AppDataConnection : DataConnection
/// </summary>
/// <param name="resourceId">资源ID</param>
/// <returns>资源数据</returns>
public Result<Optional<ExamResource>> GetExamResourceById(int resourceId)
public Result<Optional<Resource>> GetResourceById(int resourceId)
{
try
{
var resource = this.ExamResourceTable.Where(r => r.ID == resourceId).FirstOrDefault();
var resource = this.ResourceTable.Where(r => r.ID == resourceId).FirstOrDefault();
if (resource == null)
{
logger.Info($"未找到资源: {resourceId}");
return new(Optional<ExamResource>.None);
return new(Optional<Resource>.None);
}
logger.Info($"成功获取资源: {resourceId} ({resource.ResourceName})");
@@ -1045,15 +1160,15 @@ public class AppDataConnection : DataConnection
}
/// <summary>
/// 删除实验资源
/// 删除资源
/// </summary>
/// <param name="resourceId">资源ID</param>
/// <returns>删除的记录数</returns>
public Result<int> DeleteExamResource(int resourceId)
public Result<int> DeleteResource(int resourceId)
{
try
{
var result = this.ExamResourceTable.Where(r => r.ID == resourceId).Delete();
var result = this.ResourceTable.Where(r => r.ID == resourceId).Delete();
logger.Info($"资源已删除: {resourceId},删除记录数: {result}");
return new(result);
}
@@ -1132,29 +1247,20 @@ public class AppDataConnection : DataConnection
}
/// <summary>
/// 删除所有实验
/// 根据文件扩展名获取比特流MIME类型
/// </summary>
/// <returns>删除的实验数量</returns>
public int DeleteAllExams()
/// <param name="extension">文件扩展名</param>
/// <returns>MIME类型</returns>
private string GetBitstreamMimeType(string extension)
{
// 先删除所有实验资源
var resourceDeleteCount = this.DeleteAllExamResources();
logger.Info($"已删除所有实验资源,共删除 {resourceDeleteCount} 个资源");
// 再删除所有实验
var examDeleteCount = this.ExamTable.Delete();
logger.Info($"已删除所有实验,共删除 {examDeleteCount} 个实验");
return examDeleteCount;
}
/// <summary>
/// 删除所有实验资源
/// </summary>
/// <returns>删除的资源数量</returns>
public int DeleteAllExamResources()
{
var deleteCount = this.ExamResourceTable.Delete();
logger.Info($"已删除所有实验资源,共删除 {deleteCount} 个资源");
return deleteCount;
return extension.ToLowerInvariant() switch
{
".bit" => "application/octet-stream",
".sbit" => "application/octet-stream",
".bin" => "application/octet-stream",
".mcs" => "application/octet-stream",
".hex" => "text/plain",
_ => "application/octet-stream"
};
}
}