feat: 统一资源管理
This commit is contained in:
@@ -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"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user