358 lines
13 KiB
C#
358 lines
13 KiB
C#
using DotNext;
|
||
using LinqToDB;
|
||
using LinqToDB.Data;
|
||
using System.Security.Cryptography;
|
||
|
||
namespace Database;
|
||
|
||
public class ResourceManager
|
||
{
|
||
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
||
|
||
private readonly AppDataConnection _db;
|
||
|
||
public ResourceManager(AppDataConnection db)
|
||
{
|
||
this._db = db;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据文件扩展名获取MIME类型
|
||
/// </summary>
|
||
/// <param name="extension">文件扩展名</param>
|
||
/// <param name="fileName">文件名(可选,用于特殊文件判断)</param>
|
||
/// <returns>MIME类型</returns>
|
||
private string GetMimeTypeFromExtension(string extension, string fileName = "")
|
||
{
|
||
// 特殊文件名处理
|
||
if (!string.IsNullOrEmpty(fileName) && fileName.Equals("diagram.json", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
return "application/json";
|
||
}
|
||
|
||
return extension.ToLowerInvariant() switch
|
||
{
|
||
".png" => "image/png",
|
||
".jpg" or ".jpeg" => "image/jpeg",
|
||
".gif" => "image/gif",
|
||
".bmp" => "image/bmp",
|
||
".svg" => "image/svg+xml",
|
||
".bit" => "application/octet-stream",
|
||
".sbit" => "application/octet-stream",
|
||
".bin" => "application/octet-stream",
|
||
".mcs" => "application/octet-stream",
|
||
".hex" => "text/plain",
|
||
".json" => "application/json",
|
||
".zip" => "application/zip",
|
||
".md" => "text/markdown",
|
||
_ => "application/octet-stream"
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将二进制数据写入指定路径
|
||
/// </summary>
|
||
/// <param name="path">目标文件路径</param>
|
||
/// <param name="data">要写入的二进制数据</param>
|
||
/// <returns>写入是否成功</returns>
|
||
public Result<bool> WriteBytesToPath(string path, byte[] data)
|
||
{
|
||
try
|
||
{
|
||
var filePath = Path.Combine(Global.DataPath, path);
|
||
var directory = Path.GetDirectoryName(filePath);
|
||
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
|
||
{
|
||
Directory.CreateDirectory(directory);
|
||
}
|
||
File.WriteAllBytes(filePath, data);
|
||
logger.Info($"成功写入文件: {filePath},大小: {data.Length} bytes");
|
||
return new(true);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error($"写入文件时出错: {ex.Message}");
|
||
return new(ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从指定路径读取二进制数据
|
||
/// </summary>
|
||
/// <param name="path">要读取的文件路径</param>
|
||
/// <returns>读取到的二进制数据</returns>
|
||
public Result<byte[]> ReadBytesFromPath(string path)
|
||
{
|
||
try
|
||
{
|
||
var filePath = Path.Combine(Global.DataPath, path);
|
||
if (!File.Exists(filePath))
|
||
{
|
||
logger.Error($"文件不存在: {filePath}");
|
||
return new(new Exception($"文件不存在: {filePath}"));
|
||
}
|
||
var data = File.ReadAllBytes(filePath);
|
||
logger.Info($"成功读取文件: {filePath},大小: {data.Length} bytes");
|
||
return new(data);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error($"读取文件时出错: {ex.Message}");
|
||
return new(ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 添加资源
|
||
/// </summary>
|
||
/// <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<Resource> AddResource(
|
||
Guid userId, string resourceType, string resourcePurpose,
|
||
string resourceName, byte[] data, string? examId = null, string? mimeType = null)
|
||
{
|
||
try
|
||
{
|
||
// 验证用户是否存在
|
||
var user = _db.UserTable.Where(u => u.ID == userId).FirstOrDefault();
|
||
if (user == null)
|
||
{
|
||
logger.Error($"用户不存在: {userId}");
|
||
return new(new Exception($"用户不存在: {userId}"));
|
||
}
|
||
|
||
// 如果指定了实验ID,验证实验是否存在
|
||
if (!string.IsNullOrEmpty(examId))
|
||
{
|
||
var exam = _db.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类型,根据文件扩展名自动确定
|
||
if (string.IsNullOrEmpty(mimeType))
|
||
{
|
||
var extension = Path.GetExtension(resourceName).ToLowerInvariant();
|
||
mimeType = GetMimeTypeFromExtension(extension, resourceName);
|
||
}
|
||
|
||
// 计算数据的SHA256
|
||
var sha256 = SHA256.HashData(data).ToString();
|
||
if (string.IsNullOrEmpty(sha256))
|
||
{
|
||
logger.Error($"SHA256计算失败");
|
||
return new(new Exception("SHA256计算失败"));
|
||
}
|
||
|
||
var duplicateResource = _db.ResourceTable.Where(r => r.SHA256 == sha256).FirstOrDefault();
|
||
if (duplicateResource != null && duplicateResource.ResourceName == resourceName)
|
||
{
|
||
logger.Info($"资源已存在: {resourceName}");
|
||
return duplicateResource;
|
||
}
|
||
|
||
var nowTime = DateTime.Now;
|
||
var resource = new Resource
|
||
{
|
||
UserID = userId,
|
||
ExamID = examId,
|
||
ResourceType = resourceType,
|
||
ResourcePurpose = resourcePurpose,
|
||
ResourceName = resourceName,
|
||
Path = duplicateResource == null ?
|
||
Path.Combine(resourceType, nowTime.ToString("yyyyMMddHH"), resourceName) :
|
||
duplicateResource.Path,
|
||
SHA256 = sha256,
|
||
MimeType = mimeType,
|
||
UploadTime = nowTime
|
||
};
|
||
|
||
var insertedId = _db.InsertWithIdentity(resource);
|
||
resource.ID = Convert.ToInt32(insertedId);
|
||
|
||
var writeRet = WriteBytesToPath(resource.Path, data);
|
||
if (writeRet.IsSuccessful && writeRet.Value)
|
||
{
|
||
logger.Info($"新资源已添加: {userId}/{resourceType}/{resourcePurpose}/{resourceName} ({data.Length} bytes)" +
|
||
(examId != null ? $" [实验: {examId}]" : "") + $" [ID: {resource.ID}]");
|
||
return new(resource);
|
||
}
|
||
else
|
||
{
|
||
_db.ResourceTable.Where(r => r.ID == resource.ID).Delete();
|
||
|
||
logger.Error($"写入资源文件时出错: {writeRet.Error}");
|
||
return new(new Exception(writeRet.Error?.ToString() ?? $"写入失败"));
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error($"添加资源时出错: {ex.Message}");
|
||
return new(ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取资源信息列表(返回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)[]> GetResourceList(string resourceType, string? examId = null, string? resourcePurpose = null, Guid? userId = null)
|
||
{
|
||
try
|
||
{
|
||
var query = _db.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($"获取资源列表: {resourceType}" +
|
||
(examId != null ? $"/{examId}" : "") +
|
||
(resourcePurpose != null ? $"/{resourcePurpose}" : "") +
|
||
(userId != null ? $"/{userId}" : "") +
|
||
$",共 {result.Length} 个资源");
|
||
return new(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
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 = _db.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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据资源ID获取资源
|
||
/// </summary>
|
||
/// <param name="resourceId">资源ID</param>
|
||
/// <returns>资源数据</returns>
|
||
public Result<Optional<Resource>> GetResourceById(int resourceId)
|
||
{
|
||
try
|
||
{
|
||
var resource = _db.ResourceTable.Where(r => r.ID == resourceId).FirstOrDefault();
|
||
|
||
if (resource == null)
|
||
{
|
||
logger.Info($"未找到资源: {resourceId}");
|
||
return new(Optional<Resource>.None);
|
||
}
|
||
|
||
logger.Info($"成功获取资源: {resourceId} ({resource.ResourceName})");
|
||
return new(resource);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error($"获取资源时出错: {ex.Message}");
|
||
return new(ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除资源
|
||
/// </summary>
|
||
/// <param name="resourceId">资源ID</param>
|
||
/// <returns>删除的记录数</returns>
|
||
public Result<int> DeleteResource(int resourceId)
|
||
{
|
||
try
|
||
{
|
||
var result = _db.ResourceTable.Where(r => r.ID == resourceId).Delete();
|
||
logger.Info($"资源已删除: {resourceId},删除记录数: {result}");
|
||
return new(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.Error($"删除资源时出错: {ex.Message}");
|
||
return new(ex);
|
||
}
|
||
}
|
||
|
||
}
|