refactor: 重构数据库相关操作

This commit is contained in:
2025-08-10 20:13:44 +08:00
parent 079004c17d
commit b95a61c532
20 changed files with 2252 additions and 2065 deletions

View File

@@ -0,0 +1,98 @@
using DotNext;
using LinqToDB;
using LinqToDB.Data;
namespace Database;
/// <summary>
/// 应用程序数据连接类,用于与数据库交互
/// </summary>
public class AppDataConnection : DataConnection
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
static readonly string DATABASE_FILEPATH = $"{Global.DataPath}/Database.sqlite";
static readonly LinqToDB.DataOptions options =
new LinqToDB.DataOptions().UseSQLite($"Data Source={DATABASE_FILEPATH}");
/// <summary>
/// 用户表
/// </summary>
public ITable<User> UserTable => this.GetTable<User>();
/// <summary>
/// FPGA 板子表
/// </summary>
public ITable<Board> BoardTable => this.GetTable<Board>();
/// <summary>
/// 实验表
/// </summary>
public ITable<Exam> ExamTable => this.GetTable<Exam>();
/// <summary>
/// 资源表(统一管理实验资源、用户比特流等)
/// </summary>
public ITable<Resource> ResourceTable => this.GetTable<Resource>();
/// <summary>
/// 初始化应用程序数据连接
/// </summary>
public AppDataConnection() : base(options)
{
var filePath = Path.GetDirectoryName(DATABASE_FILEPATH);
if (!string.IsNullOrEmpty(filePath) && !Directory.Exists(filePath))
{
Directory.CreateDirectory(filePath);
}
if (!Path.Exists(DATABASE_FILEPATH))
{
logger.Info($"数据库文件不存在,正在创建新数据库: {DATABASE_FILEPATH}");
LinqToDB.DataProvider.SQLite.SQLiteTools.CreateDatabase(DATABASE_FILEPATH);
this.CreateAllTables();
var user = new User()
{
Name = "Admin",
EMail = "selfconfusion@gmail.com",
Password = "12345678",
Permission = Database.User.UserPermission.Admin,
};
this.Insert(user);
logger.Info("默认管理员用户已创建");
}
else
{
logger.Info($"数据库连接已建立: {DATABASE_FILEPATH}");
}
}
/// <summary>
/// 创建所有数据库表
/// </summary>
public void CreateAllTables()
{
logger.Info("正在创建数据库表...");
this.CreateTable<User>();
this.CreateTable<Board>();
this.CreateTable<Exam>();
this.CreateTable<Resource>();
logger.Info("数据库表创建完成");
}
/// <summary>
/// 删除所有数据库表
/// </summary>
public void DropAllTables()
{
logger.Warn("正在删除所有数据库表...");
this.DropTable<User>();
this.DropTable<Board>();
this.DropTable<Exam>();
this.DropTable<Resource>();
logger.Warn("所有数据库表已删除");
}
}

View File

