555 lines
17 KiB
C#
555 lines
17 KiB
C#
using DotNext;
|
||
using LinqToDB;
|
||
using LinqToDB.Data;
|
||
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 板子的通信端口
|
||
/// </summary>
|
||
[NotNull]
|
||
public required int Port { get; set; }
|
||
|
||
/// <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>
|
||
Busy,
|
||
|
||
/// <summary>
|
||
/// 可用状态,可以被分配给用户
|
||
/// </summary>
|
||
Available,
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 应用程序数据连接类,用于与数据库交互
|
||
/// </summary>
|
||
public class AppDataConnection : DataConnection
|
||
{
|
||
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
||
|
||
static readonly string DATABASE_FILEPATH = $"{Environment.CurrentDirectory}/Database.sqlite";
|
||
|
||
static readonly LinqToDB.DataOptions options =
|
||
new LinqToDB.DataOptions().UseSQLite($"Data Source={DATABASE_FILEPATH}");
|
||
|
||
/// <summary>
|
||
/// 初始化应用程序数据连接
|
||
/// </summary>
|
||
public AppDataConnection() : base(options)
|
||
{
|
||
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>();
|
||
logger.Info("数据库表创建完成");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除所有数据库表
|
||
/// </summary>
|
||
public void DropAllTables()
|
||
{
|
||
logger.Warn("正在删除所有数据库表...");
|
||
this.DropTable<User>();
|
||
this.DropTable<Board>();
|
||
logger.Warn("所有数据库表已删除");
|
||
}
|
||
|
||
/// <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 = this.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 = this.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 = this.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 = this.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 = this.UserTable.Where(u => u.ID == userId).FirstOrDefault();
|
||
if (user == null)
|
||
{
|
||
logger.Error($"未找到用户: {userId}");
|
||
return 0;
|
||
}
|
||
|
||
// 更新用户的板子绑定信息
|
||
var userResult = this.UserTable
|
||
.Where(u => u.ID == userId)
|
||
.Set(u => u.BoardID, boardId)
|
||
.Set(u => u.BoardExpireTime, expireTime)
|
||
.Update();
|
||
|
||
// 更新板子的用户绑定信息
|
||
var boardResult = this.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 = this.UserTable.Where(u => u.ID == userId).FirstOrDefault();
|
||
Guid boardId = user?.BoardID ?? Guid.Empty;
|
||
|
||
// 清空用户的板子绑定信息
|
||
var userResult = this.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 = this.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>
|
||
/// 添加一块新的 FPGA 板子到数据库
|
||
/// </summary>
|
||
/// <param name="name">FPGA 板子的名称</param>
|
||
/// <param name="ipAddr">FPGA 板子的IP地址</param>
|
||
/// <param name="port">FPGA 板子的通信端口</param>
|
||
/// <returns>插入的记录数</returns>
|
||
public int AddBoard(string name, string ipAddr, int port)
|
||
{
|
||
var board = new Board()
|
||
{
|
||
BoardName = name,
|
||
IpAddr = ipAddr,
|
||
Port = port,
|
||
Status = Database.Board.BoardStatus.Available,
|
||
};
|
||
var result = this.Insert(board);
|
||
logger.Info($"新实验板已添加: {name} ({ipAddr}:{port})");
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据名称删除实验板
|
||
/// </summary>
|
||
/// <param name="name">实验板的名称</param>
|
||
/// <returns>删除的记录数</returns>
|
||
public int DeleteBoardByName(string name)
|
||
{
|
||
// 先获取要删除的板子信息
|
||
var board = this.BoardTable.Where(b => b.BoardName == name).FirstOrDefault();
|
||
if (board == null)
|
||
{
|
||
logger.Warn($"未找到名称为 {name} 的实验板");
|
||
return 0;
|
||
}
|
||
|
||
// 如果板子被占用,先解除绑定
|
||
if (board.OccupiedUserID != Guid.Empty)
|
||
{
|
||
this.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 = this.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 = this.BoardTable.Where(b => b.ID == id).FirstOrDefault();
|
||
if (board == null)
|
||
{
|
||
logger.Warn($"未找到ID为 {id} 的实验板");
|
||
return 0;
|
||
}
|
||
|
||
// 如果板子被占用,先解除绑定
|
||
if (board.OccupiedUserID != Guid.Empty)
|
||
{
|
||
this.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 = this.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 = this.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>
|
||
/// <returns>所有实验板的数组</returns>
|
||
public Board[] GetAllBoard()
|
||
{
|
||
var boards = this.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 = this.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 = this.UserTable.Where(u => u.ID == userId).FirstOrDefault();
|
||
|
||
if (user == null)
|
||
{
|
||
logger.Error($"未找到用户: {userId}");
|
||
return new(null);
|
||
}
|
||
|
||
// 更新板子状态和用户绑定信息
|
||
this.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();
|
||
|
||
// 更新用户的板子绑定信息
|
||
this.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>
|
||
/// 用户表
|
||
/// </summary>
|
||
public ITable<User> UserTable => this.GetTable<User>();
|
||
|
||
/// <summary>
|
||
/// FPGA 板子表
|
||
/// </summary>
|
||
public ITable<Board> BoardTable => this.GetTable<Board>();
|
||
}
|