@@ -0,0 +1,154 @@
using DotNext;
using LinqToDB;
using LinqToDB.Data;
namespace Database;
public class ExamManager
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private readonly AppDataConnection _db;
public ExamManager(AppDataConnection db)
{
this._db = db;
}
/// <summary>
/// 创建新实验
/// </summary>
/// <param name="id">实验ID</param>
/// <param name="name">实验名称</param>
/// <param name="description">实验描述</param>
/// <param name="tags">实验标签</param>
/// <param name="difficulty">实验难度</param>
/// <param name="isVisibleToUsers">普通用户是否可见</param>
/// <returns>创建的实验</returns>
public Result<Exam> CreateExam(string id, string name, string description, string[]? tags = null, int difficulty = 1, bool isVisibleToUsers = true)
{
try
{
// 检查实验ID是否已存在
var existingExam = _db.ExamTable.Where(e => e.ID == id).FirstOrDefault();
if (existingExam != null)
{
logger.Error($"实验ID已存在: {id}");
return new(new Exception($"实验ID已存在: {id}"));
}
var exam = new Exam
{
ID = id,
Name = name,
Description = description,
Difficulty = Math.Max(1, Math.Min(5, difficulty)),
IsVisibleToUsers = isVisibleToUsers,
CreatedTime = DateTime.Now,
UpdatedTime = DateTime.Now
};
if (tags != null)
{
exam.SetTagsList(tags);
}
_db.Insert(exam);
logger.Info($"新实验已创建: {id} ({name})");
return new(exam);
}
catch (Exception ex)
{
logger.Error($"创建实验时出错: {ex.Message}");
return new(ex);
}
}
/// <summary>
/// 更新实验信息
/// </summary>
/// <param name="id">实验ID</param>
/// <param name="name">实验名称</param>
/// <param name="description">实验描述</param>
/// <param name="tags">实验标签</param>
/// <param name="difficulty">实验难度</param>
/// <param name="isVisibleToUsers">普通用户是否可见</param>
/// <returns>更新的记录数</returns>
public Result<int> UpdateExam(string id, string? name = null, string? description = null, string[]? tags = null, int? difficulty = null, bool? isVisibleToUsers = null)
{
try
{
int result = 0;
if (name != null)
{
result += _db.ExamTable.Where(e => e.ID == id).Set(e => e.Name, name).Update();
}
if (description != null)
{
result += _db.ExamTable.Where(e => e.ID == id).Set(e => e.Description, description).Update();
}
if (tags != null)
{
var tagsString = string.Join(",", tags.Where(tag => !string.IsNullOrWhiteSpace(tag)).Select(tag => tag.Trim()));
result += _db.ExamTable.Where(e => e.ID == id).Set(e => e.Tags, tagsString).Update();
}
if (difficulty.HasValue)
{
result += _db.ExamTable.Where(e => e.ID == id).Set(e => e.Difficulty, Math.Max(1, Math.Min(5, difficulty.Value))).Update();
}
if (isVisibleToUsers.HasValue)
{
result += _db.ExamTable.Where(e => e.ID == id).Set(e => e.IsVisibleToUsers, isVisibleToUsers.Value).Update();
}
// 更新时间
_db.ExamTable.Where(e => e.ID == id).Set(e => e.UpdatedTime, DateTime.Now).Update();
logger.Info($"实验已更新: {id},更新记录数: {result}");
return new(result);
}
catch (Exception ex)
{
logger.Error($"更新实验时出错: {ex.Message}");
return new(ex);
}
}
/// <summary>
/// 获取所有实验信息
/// </summary>
/// <returns>所有实验的数组</returns>
public Exam[] GetAllExams()
{
var exams = _db.ExamTable.OrderBy(e => e.ID).ToArray();
logger.Debug($"获取所有实验,共 {exams.Length} 个");
return exams;
}
/// <summary>
/// 根据实验ID获取实验信息
/// </summary>
/// <param name="examId">实验ID</param>
/// <returns>包含实验信息的结果,如果未找到则返回空</returns>
public Result<Optional<Exam>> GetExamByID(string examId)
{
var exams = _db.ExamTable.Where(exam => exam.ID == examId).ToArray();
if (exams.Length > 1)
{
logger.Error($"数据库中存在多个相同ID的实验: {examId}");
return new(new Exception($"数据库中存在多个相同ID的实验: {examId}"));
}
if (exams.Length == 0)
{
logger.Info($"未找到ID对应的实验: {examId}");
return new(Optional<Exam>.None);
}
logger.Debug($"成功获取实验信息: {examId}");
return new(exams[0]);
}
}

View File

@@ -0,0 +1,357 @@
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 new(new Exception($"资源已存在: {resourceName}"));
}
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);
}
}
}

341
server/src/Database/Type.cs Normal file
View File

@@ -0,0 +1,341 @@
using DotNext;
using LinqToDB;
using LinqToDB.Mapping;
namespace Database;
/// <summary>
/// 用户类,表示用户信息
/// </summary>
public class User
{
/// <summary>
/// 用户的唯一标识符
/// </summary>
[PrimaryKey]
public Guid ID { get; set; } = Guid.NewGuid();
/// <summary>
/// 用户的名称
/// </summary>
[NotNull]
public required string Name { get; set; }
/// <summary>
/// 用户的电子邮箱
/// </summary>
[NotNull]
public required string EMail { get; set; }
/// <summary>
/// 用户的密码(应该进行哈希处理)
/// </summary>
[NotNull]
public required string Password { get; set; }
/// <summary>
/// 用户权限等级
/// </summary>
[NotNull]
public required UserPermission Permission { get; set; }
/// <summary>
/// 绑定的实验板ID如果未绑定则为空
/// </summary>
[Nullable]
public Guid BoardID { get; set; }
/// <summary>
/// 用户绑定板子的过期时间
/// </summary>
[Nullable]
public DateTime? BoardExpireTime { get; set; }
/// <summary>
/// 用户权限枚举
/// </summary>
public enum UserPermission
{
/// <summary>
/// 管理员权限,可以管理用户和实验板
/// </summary>
Admin,
/// <summary>
/// 普通用户权限,只能使用实验板
/// </summary>
Normal,
}
}
/// <summary>
/// FPGA 板子类,表示板子信息
/// </summary>
public class Board
{
/// <summary>
/// FPGA 板子的唯一标识符
/// </summary>
[PrimaryKey]
public Guid ID { get; set; } = Guid.NewGuid();
/// <summary>
/// FPGA 板子的名称
/// </summary>
[NotNull]
public required string BoardName { get; set; }
/// <summary>
/// FPGA 板子的IP地址
/// </summary>
[NotNull]
public required string IpAddr { get; set; }
/// <summary>
/// FPGA 板子的MAC地址
/// </summary>
[NotNull]
public required string MacAddr { get; set; }
/// <summary>
/// FPGA 板子的通信端口
/// </summary>
[NotNull]
public int Port { get; set; } = 1234;
/// <summary>
/// FPGA 板子的当前状态
/// </summary>
[NotNull]
public required BoardStatus Status { get; set; }
/// <summary>
/// 占用该板子的用户的唯一标识符
/// </summary>
[Nullable]
public Guid OccupiedUserID { get; set; }
/// <summary>
/// 占用该板子的用户的用户名
/// </summary>
[Nullable]
public string? OccupiedUserName { get; set; }
/// <summary>
/// FPGA 板子的固件版本号
/// </summary>
[NotNull]
public string FirmVersion { get; set; } = "1.0.0";
/// <summary>
/// FPGA 板子状态枚举
/// </summary>
public enum BoardStatus
{
/// <summary>
/// 未启用状态,无法被使用
/// </summary>
Disabled,
/// <summary>
/// 繁忙状态,正在被用户使用
/// </summary>
Busy,
/// <summary>
/// 可用状态,可以被分配给用户
/// </summary>
Available,
}
}
/// <summary>
/// 实验类,表示实验信息
/// </summary>
public class Exam
{
/// <summary>
/// 实验的唯一标识符
/// </summary>
[PrimaryKey]
public required string ID { get; set; }
/// <summary>
/// 实验名称
/// </summary>
[NotNull]
public required string Name { get; set; }
/// <summary>
/// 实验描述
/// </summary>
[NotNull]
public required string Description { get; set; }
/// <summary>
/// 实验创建时间
/// </summary>
[NotNull]
public DateTime CreatedTime { get; set; } = DateTime.Now;
/// <summary>
/// 实验最后更新时间
/// </summary>
[NotNull]
public DateTime UpdatedTime { get; set; } = DateTime.Now;
/// <summary>
/// 实验标签(以逗号分隔的字符串)
/// </summary>
[NotNull]
public string Tags { get; set; } = "";
/// <summary>
/// 实验难度1-51为最简单
/// </summary>
[NotNull]
public int Difficulty { get; set; } = 1;
/// <summary>
/// 普通用户是否可见
/// </summary>
[NotNull]
public bool IsVisibleToUsers { get; set; } = true;
/// <summary>
/// 获取标签列表
/// </summary>
/// <returns>标签数组</returns>
public string[] GetTagsList()
{
if (string.IsNullOrWhiteSpace(Tags))
return Array.Empty<string>();
return Tags.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Select(tag => tag.Trim())
.Where(tag => !string.IsNullOrEmpty(tag))
.ToArray();
}
/// <summary>
/// 设置标签列表
/// </summary>
/// <param name="tags">标签数组</param>
public void SetTagsList(string[] tags)
{
Tags = string.Join(",", tags.Where(tag => !string.IsNullOrWhiteSpace(tag)).Select(tag => tag.Trim()));
}
}
/// <summary>
/// 资源类,统一管理实验资源、用户比特流等各类资源
/// </summary>
public class Resource
{
/// <summary>
/// 资源的唯一标识符
/// </summary>
[PrimaryKey, Identity]
public int ID { get; set; }
/// <summary>
/// 上传资源的用户ID
/// </summary>
[NotNull]
public required Guid UserID { get; set; }
/// <summary>
/// 所属实验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>
[NotNull]
public required string ResourceName { get; set; }
/// <summary>
/// 资源路径(包含文件名和扩展名)
/// </summary>
[NotNull]
public required string Path { get; set; }
/// <summary>
/// 资源SHA256哈希值
/// </summary>
[NotNull]
public required string SHA256 { get; set; }
/// <summary>
/// 资源创建/上传时间
/// </summary>
[NotNull]
public DateTime UploadTime { get; set; } = DateTime.Now;
/// <summary>
/// 资源的MIME类型
/// </summary>
[NotNull]
public string MimeType { get; set; } = "application/octet-stream";
/// <summary>
/// 资源类型枚举
/// </summary>
public static class ResourceTypes
{
/// <summary>
/// 图片资源类型
/// </summary>
public const string Images = "images";
/// <summary>
/// Markdown文档资源类型
/// </summary>
public const string Markdown = "markdown";
/// <summary>
/// 比特流文件资源类型
/// </summary>
public const string Bitstream = "bitstream";
/// <summary>
/// 原理图资源类型
/// </summary>
public const string Diagram = "diagram";
/// <summary>
/// 项目文件资源类型
/// </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";
}
}

View File

@@ -0,0 +1,458 @@
using DotNext;
using LinqToDB;
using LinqToDB.Data;
namespace Database;
public class UserManager
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private readonly AppDataConnection _db;
public UserManager(AppDataConnection db)
{
this._db = db;
}
/// <summary>
/// 添加一个新的用户到数据库
/// </summary>
/// <param name="name">用户的名称</param>
/// <param name="email">用户的电子邮箱地址</param>
/// <param name="password">用户的密码</param>
/// <returns>插入的记录数</returns>
public int AddUser(string name, string email, string password)
{
var user = new User()
{
Name = name,
EMail = email,
Password = password,
Permission = Database.User.UserPermission.Normal,
};
var result = _db.Insert(user);
logger.Info($"新用户已添加: {name} ({email})");
return result;
}
/// <summary>
/// 根据用户名获取用户信息
/// </summary>
/// <param name="name">用户名</param>
/// <returns>包含用户信息的结果,如果未找到或出错则返回相应状态</returns>
public Result<Optional<User>> GetUserByName(string name)
{
var user = _db.UserTable.Where((user) => user.Name == name).ToArray();
if (user.Length > 1)
{
logger.Error($"数据库中存在多个同名用户: {name}");
return new(new Exception($"数据库中存在多个同名用户: {name}"));
}
if (user.Length == 0)
{
logger.Info($"未找到用户: {name}");
return new(Optional<User>.None);
}
logger.Debug($"成功获取用户信息: {name}");
return new(user[0]);
}
/// <summary>
/// 根据电子邮箱获取用户信息
/// </summary>
/// <param name="email">用户的电子邮箱地址</param>
/// <returns>包含用户信息的结果,如果未找到或出错则返回相应状态</returns>
public Result<Optional<User>> GetUserByEMail(string email)
{
var user = _db.UserTable.Where((user) => user.EMail == email).ToArray();
if (user.Length > 1)
{
logger.Error($"数据库中存在多个相同邮箱的用户: {email}");
return new(new Exception($"数据库中存在多个相同邮箱的用户: {email}"));
}
if (user.Length == 0)
{
logger.Info($"未找到邮箱对应的用户: {email}");
return new(Optional<User>.None);
}
logger.Debug($"成功获取用户信息: {email}");
return new(user[0]);
}
/// <summary>
/// 验证用户密码
/// </summary>
/// <param name="name">用户名</param>
/// <param name="password">用户密码</param>
/// <returns>如果密码正确返回用户信息,否则返回空</returns>
public Result<Optional<User>> CheckUserPassword(string name, string password)
{
var ret = GetUserByName(name);
if (!ret.IsSuccessful)
return new(ret.Error);
if (!ret.Value.HasValue)
return new(Optional<User>.None);
var user = ret.Value.Value;
if (user.Password == password)
{
logger.Info($"用户 {name} 密码验证成功");
return new(user);
}
else
{
logger.Warn($"用户 {name} 密码验证失败");
return new(Optional<User>.None);
}
}
/// <summary>
/// 绑定用户与实验板
/// </summary>
/// <param name="userId">用户的唯一标识符</param>
/// <param name="boardId">实验板的唯一标识符</param>
/// <param name="expireTime">绑定过期时间</param>
/// <returns>更新的记录数</returns>
public int BindUserToBoard(Guid userId, Guid boardId, DateTime expireTime)
{
// 获取用户信息
var user = _db.UserTable.Where(u => u.ID == userId).FirstOrDefault();
if (user == null)
{
logger.Error($"未找到用户: {userId}");
return 0;
}
// 更新用户的板子绑定信息
var userResult = _db.UserTable
.Where(u => u.ID == userId)
.Set(u => u.BoardID, boardId)
.Set(u => u.BoardExpireTime, expireTime)
.Update();
// 更新板子的用户绑定信息
var boardResult = _db.BoardTable
.Where(b => b.ID == boardId)
.Set(b => b.Status, Board.BoardStatus.Busy)
.Set(b => b.OccupiedUserID, userId)
.Set(b => b.OccupiedUserName, user.Name)
.Update();
logger.Info($"用户 {userId} ({user.Name}) 已绑定到实验板 {boardId},过期时间: {expireTime}");
return userResult + boardResult;
}
/// <summary>
/// 解除用户与实验板的绑定
/// </summary>
/// <param name="userId">用户的唯一标识符</param>
/// <returns>更新的记录数</returns>
public int UnbindUserFromBoard(Guid userId)
{
// 获取用户当前绑定的板子ID
var user = _db.UserTable.Where(u => u.ID == userId).FirstOrDefault();
Guid boardId = user?.BoardID ?? Guid.Empty;
// 清空用户的板子绑定信息
var userResult = _db.UserTable
.Where(u => u.ID == userId)
.Set(u => u.BoardID, Guid.Empty)
.Set(u => u.BoardExpireTime, (DateTime?)null)
.Update();
// 如果用户原本绑定了板子,则清空板子的用户绑定信息
int boardResult = 0;
if (boardId != Guid.Empty)
{
boardResult = _db.BoardTable
.Where(b => b.ID == boardId)
.Set(b => b.Status, Board.BoardStatus.Available)
.Set(b => b.OccupiedUserID, Guid.Empty)
.Set(b => b.OccupiedUserName, (string?)null)
.Update();
logger.Info($"实验板 {boardId} 状态已设置为空闲,用户绑定信息已清空");
}
logger.Info($"用户 {userId} 已解除实验板绑定");
return userResult + boardResult;
}
/// <summary>
/// 自动分配一个未被占用的IP地址
/// </summary>
/// <returns>分配的IP地址字符串</returns>
public string AllocateIpAddr()
{
var usedIps = _db.BoardTable.Select(b => b.IpAddr).ToArray();
for (int i = 1; i <= 254; i++)
{
string ip = $"169.254.109.{i}";
if (!usedIps.Contains(ip))
return ip;
}
throw new Exception("没有可用的IP地址");
}
/// <summary>
/// 自动分配一个未被占用的MAC地址
/// </summary>
/// <returns>分配的MAC地址字符串</returns>
public string AllocateMacAddr()
{
var usedMacs = _db.BoardTable.Select(b => b.MacAddr).ToArray();
// 以 02-00-00-xx-xx-xx 格式分配02 表示本地管理地址
for (int i = 1; i <= 0xFFFFFF; i++)
{
string mac = $"02-00-00-{(i >> 16) & 0xFF:X2}-{(i >> 8) & 0xFF:X2}-{i & 0xFF:X2}";
if (!usedMacs.Contains(mac))
return mac;
}
throw new Exception("没有可用的MAC地址");
}
/// <summary>
/// 添加一块新的 FPGA 板子到数据库
/// </summary>
/// <param name="name">FPGA 板子的名称</param>
/// <returns>插入的记录数</returns>
public Guid AddBoard(string name)
{
if (string.IsNullOrWhiteSpace(name) || name.Contains('\'') || name.Contains(';'))
{
logger.Error("实验板名称非法,包含不允许的字符");
throw new ArgumentException("实验板名称非法");
}
var board = new Board()
{
BoardName = name,
IpAddr = AllocateIpAddr(),
MacAddr = AllocateMacAddr(),
Status = Database.Board.BoardStatus.Disabled,
};
var result = _db.Insert(board);
logger.Info($"新实验板已添加: {name} ({board.IpAddr}:{board.MacAddr})");
return board.ID;
}
/// <summary>
/// 根据名称删除实验板
/// </summary>
/// <param name="name">实验板的名称</param>
/// <returns>删除的记录数</returns>
public int DeleteBoardByName(string name)
{
// 先获取要删除的板子信息
var board = _db.BoardTable.Where(b => b.BoardName == name).FirstOrDefault();
if (board == null)
{
logger.Warn($"未找到名称为 {name} 的实验板");
return 0;
}
// 如果板子被占用,先解除绑定
if (board.OccupiedUserID != Guid.Empty)
{
_db.UserTable
.Where(u => u.ID == board.OccupiedUserID)
.Set(u => u.BoardID, Guid.Empty)
.Set(u => u.BoardExpireTime, (DateTime?)null)
.Update();
logger.Info($"已解除用户 {board.OccupiedUserID} 与实验板 {name} 的绑定");
}
var result = _db.BoardTable.Where(b => b.BoardName == name).Delete();
logger.Info($"实验板已删除: {name},删除记录数: {result}");
return result;
}
/// <summary>
/// 根据ID删除实验板
/// </summary>
/// <param name="id">实验板的唯一标识符</param>
/// <returns>删除的记录数</returns>
public int DeleteBoardByID(Guid id)
{
// 先获取要删除的板子信息
var board = _db.BoardTable.Where(b => b.ID == id).FirstOrDefault();
if (board == null)
{
logger.Warn($"未找到ID为 {id} 的实验板");
return 0;
}
// 如果板子被占用,先解除绑定
if (board.OccupiedUserID != Guid.Empty)
{
_db.UserTable
.Where(u => u.ID == board.OccupiedUserID)
.Set(u => u.BoardID, Guid.Empty)
.Set(u => u.BoardExpireTime, (DateTime?)null)
.Update();
logger.Info($"已解除用户 {board.OccupiedUserID} 与实验板 {id} 的绑定");
}
var result = _db.BoardTable.Where(b => b.ID == id).Delete();
logger.Info($"实验板已删除: {id},删除记录数: {result}");
return result;
}
/// <summary>
/// 根据实验板ID获取实验板信息
/// </summary>
/// <param name="id">实验板的唯一标识符</param>
/// <returns>包含实验板信息的结果,如果未找到则返回空</returns>
public Result<Optional<Board>> GetBoardByID(Guid id)
{
var boards = _db.BoardTable.Where(board => board.ID == id).ToArray();
if (boards.Length > 1)
{
logger.Error($"数据库中存在多个相同ID的实验板: {id}");
return new(new Exception($"数据库中存在多个相同ID的实验板: {id}"));
}
if (boards.Length == 0)
{
logger.Info($"未找到ID对应的实验板: {id}");
return new(Optional<Board>.None);
}
logger.Debug($"成功获取实验板信息: {id}");
return new(boards[0]);
}
/// <summary>
/// 根据用户名获取实验板信息
/// </summary>
/// <param name="userName">用户名</param>
/// <returns>包含实验板信息的结果,如果未找到则返回空</returns>
public Result<Optional<Board>> GetBoardByUserName(string userName)
{
var boards = _db.BoardTable.Where(board => board.OccupiedUserName == userName).ToArray();
if (boards.Length > 1)
{
logger.Error($"数据库中存在多个相同用户名的实验板: {userName}");
return new(new Exception($"数据库中存在多个相同用户名的实验板: {userName}"));
}
if (boards.Length == 0)
{
logger.Info($"未找到用户名对应的实验板: {userName}");
return new(Optional<Board>.None);
}
logger.Debug($"成功获取实验板信息: {userName}");
return new(boards[0]);
}
/// <summary>
/// 获取所有实验板信息
/// </summary>
/// <returns>所有实验板的数组</returns>
public Board[] GetAllBoard()
{
var boards = _db.BoardTable.ToArray();
logger.Debug($"获取所有实验板,共 {boards.Length} 块");
return boards;
}
/// <summary>
/// 获取一块可用的实验板并将其状态设置为繁忙
/// </summary>
/// <param name="userId">要分配板子的用户ID</param>
/// <param name="expireTime">绑定过期时间</param>
/// <returns>可用的实验板,如果没有可用的板子则返回空</returns>
public Optional<Board> GetAvailableBoard(Guid userId, DateTime expireTime)
{
var boards = _db.BoardTable.Where(
(board) => board.Status == Database.Board.BoardStatus.Available
).ToArray();
if (boards.Length == 0)
{
logger.Warn("没有可用的实验板");
return new(null);
}
else
{
var board = boards[0];
var user = _db.UserTable.Where(u => u.ID == userId).FirstOrDefault();
if (user == null)
{
logger.Error($"未找到用户: {userId}");
return new(null);
}
// 更新板子状态和用户绑定信息
_db.BoardTable
.Where(target => target.ID == board.ID)
.Set(target => target.Status, Board.BoardStatus.Busy)
.Set(target => target.OccupiedUserID, userId)
.Set(target => target.OccupiedUserName, user.Name)
.Update();
// 更新用户的板子绑定信息
_db.UserTable
.Where(u => u.ID == userId)
.Set(u => u.BoardID, board.ID)
.Set(u => u.BoardExpireTime, expireTime)
.Update();
board.Status = Database.Board.BoardStatus.Busy;
board.OccupiedUserID = userId;
board.OccupiedUserName = user.Name;
logger.Info($"实验板 {board.BoardName} ({board.ID}) 已分配给用户 {user.Name} ({userId}),过期时间: {expireTime}");
return new(board);
}
}
/// <summary>
/// [TODO:description]
/// </summary>
/// <param name="boardId">[TODO:parameter]</param>
/// <param name="newName">[TODO:parameter]</param>
/// <returns>[TODO:return]</returns>
public int UpdateBoardName(Guid boardId, string newName)
{
if (string.IsNullOrWhiteSpace(newName) || newName.Contains('\'') || newName.Contains(';'))
{
logger.Error("实验板名称非法,包含不允许的字符");
return 0;
}
var result = _db.BoardTable
.Where(b => b.ID == boardId)
.Set(b => b.BoardName, newName)
.Update();
logger.Info($"实验板名称已更新: {boardId} -> {newName}");
return result;
}
/// <summary>
/// [TODO:description]
/// </summary>
/// <param name="boardId">[TODO:parameter]</param>
/// <param name="newStatus">[TODO:parameter]</param>
/// <returns>[TODO:return]</returns>
public int UpdateBoardStatus(Guid boardId, Board.BoardStatus newStatus)
{
var result = _db.BoardTable
.Where(b => b.ID == boardId)
.Set(b => b.Status, newStatus)
.Update();
logger.Info($"TODO");
return result;
}
}