refactor: 重构数据库相关操作
This commit is contained in:
		@@ -62,7 +62,7 @@ try
 | 
			
		||||
                IssuerSigningKey = new SymmetricSecurityKey(
 | 
			
		||||
                    Encoding.UTF8.GetBytes("my secret key 1234567890my secret key 1234567890")),
 | 
			
		||||
            };
 | 
			
		||||
            options.Authority = $"http://{Global.localhost}:5000";
 | 
			
		||||
            options.Authority = $"http://{Global.LocalHost}:5000";
 | 
			
		||||
            options.RequireHttpsMetadata = false;
 | 
			
		||||
        });
 | 
			
		||||
    // Add JWT Token Authorization Policy
 | 
			
		||||
@@ -152,6 +152,11 @@ try
 | 
			
		||||
    builder.Services.AddSingleton<ProgressTrackerService>();
 | 
			
		||||
    builder.Services.AddHostedService(provider => provider.GetRequiredService<ProgressTrackerService>());
 | 
			
		||||
 | 
			
		||||
    // 添加数据库资源管理器服务
 | 
			
		||||
    builder.Services.AddSingleton<Database.AppDataConnection>();
 | 
			
		||||
    builder.Services.AddSingleton<Database.UserManager>();
 | 
			
		||||
    builder.Services.AddSingleton<Database.ResourceManager>();
 | 
			
		||||
 | 
			
		||||
    // Application Settings
 | 
			
		||||
    var app = builder.Build();
 | 
			
		||||
    // Configure the HTTP request pipeline.
 | 
			
		||||
@@ -209,7 +214,7 @@ try
 | 
			
		||||
        settings.PostProcess = (document, httpRequest) =>
 | 
			
		||||
        {
 | 
			
		||||
            document.Servers.Clear();
 | 
			
		||||
            document.Servers.Add(new NSwag.OpenApiServer { Url = $"http://{Global.localhost}:5000" });
 | 
			
		||||
            document.Servers.Add(new NSwag.OpenApiServer { Url = $"http://{Global.LocalHost}:5000" });
 | 
			
		||||
        };
 | 
			
		||||
    });
 | 
			
		||||
    app.UseSwaggerUi();
 | 
			
		||||
@@ -232,7 +237,7 @@ try
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var document = await OpenApiDocument.FromUrlAsync($"http://{Global.localhost}:5000/swagger/v1/swagger.json");
 | 
			
		||||
            var document = await OpenApiDocument.FromUrlAsync($"http://{Global.LocalHost}:5000/swagger/v1/swagger.json");
 | 
			
		||||
 | 
			
		||||
            var settings = new TypeScriptClientGeneratorSettings
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,8 @@ using System.Net.Sockets;
 | 
			
		||||
public static class Global
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    public static readonly string localhost = "127.0.0.1";
 | 
			
		||||
    public static readonly string LocalHost = "127.0.0.1";
 | 
			
		||||
    public static readonly string DataPath = Path.Combine(Environment.CurrentDirectory, "data");
 | 
			
		||||
 | 
			
		||||
    public static string GetLocalIPAddress()
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -17,38 +17,15 @@ namespace server.Controllers;
 | 
			
		||||
public class DataController : ControllerBase
 | 
			
		||||
{
 | 
			
		||||
    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
    private readonly Database.UserManager _userManager;
 | 
			
		||||
 | 
			
		||||
    // 固定的实验板IP,端口,MAC地址
 | 
			
		||||
    private const string BOARD_IP = "169.254.109.0";
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// [TODO:description]
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class UserInfo
 | 
			
		||||
    public DataController(Database.UserManager userManager)
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 用户的唯一标识符
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public Guid ID { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 用户的名称
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public required string Name { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 用户的电子邮箱
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public required string EMail { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 用户关联的板卡ID
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public Guid BoardID { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 用户绑定板子的过期时间
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public DateTime? BoardExpireTime { get; set; }
 | 
			
		||||
        _userManager = userManager;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
@@ -112,8 +89,7 @@ public class DataController : ControllerBase
 | 
			
		||||
    public IActionResult Login(string name, string password)
 | 
			
		||||
    {
 | 
			
		||||
        // 验证用户密码
 | 
			
		||||
        using var db = new Database.AppDataConnection();
 | 
			
		||||
        var ret = db.CheckUserPassword(name, password);
 | 
			
		||||
        var ret = _userManager.CheckUserPassword(name, password);
 | 
			
		||||
        if (!ret.IsSuccessful) return StatusCode(StatusCodes.Status500InternalServerError, "数据库操作失败");
 | 
			
		||||
        if (!ret.Value.HasValue) return BadRequest("用户名或密码错误");
 | 
			
		||||
        var user = ret.Value.Value;
 | 
			
		||||
@@ -188,8 +164,7 @@ public class DataController : ControllerBase
 | 
			
		||||
            return Unauthorized("未找到用户名信息");
 | 
			
		||||
 | 
			
		||||
        // Get User Info
 | 
			
		||||
        using var db = new Database.AppDataConnection();
 | 
			
		||||
        var ret = db.GetUserByName(userName);
 | 
			
		||||
        var ret = _userManager.GetUserByName(userName);
 | 
			
		||||
        if (!ret.IsSuccessful)
 | 
			
		||||
            return StatusCode(StatusCodes.Status500InternalServerError, "数据库操作失败");
 | 
			
		||||
 | 
			
		||||
@@ -236,8 +211,7 @@ public class DataController : ControllerBase
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            using var db = new Database.AppDataConnection();
 | 
			
		||||
            var ret = db.AddUser(name, email, password);
 | 
			
		||||
            var ret = _userManager.AddUser(name, email, password);
 | 
			
		||||
            return Ok(ret);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
@@ -265,15 +239,14 @@ public class DataController : ControllerBase
 | 
			
		||||
            if (string.IsNullOrEmpty(userName))
 | 
			
		||||
                return Unauthorized("未找到用户名信息");
 | 
			
		||||
 | 
			
		||||
            using var db = new Database.AppDataConnection();
 | 
			
		||||
            var userRet = db.GetUserByName(userName);
 | 
			
		||||
            var userRet = _userManager.GetUserByName(userName);
 | 
			
		||||
            if (!userRet.IsSuccessful || !userRet.Value.HasValue)
 | 
			
		||||
                return BadRequest("用户不存在");
 | 
			
		||||
 | 
			
		||||
            var user = userRet.Value.Value;
 | 
			
		||||
            var expireTime = DateTime.UtcNow.AddHours(durationHours);
 | 
			
		||||
 | 
			
		||||
            var boardOpt = db.GetAvailableBoard(user.ID, expireTime);
 | 
			
		||||
            var boardOpt = _userManager.GetAvailableBoard(user.ID, expireTime);
 | 
			
		||||
            if (!boardOpt.HasValue)
 | 
			
		||||
                return NotFound("没有可用的实验板");
 | 
			
		||||
 | 
			
		||||
@@ -309,13 +282,12 @@ public class DataController : ControllerBase
 | 
			
		||||
            if (string.IsNullOrEmpty(userName))
 | 
			
		||||
                return Unauthorized("未找到用户名信息");
 | 
			
		||||
 | 
			
		||||
            using var db = new Database.AppDataConnection();
 | 
			
		||||
            var userRet = db.GetUserByName(userName);
 | 
			
		||||
            var userRet = _userManager.GetUserByName(userName);
 | 
			
		||||
            if (!userRet.IsSuccessful || !userRet.Value.HasValue)
 | 
			
		||||
                return BadRequest("用户不存在");
 | 
			
		||||
 | 
			
		||||
            var user = userRet.Value.Value;
 | 
			
		||||
            var result = db.UnbindUserFromBoard(user.ID);
 | 
			
		||||
            var result = _userManager.UnbindUserFromBoard(user.ID);
 | 
			
		||||
            return Ok(result > 0);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
@@ -338,8 +310,7 @@ public class DataController : ControllerBase
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            using var db = new Database.AppDataConnection();
 | 
			
		||||
            var ret = db.GetBoardByID(id);
 | 
			
		||||
            var ret = _userManager.GetBoardByID(id);
 | 
			
		||||
            if (!ret.IsSuccessful)
 | 
			
		||||
                return StatusCode(StatusCodes.Status500InternalServerError, "数据库操作失败");
 | 
			
		||||
            if (!ret.Value.HasValue)
 | 
			
		||||
@@ -375,8 +346,7 @@ public class DataController : ControllerBase
 | 
			
		||||
            return BadRequest("板子名称不能为空");
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            using var db = new Database.AppDataConnection();
 | 
			
		||||
            var ret = db.AddBoard(name);
 | 
			
		||||
            var ret = _userManager.AddBoard(name);
 | 
			
		||||
            return Ok(ret);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
@@ -402,8 +372,7 @@ public class DataController : ControllerBase
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            using var db = new Database.AppDataConnection();
 | 
			
		||||
            var ret = db.DeleteBoardByID(id);
 | 
			
		||||
            var ret = _userManager.DeleteBoardByID(id);
 | 
			
		||||
            return Ok(ret);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
@@ -425,8 +394,7 @@ public class DataController : ControllerBase
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            using var db = new Database.AppDataConnection();
 | 
			
		||||
            var boards = db.GetAllBoard();
 | 
			
		||||
            var boards = _userManager.GetAllBoard();
 | 
			
		||||
            return Ok(boards);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
@@ -453,8 +421,7 @@ public class DataController : ControllerBase
 | 
			
		||||
            return BadRequest("新名称不能为空");
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            using var db = new Database.AppDataConnection();
 | 
			
		||||
            var result = db.UpdateBoardName(boardId, newName);
 | 
			
		||||
            var result = _userManager.UpdateBoardName(boardId, newName);
 | 
			
		||||
            return Ok(result);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
@@ -479,8 +446,7 @@ public class DataController : ControllerBase
 | 
			
		||||
            return BadRequest("板子Guid不能为空");
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            using var db = new Database.AppDataConnection();
 | 
			
		||||
            var result = db.UpdateBoardStatus(boardId, newStatus);
 | 
			
		||||
            var result = _userManager.UpdateBoardStatus(boardId, newStatus);
 | 
			
		||||
            return Ok(result);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
@@ -489,4 +455,36 @@ public class DataController : ControllerBase
 | 
			
		||||
            return StatusCode(StatusCodes.Status500InternalServerError, "更新失败,请稍后重试");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// [TODO:description]
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class UserInfo
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 用户的唯一标识符
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public Guid ID { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 用户的名称
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public required string Name { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 用户的电子邮箱
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public required string EMail { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 用户关联的板卡ID
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public Guid BoardID { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 用户绑定板子的过期时间
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public DateTime? BoardExpireTime { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,77 +15,11 @@ public class DebuggerController : ControllerBase
 | 
			
		||||
{
 | 
			
		||||
    private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 表示单个信号通道的配置信息
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class ChannelConfig
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 通道名称
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public string name;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 通道显示颜色(如前端波形显示用)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public string color;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 通道信号线宽度(位数)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public UInt32 wireWidth;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 信号线在父端口中的起始索引(bit)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public UInt32 wireStartIndex;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 父端口编号
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public UInt32 parentPort;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 捕获模式(如上升沿、下降沿等)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public CaptureMode mode;
 | 
			
		||||
    }
 | 
			
		||||
    private readonly Database.UserManager _userManager;
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 调试器整体配置信息
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class DebuggerConfig
 | 
			
		||||
    public DebuggerController(Database.UserManager userManager)
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 时钟频率
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public UInt32 clkFreq;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 总端口数量
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public UInt32 totalPortNum;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 捕获深度(采样点数)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public UInt32 captureDepth;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 触发器数量
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public UInt32 triggerNum;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 所有信号通道的配置信息
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public ChannelConfig[] channelConfigs;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 单个通道的捕获数据
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class ChannelCaptureData
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 通道名称
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public string name;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 通道捕获到的数据(Base64编码的UInt32数组)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public string data;
 | 
			
		||||
        this._userManager = userManager;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
@@ -99,8 +33,7 @@ public class DebuggerController : ControllerBase
 | 
			
		||||
            if (string.IsNullOrEmpty(userName))
 | 
			
		||||
                return null;
 | 
			
		||||
 | 
			
		||||
            using var db = new Database.AppDataConnection();
 | 
			
		||||
            var userRet = db.GetUserByName(userName);
 | 
			
		||||
            var userRet = _userManager.GetUserByName(userName);
 | 
			
		||||
            if (!userRet.IsSuccessful || !userRet.Value.HasValue)
 | 
			
		||||
                return null;
 | 
			
		||||
 | 
			
		||||
@@ -108,7 +41,7 @@ public class DebuggerController : ControllerBase
 | 
			
		||||
            if (user.BoardID == Guid.Empty)
 | 
			
		||||
                return null;
 | 
			
		||||
 | 
			
		||||
            var boardRet = db.GetBoardByID(user.BoardID);
 | 
			
		||||
            var boardRet = _userManager.GetBoardByID(user.BoardID);
 | 
			
		||||
            if (!boardRet.IsSuccessful || !boardRet.Value.HasValue)
 | 
			
		||||
                return null;
 | 
			
		||||
 | 
			
		||||
@@ -464,4 +397,77 @@ public class DebuggerController : ControllerBase
 | 
			
		||||
            return StatusCode(StatusCodes.Status500InternalServerError, "操作失败,请稍后重试");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 表示单个信号通道的配置信息
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class ChannelConfig
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 通道名称
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public string name;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 通道显示颜色(如前端波形显示用)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public string color;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 通道信号线宽度(位数)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public UInt32 wireWidth;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 信号线在父端口中的起始索引(bit)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public UInt32 wireStartIndex;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 父端口编号
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public UInt32 parentPort;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 捕获模式(如上升沿、下降沿等)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public CaptureMode mode;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 调试器整体配置信息
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class DebuggerConfig
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 时钟频率
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public UInt32 clkFreq;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 总端口数量
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public UInt32 totalPortNum;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 捕获深度(采样点数)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public UInt32 captureDepth;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 触发器数量
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public UInt32 triggerNum;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 所有信号通道的配置信息
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public ChannelConfig[] channelConfigs;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 单个通道的捕获数据
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class ChannelCaptureData
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 通道名称
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public string name;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 通道捕获到的数据(Base64编码的UInt32数组)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        required public string data;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,163 @@ public class ExamController : ControllerBase
 | 
			
		||||
{
 | 
			
		||||
    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
    private readonly Database.ExamManager _examManager;
 | 
			
		||||
 | 
			
		||||
    public ExamController(Database.ExamManager examManager)
 | 
			
		||||
    {
 | 
			
		||||
        _examManager = examManager;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取所有实验列表
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <returns>实验列表</returns>
 | 
			
		||||
    [Authorize]
 | 
			
		||||
    [HttpGet("list")]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(ExamSummary[]), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
 | 
			
		||||
    public IActionResult GetExamList()
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var exams = _examManager.GetAllExams();
 | 
			
		||||
 | 
			
		||||
            var examSummaries = exams.Select(exam => new ExamSummary
 | 
			
		||||
            {
 | 
			
		||||
                ID = exam.ID,
 | 
			
		||||
                Name = exam.Name,
 | 
			
		||||
                CreatedTime = exam.CreatedTime,
 | 
			
		||||
                UpdatedTime = exam.UpdatedTime,
 | 
			
		||||
                Tags = exam.GetTagsList(),
 | 
			
		||||
                Difficulty = exam.Difficulty,
 | 
			
		||||
                IsVisibleToUsers = exam.IsVisibleToUsers
 | 
			
		||||
            }).ToArray();
 | 
			
		||||
 | 
			
		||||
            logger.Info($"成功获取实验列表,共 {examSummaries.Length} 个实验");
 | 
			
		||||
            return Ok(examSummaries);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"获取实验列表时出错: {ex.Message}");
 | 
			
		||||
            return StatusCode(StatusCodes.Status500InternalServerError, $"获取实验列表失败: {ex.Message}");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 根据实验ID获取实验详细信息
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="examId">实验ID</param>
 | 
			
		||||
    /// <returns>实验详细信息</returns>
 | 
			
		||||
    [Authorize]
 | 
			
		||||
    [HttpGet("{examId}")]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(ExamInfo), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
 | 
			
		||||
    public IActionResult GetExam(string examId)
 | 
			
		||||
    {
 | 
			
		||||
        if (string.IsNullOrWhiteSpace(examId))
 | 
			
		||||
            return BadRequest("实验ID不能为空");
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var result = _examManager.GetExamByID(examId);
 | 
			
		||||
 | 
			
		||||
            if (!result.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"获取实验时出错: {result.Error.Message}");
 | 
			
		||||
                return StatusCode(StatusCodes.Status500InternalServerError, $"获取实验失败: {result.Error.Message}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!result.Value.HasValue)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Warn($"实验不存在: {examId}");
 | 
			
		||||
                return NotFound($"实验 {examId} 不存在");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var exam = result.Value.Value;
 | 
			
		||||
            var examInfo = new ExamInfo
 | 
			
		||||
            {
 | 
			
		||||
                ID = exam.ID,
 | 
			
		||||
                Name = exam.Name,
 | 
			
		||||
                Description = exam.Description,
 | 
			
		||||
                CreatedTime = exam.CreatedTime,
 | 
			
		||||
                UpdatedTime = exam.UpdatedTime,
 | 
			
		||||
                Tags = exam.GetTagsList(),
 | 
			
		||||
                Difficulty = exam.Difficulty,
 | 
			
		||||
                IsVisibleToUsers = exam.IsVisibleToUsers
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            logger.Info($"成功获取实验信息: {examId}");
 | 
			
		||||
            return Ok(examInfo);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"获取实验 {examId} 时出错: {ex.Message}");
 | 
			
		||||
            return StatusCode(StatusCodes.Status500InternalServerError, $"获取实验失败: {ex.Message}");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 创建新实验
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="request">创建实验请求</param>
 | 
			
		||||
    /// <returns>创建结果</returns>
 | 
			
		||||
    [Authorize("Admin")]
 | 
			
		||||
    [HttpPost]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(ExamInfo), StatusCodes.Status201Created)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status409Conflict)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
 | 
			
		||||
    public IActionResult CreateExam([FromBody] CreateExamRequest request)
 | 
			
		||||
    {
 | 
			
		||||
        if (string.IsNullOrWhiteSpace(request.ID) || string.IsNullOrWhiteSpace(request.Name) || string.IsNullOrWhiteSpace(request.Description))
 | 
			
		||||
            return BadRequest("实验ID、名称和描述不能为空");
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var result = _examManager.CreateExam(request.ID, request.Name, request.Description, request.Tags, request.Difficulty, request.IsVisibleToUsers);
 | 
			
		||||
 | 
			
		||||
            if (!result.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                if (result.Error.Message.Contains("已存在"))
 | 
			
		||||
                    return Conflict(result.Error.Message);
 | 
			
		||||
 | 
			
		||||
                logger.Error($"创建实验时出错: {result.Error.Message}");
 | 
			
		||||
                return StatusCode(StatusCodes.Status500InternalServerError, $"创建实验失败: {result.Error.Message}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var exam = result.Value;
 | 
			
		||||
            var examInfo = new ExamInfo
 | 
			
		||||
            {
 | 
			
		||||
                ID = exam.ID,
 | 
			
		||||
                Name = exam.Name,
 | 
			
		||||
                Description = exam.Description,
 | 
			
		||||
                CreatedTime = exam.CreatedTime,
 | 
			
		||||
                UpdatedTime = exam.UpdatedTime,
 | 
			
		||||
                Tags = exam.GetTagsList(),
 | 
			
		||||
                Difficulty = exam.Difficulty,
 | 
			
		||||
                IsVisibleToUsers = exam.IsVisibleToUsers
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            logger.Info($"成功创建实验: {request.ID}");
 | 
			
		||||
            return CreatedAtAction(nameof(GetExam), new { examId = request.ID }, examInfo);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"创建实验 {request.ID} 时出错: {ex.Message}");
 | 
			
		||||
            return StatusCode(StatusCodes.Status500InternalServerError, $"创建实验失败: {ex.Message}");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 实验信息类
 | 
			
		||||
    /// </summary>
 | 
			
		||||
@@ -136,156 +293,4 @@ public class ExamController : ControllerBase
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public bool IsVisibleToUsers { get; set; } = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取所有实验列表
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <returns>实验列表</returns>
 | 
			
		||||
    [Authorize]
 | 
			
		||||
    [HttpGet("list")]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(ExamSummary[]), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
 | 
			
		||||
    public IActionResult GetExamList()
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            using var db = new Database.AppDataConnection();
 | 
			
		||||
            var exams = db.GetAllExams();
 | 
			
		||||
 | 
			
		||||
            var examSummaries = exams.Select(exam => new ExamSummary
 | 
			
		||||
            {
 | 
			
		||||
                ID = exam.ID,
 | 
			
		||||
                Name = exam.Name,
 | 
			
		||||
                CreatedTime = exam.CreatedTime,
 | 
			
		||||
                UpdatedTime = exam.UpdatedTime,
 | 
			
		||||
                Tags = exam.GetTagsList(),
 | 
			
		||||
                Difficulty = exam.Difficulty,
 | 
			
		||||
                IsVisibleToUsers = exam.IsVisibleToUsers
 | 
			
		||||
            }).ToArray();
 | 
			
		||||
 | 
			
		||||
            logger.Info($"成功获取实验列表,共 {examSummaries.Length} 个实验");
 | 
			
		||||
            return Ok(examSummaries);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"获取实验列表时出错: {ex.Message}");
 | 
			
		||||
            return StatusCode(StatusCodes.Status500InternalServerError, $"获取实验列表失败: {ex.Message}");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 根据实验ID获取实验详细信息
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="examId">实验ID</param>
 | 
			
		||||
    /// <returns>实验详细信息</returns>
 | 
			
		||||
    [Authorize]
 | 
			
		||||
    [HttpGet("{examId}")]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(ExamInfo), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
 | 
			
		||||
    public IActionResult GetExam(string examId)
 | 
			
		||||
    {
 | 
			
		||||
        if (string.IsNullOrWhiteSpace(examId))
 | 
			
		||||
            return BadRequest("实验ID不能为空");
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            using var db = new Database.AppDataConnection();
 | 
			
		||||
            var result = db.GetExamByID(examId);
 | 
			
		||||
 | 
			
		||||
            if (!result.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"获取实验时出错: {result.Error.Message}");
 | 
			
		||||
                return StatusCode(StatusCodes.Status500InternalServerError, $"获取实验失败: {result.Error.Message}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!result.Value.HasValue)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Warn($"实验不存在: {examId}");
 | 
			
		||||
                return NotFound($"实验 {examId} 不存在");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var exam = result.Value.Value;
 | 
			
		||||
            var examInfo = new ExamInfo
 | 
			
		||||
            {
 | 
			
		||||
                ID = exam.ID,
 | 
			
		||||
                Name = exam.Name,
 | 
			
		||||
                Description = exam.Description,
 | 
			
		||||
                CreatedTime = exam.CreatedTime,
 | 
			
		||||
                UpdatedTime = exam.UpdatedTime,
 | 
			
		||||
                Tags = exam.GetTagsList(),
 | 
			
		||||
                Difficulty = exam.Difficulty,
 | 
			
		||||
                IsVisibleToUsers = exam.IsVisibleToUsers
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            logger.Info($"成功获取实验信息: {examId}");
 | 
			
		||||
            return Ok(examInfo);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"获取实验 {examId} 时出错: {ex.Message}");
 | 
			
		||||
            return StatusCode(StatusCodes.Status500InternalServerError, $"获取实验失败: {ex.Message}");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 创建新实验
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="request">创建实验请求</param>
 | 
			
		||||
    /// <returns>创建结果</returns>
 | 
			
		||||
    [Authorize("Admin")]
 | 
			
		||||
    [HttpPost]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(ExamInfo), StatusCodes.Status201Created)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status409Conflict)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
 | 
			
		||||
    public IActionResult CreateExam([FromBody] CreateExamRequest request)
 | 
			
		||||
    {
 | 
			
		||||
        if (string.IsNullOrWhiteSpace(request.ID) || string.IsNullOrWhiteSpace(request.Name) || string.IsNullOrWhiteSpace(request.Description))
 | 
			
		||||
            return BadRequest("实验ID、名称和描述不能为空");
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            using var db = new Database.AppDataConnection();
 | 
			
		||||
            var result = db.CreateExam(request.ID, request.Name, request.Description, request.Tags, request.Difficulty, request.IsVisibleToUsers);
 | 
			
		||||
 | 
			
		||||
            if (!result.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                if (result.Error.Message.Contains("已存在"))
 | 
			
		||||
                    return Conflict(result.Error.Message);
 | 
			
		||||
                
 | 
			
		||||
                logger.Error($"创建实验时出错: {result.Error.Message}");
 | 
			
		||||
                return StatusCode(StatusCodes.Status500InternalServerError, $"创建实验失败: {result.Error.Message}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var exam = result.Value;
 | 
			
		||||
            var examInfo = new ExamInfo
 | 
			
		||||
            {
 | 
			
		||||
                ID = exam.ID,
 | 
			
		||||
                Name = exam.Name,
 | 
			
		||||
                Description = exam.Description,
 | 
			
		||||
                CreatedTime = exam.CreatedTime,
 | 
			
		||||
                UpdatedTime = exam.UpdatedTime,
 | 
			
		||||
                Tags = exam.GetTagsList(),
 | 
			
		||||
                Difficulty = exam.Difficulty,
 | 
			
		||||
                IsVisibleToUsers = exam.IsVisibleToUsers
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            logger.Info($"成功创建实验: {request.ID}");
 | 
			
		||||
            return CreatedAtAction(nameof(GetExam), new { examId = request.ID }, examInfo);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"创建实验 {request.ID} 时出错: {ex.Message}");
 | 
			
		||||
            return StatusCode(StatusCodes.Status500InternalServerError, $"创建实验失败: {ex.Message}");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Cors;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using System.Security.Claims;
 | 
			
		||||
using server.Services;
 | 
			
		||||
using Database;
 | 
			
		||||
 | 
			
		||||
namespace server.Controllers;
 | 
			
		||||
 | 
			
		||||
@@ -12,12 +11,15 @@ namespace server.Controllers;
 | 
			
		||||
[EnableCors("Users")]
 | 
			
		||||
public class HdmiVideoStreamController : ControllerBase
 | 
			
		||||
{
 | 
			
		||||
    private readonly HttpHdmiVideoStreamService _videoStreamService;
 | 
			
		||||
    private readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
    public HdmiVideoStreamController(HttpHdmiVideoStreamService videoStreamService)
 | 
			
		||||
    private readonly HttpHdmiVideoStreamService _videoStreamService;
 | 
			
		||||
    private readonly Database.UserManager _userManager;
 | 
			
		||||
 | 
			
		||||
    public HdmiVideoStreamController(HttpHdmiVideoStreamService videoStreamService, Database.UserManager userManager)
 | 
			
		||||
    {
 | 
			
		||||
        _videoStreamService = videoStreamService;
 | 
			
		||||
        _userManager = userManager;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 管理员获取所有板子的 endpoints
 | 
			
		||||
@@ -40,11 +42,7 @@ public class HdmiVideoStreamController : ControllerBase
 | 
			
		||||
        if (string.IsNullOrEmpty(userName))
 | 
			
		||||
            return Unauthorized("User name not found in claims.");
 | 
			
		||||
 | 
			
		||||
        var db = new AppDataConnection();
 | 
			
		||||
        if (db == null)
 | 
			
		||||
            return NotFound("Database connection failed.");
 | 
			
		||||
 | 
			
		||||
        var userRet = db.GetUserByName(userName);
 | 
			
		||||
        var userRet = _userManager.GetUserByName(userName);
 | 
			
		||||
        if (!userRet.IsSuccessful || !userRet.Value.HasValue)
 | 
			
		||||
            return NotFound("User not found.");
 | 
			
		||||
 | 
			
		||||
@@ -53,7 +51,7 @@ public class HdmiVideoStreamController : ControllerBase
 | 
			
		||||
        if (boardId == Guid.Empty)
 | 
			
		||||
            return NotFound("No board bound to this user.");
 | 
			
		||||
 | 
			
		||||
        var boardRet = db.GetBoardByID(boardId);
 | 
			
		||||
        var boardRet = _userManager.GetBoardByID(boardId);
 | 
			
		||||
        if (!boardRet.IsSuccessful || !boardRet.Value.HasValue)
 | 
			
		||||
            return NotFound("Board not found.");
 | 
			
		||||
 | 
			
		||||
@@ -70,11 +68,7 @@ public class HdmiVideoStreamController : ControllerBase
 | 
			
		||||
        if (string.IsNullOrEmpty(userName))
 | 
			
		||||
            return Unauthorized("User name not found in claims.");
 | 
			
		||||
 | 
			
		||||
        var db = new AppDataConnection();
 | 
			
		||||
        if (db == null)
 | 
			
		||||
            return NotFound("Database connection failed.");
 | 
			
		||||
 | 
			
		||||
        var userRet = db.GetUserByName(userName);
 | 
			
		||||
        var userRet = _userManager.GetUserByName(userName);
 | 
			
		||||
        if (!userRet.IsSuccessful || !userRet.Value.HasValue)
 | 
			
		||||
            return NotFound("User not found.");
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -17,12 +17,17 @@ public class JtagController : ControllerBase
 | 
			
		||||
    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
    private readonly ProgressTrackerService _tracker;
 | 
			
		||||
    private readonly UserManager _userManager;
 | 
			
		||||
    private readonly ResourceManager _resourceManager;
 | 
			
		||||
 | 
			
		||||
    private const string BITSTREAM_PATH = "bitstream/Jtag";
 | 
			
		||||
 | 
			
		||||
    public JtagController(ProgressTrackerService tracker)
 | 
			
		||||
    public JtagController(
 | 
			
		||||
        ProgressTrackerService tracker, UserManager userManager, ResourceManager resourceManager)
 | 
			
		||||
    {
 | 
			
		||||
        _tracker = tracker;
 | 
			
		||||
        _userManager = userManager;
 | 
			
		||||
        _resourceManager = resourceManager;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
@@ -127,6 +132,7 @@ public class JtagController : ControllerBase
 | 
			
		||||
    /// <param name="address">JTAG 设备地址</param>
 | 
			
		||||
    /// <param name="port">JTAG 设备端口</param>
 | 
			
		||||
    /// <param name="bitstreamId">比特流ID</param>
 | 
			
		||||
    /// <param name="cancelToken">取消令牌</param>
 | 
			
		||||
    /// <returns>进度跟踪TaskID</returns>
 | 
			
		||||
    [HttpPost("DownloadBitstream")]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
@@ -134,7 +140,7 @@ public class JtagController : ControllerBase
 | 
			
		||||
    [ProducesResponseType(typeof(string), StatusCodes.Status400BadRequest)]
 | 
			
		||||
    [ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
 | 
			
		||||
    public async ValueTask<IResult> DownloadBitstream(string address, int port, int bitstreamId, CancellationToken cancelToken)
 | 
			
		||||
    public IResult DownloadBitstream(string address, int port, int bitstreamId, CancellationToken cancelToken)
 | 
			
		||||
    {
 | 
			
		||||
        logger.Info($"User {User.Identity?.Name} initiating bitstream download to device {address}:{port} using bitstream ID: {bitstreamId}");
 | 
			
		||||
 | 
			
		||||
@@ -149,35 +155,39 @@ public class JtagController : ControllerBase
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 从数据库获取用户信息
 | 
			
		||||
            using var db = new Database.AppDataConnection();
 | 
			
		||||
            var userResult = db.GetUserByName(username);
 | 
			
		||||
            var userResult = _userManager.GetUserByName(username);
 | 
			
		||||
            if (!userResult.IsSuccessful || !userResult.Value.HasValue)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"User {username} not found in database");
 | 
			
		||||
                return TypedResults.BadRequest("用户不存在");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var user = userResult.Value.Value;
 | 
			
		||||
 | 
			
		||||
            // 从数据库获取比特流
 | 
			
		||||
            var bitstreamResult = db.GetResourceById(bitstreamId);
 | 
			
		||||
            var user = userResult.Value.Value;
 | 
			
		||||
            var resourceRet = _resourceManager.GetResourceById(bitstreamId);
 | 
			
		||||
 | 
			
		||||
            if (!bitstreamResult.IsSuccessful)
 | 
			
		||||
            if (!resourceRet.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"User {username} failed to get bitstream from database: {bitstreamResult.Error}");
 | 
			
		||||
                return TypedResults.InternalServerError($"数据库查询失败: {bitstreamResult.Error?.Message}");
 | 
			
		||||
                logger.Error($"User {username} failed to get bitstream from database: {resourceRet.Error}");
 | 
			
		||||
                return TypedResults.InternalServerError($"数据库查询失败: {resourceRet.Error?.Message}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!bitstreamResult.Value.HasValue)
 | 
			
		||||
            if (!resourceRet.Value.HasValue)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Warn($"User {username} attempted to download non-existent bitstream ID: {bitstreamId}");
 | 
			
		||||
                return TypedResults.BadRequest("比特流不存在");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var bitstream = bitstreamResult.Value.Value;
 | 
			
		||||
 | 
			
		||||
            // 处理比特流数据
 | 
			
		||||
            var fileBytes = bitstream.Data;
 | 
			
		||||
            var resource = resourceRet.Value.Value;
 | 
			
		||||
            var bitstreamRet = _resourceManager.ReadBytesFromPath(resource.Path);
 | 
			
		||||
            if (!bitstreamRet.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"User {username} failed to read bitstream file: {bitstreamRet.Error}");
 | 
			
		||||
                return TypedResults.InternalServerError($"比特流读取失败: {bitstreamRet.Error?.Message}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var fileBytes = bitstreamRet.Value;
 | 
			
		||||
            if (fileBytes == null || fileBytes.Length == 0)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Warn($"User {username} found empty bitstream data for ID: {bitstreamId}");
 | 
			
		||||
@@ -235,7 +245,7 @@ public class JtagController : ControllerBase
 | 
			
		||||
 | 
			
		||||
                    if (ret.IsSuccessful)
 | 
			
		||||
                    {
 | 
			
		||||
                        logger.Info($"User {username} successfully downloaded bitstream '{bitstream.ResourceName}' to device {address}");
 | 
			
		||||
                        logger.Info($"User {username} successfully downloaded bitstream '{resource.ResourceName}' to device {address}");
 | 
			
		||||
                        progress.Finish();
 | 
			
		||||
                    }
 | 
			
		||||
                    else
 | 
			
		||||
 
 | 
			
		||||
@@ -15,56 +15,11 @@ public class LogicAnalyzerController : ControllerBase
 | 
			
		||||
{
 | 
			
		||||
    private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 信号触发配置
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class SignalTriggerConfig
 | 
			
		||||
    private readonly Database.UserManager _userManager;
 | 
			
		||||
 | 
			
		||||
    public LogicAnalyzerController(Database.UserManager userManager)
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 信号索引 (0-7)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public int SignalIndex { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 操作符
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public SignalOperator Operator { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 信号值
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public SignalValue Value { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 捕获配置
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class CaptureConfig
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 全局触发模式
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public GlobalCaptureMode GlobalMode { get; set; }
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 捕获深度
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public int CaptureLength { get; set; } = 2048 * 32;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 预采样深度
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public int PreCaptureLength { get; set; } = 2048;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 有效通道
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public AnalyzerChannelDiv ChannelDiv { get; set; } = AnalyzerChannelDiv.EIGHT;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 时钟分频系数
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public AnalyzerClockDiv ClockDiv { get; set; } = AnalyzerClockDiv.DIV1;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 信号触发配置列表
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public SignalTriggerConfig[] SignalConfigs { get; set; } = Array.Empty<SignalTriggerConfig>();
 | 
			
		||||
        _userManager = userManager;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
@@ -78,8 +33,7 @@ public class LogicAnalyzerController : ControllerBase
 | 
			
		||||
            if (string.IsNullOrEmpty(userName))
 | 
			
		||||
                return null;
 | 
			
		||||
 | 
			
		||||
            using var db = new Database.AppDataConnection();
 | 
			
		||||
            var userRet = db.GetUserByName(userName);
 | 
			
		||||
            var userRet = _userManager.GetUserByName(userName);
 | 
			
		||||
            if (!userRet.IsSuccessful || !userRet.Value.HasValue)
 | 
			
		||||
                return null;
 | 
			
		||||
 | 
			
		||||
@@ -87,7 +41,7 @@ public class LogicAnalyzerController : ControllerBase
 | 
			
		||||
            if (user.BoardID == Guid.Empty)
 | 
			
		||||
                return null;
 | 
			
		||||
 | 
			
		||||
            var boardRet = db.GetBoardByID(user.BoardID);
 | 
			
		||||
            var boardRet = _userManager.GetBoardByID(user.BoardID);
 | 
			
		||||
            if (!boardRet.IsSuccessful || !boardRet.Value.HasValue)
 | 
			
		||||
                return null;
 | 
			
		||||
 | 
			
		||||
@@ -422,4 +376,57 @@ public class LogicAnalyzerController : ControllerBase
 | 
			
		||||
            return StatusCode(StatusCodes.Status500InternalServerError, "操作失败,请稍后重试");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 信号触发配置
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class SignalTriggerConfig
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 信号索引 (0-7)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public int SignalIndex { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 操作符
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public SignalOperator Operator { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 信号值
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public SignalValue Value { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 捕获配置
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class CaptureConfig
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 全局触发模式
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public GlobalCaptureMode GlobalMode { get; set; }
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 捕获深度
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public int CaptureLength { get; set; } = 2048 * 32;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 预采样深度
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public int PreCaptureLength { get; set; } = 2048;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 有效通道
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public AnalyzerChannelDiv ChannelDiv { get; set; } = AnalyzerChannelDiv.EIGHT;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 时钟分频系数
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public AnalyzerClockDiv ClockDiv { get; set; } = AnalyzerClockDiv.DIV1;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 信号触发配置列表
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public SignalTriggerConfig[] SignalConfigs { get; set; } = Array.Empty<SignalTriggerConfig>();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,71 +15,11 @@ public class OscilloscopeApiController : ControllerBase
 | 
			
		||||
{
 | 
			
		||||
    private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 示波器完整配置
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class OscilloscopeFullConfig
 | 
			
		||||
    private readonly Database.UserManager _userManager;
 | 
			
		||||
 | 
			
		||||
    public OscilloscopeApiController(Database.UserManager userManager)
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 是否启动捕获
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public bool CaptureEnabled { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 触发电平(0-255)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public byte TriggerLevel { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 触发边沿(true为上升沿,false为下降沿)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public bool TriggerRisingEdge { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 水平偏移量(0-1023)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public ushort HorizontalShift { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 抽样率(0-1023)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public ushort DecimationRate { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 是否自动刷新RAM
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public bool AutoRefreshRAM { get; set; } = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 示波器状态和数据
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class OscilloscopeDataResponse
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// AD采样频率
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public uint ADFrequency { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// AD采样幅度
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public byte ADVpp { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// AD采样最大值
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public byte ADMax { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// AD采样最小值
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public byte ADMin { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 波形数据(Base64编码)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public string WaveformData { get; set; } = string.Empty;
 | 
			
		||||
        _userManager = userManager;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
@@ -93,8 +33,7 @@ public class OscilloscopeApiController : ControllerBase
 | 
			
		||||
            if (string.IsNullOrEmpty(userName))
 | 
			
		||||
                return null;
 | 
			
		||||
 | 
			
		||||
            using var db = new Database.AppDataConnection();
 | 
			
		||||
            var userRet = db.GetUserByName(userName);
 | 
			
		||||
            var userRet = _userManager.GetUserByName(userName);
 | 
			
		||||
            if (!userRet.IsSuccessful || !userRet.Value.HasValue)
 | 
			
		||||
                return null;
 | 
			
		||||
 | 
			
		||||
@@ -102,7 +41,7 @@ public class OscilloscopeApiController : ControllerBase
 | 
			
		||||
            if (user.BoardID == Guid.Empty)
 | 
			
		||||
                return null;
 | 
			
		||||
 | 
			
		||||
            var boardRet = db.GetBoardByID(user.BoardID);
 | 
			
		||||
            var boardRet = _userManager.GetBoardByID(user.BoardID);
 | 
			
		||||
            if (!boardRet.IsSuccessful || !boardRet.Value.HasValue)
 | 
			
		||||
                return null;
 | 
			
		||||
 | 
			
		||||
@@ -481,4 +420,72 @@ public class OscilloscopeApiController : ControllerBase
 | 
			
		||||
            return StatusCode(StatusCodes.Status500InternalServerError, "操作失败,请稍后重试");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 示波器完整配置
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class OscilloscopeFullConfig
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 是否启动捕获
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public bool CaptureEnabled { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 触发电平(0-255)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public byte TriggerLevel { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 触发边沿(true为上升沿,false为下降沿)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public bool TriggerRisingEdge { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 水平偏移量(0-1023)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public ushort HorizontalShift { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 抽样率(0-1023)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public ushort DecimationRate { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 是否自动刷新RAM
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public bool AutoRefreshRAM { get; set; } = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 示波器状态和数据
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class OscilloscopeDataResponse
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// AD采样频率
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public uint ADFrequency { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// AD采样幅度
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public byte ADVpp { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// AD采样最大值
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public byte ADMax { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// AD采样最小值
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public byte ADMin { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 波形数据(Base64编码)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public string WaveformData { get; set; } = string.Empty;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,316 @@ public class ResourceController : ControllerBase
 | 
			
		||||
{
 | 
			
		||||
    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
    private readonly UserManager _userManager;
 | 
			
		||||
    private readonly ResourceManager _resourceManager;
 | 
			
		||||
 | 
			
		||||
    public ResourceController(UserManager userManager, ResourceManager resourceManager)
 | 
			
		||||
    {
 | 
			
		||||
        _userManager = userManager;
 | 
			
		||||
        _resourceManager = resourceManager;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 添加资源(文件上传)
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="request">添加资源请求</param>
 | 
			
		||||
    /// <param name="file">资源文件</param>
 | 
			
		||||
    /// <returns>添加结果</returns>
 | 
			
		||||
    [Authorize]
 | 
			
		||||
    [HttpPost]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(ResourceInfo), StatusCodes.Status201Created)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
 | 
			
		||||
    public async Task<IActionResult> AddResource([FromForm] AddResourceRequest request, IFormFile file)
 | 
			
		||||
    {
 | 
			
		||||
        if (string.IsNullOrWhiteSpace(request.ResourceType) || string.IsNullOrWhiteSpace(request.ResourcePurpose) || file == null)
 | 
			
		||||
            return BadRequest("资源类型、资源用途和文件不能为空");
 | 
			
		||||
 | 
			
		||||
        // 验证资源用途
 | 
			
		||||
        if (request.ResourcePurpose != Resource.ResourcePurposes.Template && request.ResourcePurpose != Resource.ResourcePurposes.User)
 | 
			
		||||
            return BadRequest($"无效的资源用途: {request.ResourcePurpose}");
 | 
			
		||||
 | 
			
		||||
        // 模板资源需要管理员权限
 | 
			
		||||
        if (request.ResourcePurpose == Resource.ResourcePurposes.Template && !User.IsInRole("Admin"))
 | 
			
		||||
            return Forbid("只有管理员可以添加模板资源");
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            // 获取当前用户ID
 | 
			
		||||
            var userName = User.Identity?.Name;
 | 
			
		||||
            if (string.IsNullOrEmpty(userName))
 | 
			
		||||
                return Unauthorized("无法获取用户信息");
 | 
			
		||||
 | 
			
		||||
            var userResult = _userManager.GetUserByName(userName);
 | 
			
		||||
            if (!userResult.IsSuccessful || !userResult.Value.HasValue)
 | 
			
		||||
                return Unauthorized("用户不存在");
 | 
			
		||||
 | 
			
		||||
            var user = userResult.Value.Value;
 | 
			
		||||
 | 
			
		||||
            // 读取文件数据
 | 
			
		||||
            using var memoryStream = new MemoryStream();
 | 
			
		||||
            await file.CopyToAsync(memoryStream);
 | 
			
		||||
            var fileData = memoryStream.ToArray();
 | 
			
		||||
 | 
			
		||||
            var result = _resourceManager.AddResource(
 | 
			
		||||
                user.ID, request.ResourceType, request.ResourcePurpose,
 | 
			
		||||
                file.FileName, fileData, request.ExamID);
 | 
			
		||||
 | 
			
		||||
            if (!result.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                if (result.Error.Message.Contains("不存在"))
 | 
			
		||||
                    return NotFound(result.Error.Message);
 | 
			
		||||
 | 
			
		||||
                logger.Error($"添加资源时出错: {result.Error.Message}");
 | 
			
		||||
                return StatusCode(StatusCodes.Status500InternalServerError, $"添加资源失败: {result.Error.Message}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var resource = result.Value;
 | 
			
		||||
            var resourceInfo = new ResourceInfo
 | 
			
		||||
            {
 | 
			
		||||
                ID = resource.ID,
 | 
			
		||||
                Name = resource.ResourceName,
 | 
			
		||||
                Type = resource.ResourceType,
 | 
			
		||||
                Purpose = resource.ResourcePurpose,
 | 
			
		||||
                UploadTime = resource.UploadTime,
 | 
			
		||||
                ExamID = resource.ExamID,
 | 
			
		||||
                MimeType = resource.MimeType
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            logger.Info($"成功添加资源: {request.ResourceType}/{request.ResourcePurpose}/{file.FileName}");
 | 
			
		||||
            return CreatedAtAction(nameof(GetResourceById), new { resourceId = resource.ID }, resourceInfo);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"添加资源 {request.ResourceType}/{request.ResourcePurpose}/{file.FileName} 时出错: {ex.Message}");
 | 
			
		||||
            return StatusCode(StatusCodes.Status500InternalServerError, $"添加资源失败: {ex.Message}");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取资源列表
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="examId">实验ID(可选)</param>
 | 
			
		||||
    /// <param name="resourceType">资源类型(可选)</param>
 | 
			
		||||
    /// <param name="resourcePurpose">资源用途(可选)</param>
 | 
			
		||||
    /// <returns>资源列表</returns>
 | 
			
		||||
    [Authorize]
 | 
			
		||||
    [HttpGet]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(ResourceInfo[]), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
 | 
			
		||||
    public IActionResult GetResourceList([FromQuery] string? examId = null, [FromQuery] string? resourceType = null, [FromQuery] string? resourcePurpose = null)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            // 获取当前用户ID
 | 
			
		||||
            var userName = User.Identity?.Name;
 | 
			
		||||
            if (string.IsNullOrEmpty(userName))
 | 
			
		||||
                return Unauthorized("无法获取用户信息");
 | 
			
		||||
 | 
			
		||||
            var userResult = _userManager.GetUserByName(userName);
 | 
			
		||||
            if (!userResult.IsSuccessful || !userResult.Value.HasValue)
 | 
			
		||||
                return Unauthorized("用户不存在");
 | 
			
		||||
 | 
			
		||||
            var user = userResult.Value.Value;
 | 
			
		||||
 | 
			
		||||
            // 普通用户只能查看自己的资源和模板资源
 | 
			
		||||
            Guid? userId = null;
 | 
			
		||||
            if (!User.IsInRole("Admin"))
 | 
			
		||||
            {
 | 
			
		||||
                // 如果指定了用户资源用途,则只查看自己的资源
 | 
			
		||||
                if (resourcePurpose == Resource.ResourcePurposes.User)
 | 
			
		||||
                {
 | 
			
		||||
                    userId = user.ID;
 | 
			
		||||
                }
 | 
			
		||||
                // 如果指定了模板资源用途,则不限制用户ID
 | 
			
		||||
                else if (resourcePurpose == Resource.ResourcePurposes.Template)
 | 
			
		||||
                {
 | 
			
		||||
                    userId = null;
 | 
			
		||||
                }
 | 
			
		||||
                // 如果没有指定用途,则查看自己的用户资源和所有模板资源
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    // 这种情况下需要分别查询并合并结果
 | 
			
		||||
                    var userResourcesResult = _resourceManager.GetFullResourceList(examId, resourceType, Resource.ResourcePurposes.User, user.ID);
 | 
			
		||||
                    var templateResourcesResult = _resourceManager.GetFullResourceList(examId, resourceType, Resource.ResourcePurposes.Template, null);
 | 
			
		||||
 | 
			
		||||
                    if (!userResourcesResult.IsSuccessful || !templateResourcesResult.IsSuccessful)
 | 
			
		||||
                    {
 | 
			
		||||
                        logger.Error($"获取资源列表时出错");
 | 
			
		||||
                        return StatusCode(StatusCodes.Status500InternalServerError, "获取资源列表失败");
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var allResources = userResourcesResult.Value.Concat(templateResourcesResult.Value)
 | 
			
		||||
                        .OrderByDescending(r => r.UploadTime);
 | 
			
		||||
                    var mergedResourceInfos = allResources.Select(r => new ResourceInfo
 | 
			
		||||
                    {
 | 
			
		||||
                        ID = r.ID,
 | 
			
		||||
                        Name = r.ResourceName,
 | 
			
		||||
                        Type = r.ResourceType,
 | 
			
		||||
                        Purpose = r.ResourcePurpose,
 | 
			
		||||
                        UploadTime = r.UploadTime,
 | 
			
		||||
                        ExamID = r.ExamID,
 | 
			
		||||
                        MimeType = r.MimeType
 | 
			
		||||
                    }).ToArray();
 | 
			
		||||
 | 
			
		||||
                    logger.Info($"成功获取资源列表,共 {mergedResourceInfos.Length} 个资源");
 | 
			
		||||
                    return Ok(mergedResourceInfos);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var result = _resourceManager.GetFullResourceList(examId, resourceType, resourcePurpose, userId);
 | 
			
		||||
 | 
			
		||||
            if (!result.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"获取资源列表时出错: {result.Error.Message}");
 | 
			
		||||
                return StatusCode(StatusCodes.Status500InternalServerError, $"获取资源列表失败: {result.Error.Message}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var resources = result.Value.Select(r => new ResourceInfo
 | 
			
		||||
            {
 | 
			
		||||
                ID = r.ID,
 | 
			
		||||
                Name = r.ResourceName,
 | 
			
		||||
                Type = r.ResourceType,
 | 
			
		||||
                Purpose = r.ResourcePurpose,
 | 
			
		||||
                UploadTime = r.UploadTime,
 | 
			
		||||
                ExamID = r.ExamID,
 | 
			
		||||
                MimeType = r.MimeType
 | 
			
		||||
            }).ToArray();
 | 
			
		||||
 | 
			
		||||
            logger.Info($"成功获取资源列表,共 {resources.Length} 个资源");
 | 
			
		||||
            return Ok(resources);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"获取资源列表时出错: {ex.Message}");
 | 
			
		||||
            return StatusCode(StatusCodes.Status500InternalServerError, $"获取资源列表失败: {ex.Message}");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 根据资源ID下载资源
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="resourceId">资源ID</param>
 | 
			
		||||
    /// <returns>资源文件</returns>
 | 
			
		||||
    [HttpGet("{resourceId}")]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(FileResult), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
 | 
			
		||||
    public IActionResult GetResourceById(int resourceId)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var result = _resourceManager.GetResourceById(resourceId);
 | 
			
		||||
 | 
			
		||||
            if (!result.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"获取资源时出错: {result.Error.Message}");
 | 
			
		||||
                return StatusCode(StatusCodes.Status500InternalServerError, $"获取资源失败: {result.Error.Message}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!result.Value.HasValue)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Warn($"资源不存在: {resourceId}");
 | 
			
		||||
                return NotFound($"资源 {resourceId} 不存在");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var resource = result.Value.Value;
 | 
			
		||||
            logger.Info($"成功获取资源: {resourceId} ({resource.ResourceName})");
 | 
			
		||||
 | 
			
		||||
            var dataRet = _resourceManager.ReadBytesFromPath(resource.Path);
 | 
			
		||||
            if (!dataRet.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"读取资源数据时出错: {dataRet.Error.Message}");
 | 
			
		||||
                return StatusCode(StatusCodes.Status500InternalServerError, $"读取资源数据失败: {dataRet.Error.Message}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return File(dataRet.Value, resource.MimeType ?? "application/octet-stream", resource.ResourceName);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"获取资源 {resourceId} 时出错: {ex.Message}");
 | 
			
		||||
            return StatusCode(StatusCodes.Status500InternalServerError, $"获取资源失败: {ex.Message}");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 删除资源
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="resourceId">资源ID</param>
 | 
			
		||||
    /// <returns>删除结果</returns>
 | 
			
		||||
    [Authorize]
 | 
			
		||||
    [HttpDelete("{resourceId}")]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
 | 
			
		||||
    public IActionResult DeleteResource(int resourceId)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            // 获取当前用户信息
 | 
			
		||||
            var userName = User.Identity?.Name;
 | 
			
		||||
            if (string.IsNullOrEmpty(userName))
 | 
			
		||||
                return Unauthorized("无法获取用户信息");
 | 
			
		||||
 | 
			
		||||
            var userResult = _userManager.GetUserByName(userName);
 | 
			
		||||
            if (!userResult.IsSuccessful || !userResult.Value.HasValue)
 | 
			
		||||
                return Unauthorized("用户不存在");
 | 
			
		||||
 | 
			
		||||
            var user = userResult.Value.Value;
 | 
			
		||||
 | 
			
		||||
            // 先获取资源信息以验证权限
 | 
			
		||||
            var resourceResult = _resourceManager.GetResourceById(resourceId);
 | 
			
		||||
            if (!resourceResult.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"获取资源时出错: {resourceResult.Error.Message}");
 | 
			
		||||
                return StatusCode(StatusCodes.Status500InternalServerError, $"获取资源失败: {resourceResult.Error.Message}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!resourceResult.Value.HasValue)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Warn($"资源不存在: {resourceId}");
 | 
			
		||||
                return NotFound($"资源 {resourceId} 不存在");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var resource = resourceResult.Value.Value;
 | 
			
		||||
 | 
			
		||||
            // 权限检查:管理员可以删除所有资源,普通用户只能删除自己的用户资源
 | 
			
		||||
            if (!User.IsInRole("Admin"))
 | 
			
		||||
            {
 | 
			
		||||
                if (resource.ResourcePurpose == Resource.ResourcePurposes.Template)
 | 
			
		||||
                    return Forbid("普通用户不能删除模板资源");
 | 
			
		||||
 | 
			
		||||
                if (resource.UserID != user.ID)
 | 
			
		||||
                    return Forbid("只能删除自己的资源");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var deleteResult = _resourceManager.DeleteResource(resourceId);
 | 
			
		||||
            if (!deleteResult.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"删除资源时出错: {deleteResult.Error.Message}");
 | 
			
		||||
                return StatusCode(StatusCodes.Status500InternalServerError, $"删除资源失败: {deleteResult.Error.Message}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            logger.Info($"成功删除资源: {resourceId} ({resource.ResourceName})");
 | 
			
		||||
            return NoContent();
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"删除资源 {resourceId} 时出错: {ex.Message}");
 | 
			
		||||
            return StatusCode(StatusCodes.Status500InternalServerError, $"删除资源失败: {ex.Message}");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 资源信息类
 | 
			
		||||
    /// </summary>
 | 
			
		||||
@@ -77,301 +387,4 @@ public class ResourceController : ControllerBase
 | 
			
		||||
        public string? ExamID { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 添加资源(文件上传)
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="request">添加资源请求</param>
 | 
			
		||||
    /// <param name="file">资源文件</param>
 | 
			
		||||
    /// <returns>添加结果</returns>
 | 
			
		||||
    [Authorize]
 | 
			
		||||
    [HttpPost]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(ResourceInfo), StatusCodes.Status201Created)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
 | 
			
		||||
    public async Task<IActionResult> AddResource([FromForm] AddResourceRequest request, IFormFile file)
 | 
			
		||||
    {
 | 
			
		||||
        if (string.IsNullOrWhiteSpace(request.ResourceType) || string.IsNullOrWhiteSpace(request.ResourcePurpose) || file == null)
 | 
			
		||||
            return BadRequest("资源类型、资源用途和文件不能为空");
 | 
			
		||||
 | 
			
		||||
        // 验证资源用途
 | 
			
		||||
        if (request.ResourcePurpose != Resource.ResourcePurposes.Template && request.ResourcePurpose != Resource.ResourcePurposes.User)
 | 
			
		||||
            return BadRequest($"无效的资源用途: {request.ResourcePurpose}");
 | 
			
		||||
 | 
			
		||||
        // 模板资源需要管理员权限
 | 
			
		||||
        if (request.ResourcePurpose == Resource.ResourcePurposes.Template && !User.IsInRole("Admin"))
 | 
			
		||||
            return Forbid("只有管理员可以添加模板资源");
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            using var db = new Database.AppDataConnection();
 | 
			
		||||
            
 | 
			
		||||
            // 获取当前用户ID
 | 
			
		||||
            var userName = User.Identity?.Name;
 | 
			
		||||
            if (string.IsNullOrEmpty(userName))
 | 
			
		||||
                return Unauthorized("无法获取用户信息");
 | 
			
		||||
 | 
			
		||||
            var userResult = db.GetUserByName(userName);
 | 
			
		||||
            if (!userResult.IsSuccessful || !userResult.Value.HasValue)
 | 
			
		||||
                return Unauthorized("用户不存在");
 | 
			
		||||
 | 
			
		||||
            var user = userResult.Value.Value;
 | 
			
		||||
 | 
			
		||||
            // 读取文件数据
 | 
			
		||||
            using var memoryStream = new MemoryStream();
 | 
			
		||||
            await file.CopyToAsync(memoryStream);
 | 
			
		||||
            var fileData = memoryStream.ToArray();
 | 
			
		||||
 | 
			
		||||
            var result = db.AddResource(user.ID, request.ResourceType, request.ResourcePurpose, file.FileName, fileData, request.ExamID);
 | 
			
		||||
 | 
			
		||||
            if (!result.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                if (result.Error.Message.Contains("不存在"))
 | 
			
		||||
                    return NotFound(result.Error.Message);
 | 
			
		||||
                
 | 
			
		||||
                logger.Error($"添加资源时出错: {result.Error.Message}");
 | 
			
		||||
                return StatusCode(StatusCodes.Status500InternalServerError, $"添加资源失败: {result.Error.Message}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var resource = result.Value;
 | 
			
		||||
            var resourceInfo = new ResourceInfo
 | 
			
		||||
            {
 | 
			
		||||
                ID = resource.ID,
 | 
			
		||||
                Name = resource.ResourceName,
 | 
			
		||||
                Type = resource.ResourceType,
 | 
			
		||||
                Purpose = resource.ResourcePurpose,
 | 
			
		||||
                UploadTime = resource.UploadTime,
 | 
			
		||||
                ExamID = resource.ExamID,
 | 
			
		||||
                MimeType = resource.MimeType
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            logger.Info($"成功添加资源: {request.ResourceType}/{request.ResourcePurpose}/{file.FileName}");
 | 
			
		||||
            return CreatedAtAction(nameof(GetResourceById), new { resourceId = resource.ID }, resourceInfo);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"添加资源 {request.ResourceType}/{request.ResourcePurpose}/{file.FileName} 时出错: {ex.Message}");
 | 
			
		||||
            return StatusCode(StatusCodes.Status500InternalServerError, $"添加资源失败: {ex.Message}");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取资源列表
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="examId">实验ID(可选)</param>
 | 
			
		||||
    /// <param name="resourceType">资源类型(可选)</param>
 | 
			
		||||
    /// <param name="resourcePurpose">资源用途(可选)</param>
 | 
			
		||||
    /// <returns>资源列表</returns>
 | 
			
		||||
    [Authorize]
 | 
			
		||||
    [HttpGet]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(ResourceInfo[]), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
 | 
			
		||||
    public IActionResult GetResourceList([FromQuery] string? examId = null, [FromQuery] string? resourceType = null, [FromQuery] string? resourcePurpose = null)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            using var db = new Database.AppDataConnection();
 | 
			
		||||
            
 | 
			
		||||
            // 获取当前用户ID
 | 
			
		||||
            var userName = User.Identity?.Name;
 | 
			
		||||
            if (string.IsNullOrEmpty(userName))
 | 
			
		||||
                return Unauthorized("无法获取用户信息");
 | 
			
		||||
 | 
			
		||||
            var userResult = db.GetUserByName(userName);
 | 
			
		||||
            if (!userResult.IsSuccessful || !userResult.Value.HasValue)
 | 
			
		||||
                return Unauthorized("用户不存在");
 | 
			
		||||
 | 
			
		||||
            var user = userResult.Value.Value;
 | 
			
		||||
 | 
			
		||||
            // 普通用户只能查看自己的资源和模板资源
 | 
			
		||||
            Guid? userId = null;
 | 
			
		||||
            if (!User.IsInRole("Admin"))
 | 
			
		||||
            {
 | 
			
		||||
                // 如果指定了用户资源用途,则只查看自己的资源
 | 
			
		||||
                if (resourcePurpose == Resource.ResourcePurposes.User)
 | 
			
		||||
                {
 | 
			
		||||
                    userId = user.ID;
 | 
			
		||||
                }
 | 
			
		||||
                // 如果指定了模板资源用途,则不限制用户ID
 | 
			
		||||
                else if (resourcePurpose == Resource.ResourcePurposes.Template)
 | 
			
		||||
                {
 | 
			
		||||
                    userId = null;
 | 
			
		||||
                }
 | 
			
		||||
                // 如果没有指定用途,则查看自己的用户资源和所有模板资源
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    // 这种情况下需要分别查询并合并结果
 | 
			
		||||
                    var userResourcesResult = db.GetFullResourceList(examId, resourceType, Resource.ResourcePurposes.User, user.ID);
 | 
			
		||||
                    var templateResourcesResult = db.GetFullResourceList(examId, resourceType, Resource.ResourcePurposes.Template, null);
 | 
			
		||||
                    
 | 
			
		||||
                    if (!userResourcesResult.IsSuccessful || !templateResourcesResult.IsSuccessful)
 | 
			
		||||
                    {
 | 
			
		||||
                        logger.Error($"获取资源列表时出错");
 | 
			
		||||
                        return StatusCode(StatusCodes.Status500InternalServerError, "获取资源列表失败");
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    var allResources = userResourcesResult.Value.Concat(templateResourcesResult.Value)
 | 
			
		||||
                        .OrderByDescending(r => r.UploadTime);
 | 
			
		||||
                    var mergedResourceInfos = allResources.Select(r => new ResourceInfo
 | 
			
		||||
                    {
 | 
			
		||||
                        ID = r.ID,
 | 
			
		||||
                        Name = r.ResourceName,
 | 
			
		||||
                        Type = r.ResourceType,
 | 
			
		||||
                        Purpose = r.ResourcePurpose,
 | 
			
		||||
                        UploadTime = r.UploadTime,
 | 
			
		||||
                        ExamID = r.ExamID,
 | 
			
		||||
                        MimeType = r.MimeType
 | 
			
		||||
                    }).ToArray();
 | 
			
		||||
 | 
			
		||||
                    logger.Info($"成功获取资源列表,共 {mergedResourceInfos.Length} 个资源");
 | 
			
		||||
                    return Ok(mergedResourceInfos);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var result = db.GetFullResourceList(examId, resourceType, resourcePurpose, userId);
 | 
			
		||||
 | 
			
		||||
            if (!result.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"获取资源列表时出错: {result.Error.Message}");
 | 
			
		||||
                return StatusCode(StatusCodes.Status500InternalServerError, $"获取资源列表失败: {result.Error.Message}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var resources = result.Value.Select(r => new ResourceInfo
 | 
			
		||||
            {
 | 
			
		||||
                ID = r.ID,
 | 
			
		||||
                Name = r.ResourceName,
 | 
			
		||||
                Type = r.ResourceType,
 | 
			
		||||
                Purpose = r.ResourcePurpose,
 | 
			
		||||
                UploadTime = r.UploadTime,
 | 
			
		||||
                ExamID = r.ExamID,
 | 
			
		||||
                MimeType = r.MimeType
 | 
			
		||||
            }).ToArray();
 | 
			
		||||
 | 
			
		||||
            logger.Info($"成功获取资源列表,共 {resources.Length} 个资源");
 | 
			
		||||
            return Ok(resources);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"获取资源列表时出错: {ex.Message}");
 | 
			
		||||
            return StatusCode(StatusCodes.Status500InternalServerError, $"获取资源列表失败: {ex.Message}");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 根据资源ID下载资源
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="resourceId">资源ID</param>
 | 
			
		||||
    /// <returns>资源文件</returns>
 | 
			
		||||
    [HttpGet("{resourceId}")]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(FileResult), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
 | 
			
		||||
    public IActionResult GetResourceById(int resourceId)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            using var db = new Database.AppDataConnection();
 | 
			
		||||
            var result = db.GetResourceById(resourceId);
 | 
			
		||||
 | 
			
		||||
            if (!result.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"获取资源时出错: {result.Error.Message}");
 | 
			
		||||
                return StatusCode(StatusCodes.Status500InternalServerError, $"获取资源失败: {result.Error.Message}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!result.Value.HasValue)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Warn($"资源不存在: {resourceId}");
 | 
			
		||||
                return NotFound($"资源 {resourceId} 不存在");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var resource = result.Value.Value;
 | 
			
		||||
            logger.Info($"成功获取资源: {resourceId} ({resource.ResourceName})");
 | 
			
		||||
            return File(resource.Data, resource.MimeType ?? "application/octet-stream", resource.ResourceName);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"获取资源 {resourceId} 时出错: {ex.Message}");
 | 
			
		||||
            return StatusCode(StatusCodes.Status500InternalServerError, $"获取资源失败: {ex.Message}");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 删除资源
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="resourceId">资源ID</param>
 | 
			
		||||
    /// <returns>删除结果</returns>
 | 
			
		||||
    [Authorize]
 | 
			
		||||
    [HttpDelete("{resourceId}")]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
 | 
			
		||||
    public IActionResult DeleteResource(int resourceId)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            using var db = new Database.AppDataConnection();
 | 
			
		||||
            
 | 
			
		||||
            // 获取当前用户信息
 | 
			
		||||
            var userName = User.Identity?.Name;
 | 
			
		||||
            if (string.IsNullOrEmpty(userName))
 | 
			
		||||
                return Unauthorized("无法获取用户信息");
 | 
			
		||||
 | 
			
		||||
            var userResult = db.GetUserByName(userName);
 | 
			
		||||
            if (!userResult.IsSuccessful || !userResult.Value.HasValue)
 | 
			
		||||
                return Unauthorized("用户不存在");
 | 
			
		||||
 | 
			
		||||
            var user = userResult.Value.Value;
 | 
			
		||||
 | 
			
		||||
            // 先获取资源信息以验证权限
 | 
			
		||||
            var resourceResult = db.GetResourceById(resourceId);
 | 
			
		||||
            if (!resourceResult.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"获取资源时出错: {resourceResult.Error.Message}");
 | 
			
		||||
                return StatusCode(StatusCodes.Status500InternalServerError, $"获取资源失败: {resourceResult.Error.Message}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!resourceResult.Value.HasValue)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Warn($"资源不存在: {resourceId}");
 | 
			
		||||
                return NotFound($"资源 {resourceId} 不存在");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var resource = resourceResult.Value.Value;
 | 
			
		||||
 | 
			
		||||
            // 权限检查:管理员可以删除所有资源,普通用户只能删除自己的用户资源
 | 
			
		||||
            if (!User.IsInRole("Admin"))
 | 
			
		||||
            {
 | 
			
		||||
                if (resource.ResourcePurpose == Resource.ResourcePurposes.Template)
 | 
			
		||||
                    return Forbid("普通用户不能删除模板资源");
 | 
			
		||||
                
 | 
			
		||||
                if (resource.UserID != user.ID)
 | 
			
		||||
                    return Forbid("只能删除自己的资源");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var deleteResult = db.DeleteResource(resourceId);
 | 
			
		||||
            if (!deleteResult.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"删除资源时出错: {deleteResult.Error.Message}");
 | 
			
		||||
                return StatusCode(StatusCodes.Status500InternalServerError, $"删除资源失败: {deleteResult.Error.Message}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            logger.Info($"成功删除资源: {resourceId} ({resource.ResourceName})");
 | 
			
		||||
            return NoContent();
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"删除资源 {resourceId} 时出错: {ex.Message}");
 | 
			
		||||
            return StatusCode(StatusCodes.Status500InternalServerError, $"删除资源失败: {ex.Message}");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Cors;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using System.Security.Claims;
 | 
			
		||||
using Database;
 | 
			
		||||
using DotNext;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
@@ -15,44 +14,21 @@ using DotNext;
 | 
			
		||||
public class VideoStreamController : ControllerBase
 | 
			
		||||
{
 | 
			
		||||
    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
    private readonly server.Services.HttpVideoStreamService _videoStreamService;
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 分辨率配置请求模型
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class ResolutionConfigRequest
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 宽度
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [Required]
 | 
			
		||||
        [Range(640, 1920, ErrorMessage = "宽度必须在640-1920范围内")]
 | 
			
		||||
        public int Width { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 高度
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [Required]
 | 
			
		||||
        [Range(480, 1080, ErrorMessage = "高度必须在480-1080范围内")]
 | 
			
		||||
        public int Height { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class AvailableResolutionsResponse
 | 
			
		||||
    {
 | 
			
		||||
        public int Width { get; set; }
 | 
			
		||||
        public int Height { get; set; }
 | 
			
		||||
        public string Name { get; set; } = string.Empty;
 | 
			
		||||
        public string Value => $"{Width}x{Height}";
 | 
			
		||||
    }
 | 
			
		||||
    private readonly Database.UserManager _userManager;
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 初始化HTTP视频流控制器
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="videoStreamService">HTTP视频流服务</param>
 | 
			
		||||
    public VideoStreamController(server.Services.HttpVideoStreamService videoStreamService)
 | 
			
		||||
    /// <param name="userManager">用户管理服务</param>
 | 
			
		||||
    public VideoStreamController(
 | 
			
		||||
        server.Services.HttpVideoStreamService videoStreamService, Database.UserManager userManager)
 | 
			
		||||
    {
 | 
			
		||||
        logger.Info("创建VideoStreamController,命名空间:{Namespace}", this.GetType().Namespace);
 | 
			
		||||
        _videoStreamService = videoStreamService;
 | 
			
		||||
        _userManager = userManager;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Optional<string> TryGetBoardId()
 | 
			
		||||
@@ -64,14 +40,7 @@ public class VideoStreamController : ControllerBase
 | 
			
		||||
            return Optional<string>.None;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var db = new AppDataConnection();
 | 
			
		||||
        if (db == null)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error("Database connection failed.");
 | 
			
		||||
            return Optional<string>.None;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var userRet = db.GetUserByName(userName);
 | 
			
		||||
        var userRet = _userManager.GetUserByName(userName);
 | 
			
		||||
        if (!userRet.IsSuccessful || !userRet.Value.HasValue)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error("User not found.");
 | 
			
		||||
@@ -349,4 +318,32 @@ public class VideoStreamController : ControllerBase
 | 
			
		||||
            return TypedResults.InternalServerError($"执行自动对焦失败: {ex.Message}");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 分辨率配置请求模型
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class ResolutionConfigRequest
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 宽度
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [Required]
 | 
			
		||||
        [Range(640, 1920, ErrorMessage = "宽度必须在640-1920范围内")]
 | 
			
		||||
        public int Width { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 高度
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [Required]
 | 
			
		||||
        [Range(480, 1080, ErrorMessage = "高度必须在480-1080范围内")]
 | 
			
		||||
        public int Height { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class AvailableResolutionsResponse
 | 
			
		||||
    {
 | 
			
		||||
        public int Width { get; set; }
 | 
			
		||||
        public int Height { get; set; }
 | 
			
		||||
        public string Name { get; set; } = string.Empty;
 | 
			
		||||
        public string Value => $"{Width}x{Height}";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										98
									
								
								server/src/Database/Connection.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								server/src/Database/Connection.cs
									
									
									
									
									
										Normal 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("所有数据库表已删除");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										154
									
								
								server/src/Database/ExamManager.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								server/src/Database/ExamManager.cs
									
									
									
									
									
										Normal 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]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										357
									
								
								server/src/Database/ResourceManager.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										357
									
								
								server/src/Database/ResourceManager.cs
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										341
									
								
								server/src/Database/Type.cs
									
									
									
									
									
										Normal 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-5,1为最简单)
 | 
			
		||||
    /// </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";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										458
									
								
								server/src/Database/UserManager.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										458
									
								
								server/src/Database/UserManager.cs
									
									
									
									
									
										Normal 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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -28,22 +28,24 @@ public interface IJtagReceiver
 | 
			
		||||
public class JtagHub : Hub<IJtagReceiver>, IJtagHub
 | 
			
		||||
{
 | 
			
		||||
    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
    private readonly IHubContext<JtagHub, IJtagReceiver> _hubContext;
 | 
			
		||||
    private readonly Database.UserManager _userManager;
 | 
			
		||||
 | 
			
		||||
    private static ConcurrentDictionary<string, int> FreqTable = new();
 | 
			
		||||
    private static ConcurrentDictionary<string, CancellationTokenSource> CancellationTokenSourceTable = new();
 | 
			
		||||
 | 
			
		||||
    private readonly IHubContext<JtagHub, IJtagReceiver> _hubContext;
 | 
			
		||||
 | 
			
		||||
    public JtagHub(IHubContext<JtagHub, IJtagReceiver> hubContext)
 | 
			
		||||
    public JtagHub(IHubContext<JtagHub, IJtagReceiver> hubContext, Database.UserManager userManager)
 | 
			
		||||
    {
 | 
			
		||||
        _hubContext = hubContext;
 | 
			
		||||
        _userManager = userManager;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Optional<Peripherals.JtagClient.Jtag> GetJtagClient(string userName)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            using var db = new Database.AppDataConnection();
 | 
			
		||||
            var board = db.GetBoardByUserName(userName);
 | 
			
		||||
            var board = _userManager.GetBoardByUserName(userName);
 | 
			
		||||
            if (!board.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"Find Board {board.Value.Value.ID} failed because {board.Error}");
 | 
			
		||||
@@ -97,7 +99,7 @@ public class JtagHub : Hub<IJtagReceiver>, IJtagHub
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await SetBoundaryScanFreq(freq);
 | 
			
		||||
            SetBoundaryScanFreq(freq);
 | 
			
		||||
            var cts = new CancellationTokenSource();
 | 
			
		||||
            CancellationTokenSourceTable.AddOrUpdate(userName, cts, (key, value) => cts);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,15 +15,23 @@ public class HdmiVideoStreamEndpoint
 | 
			
		||||
public class HttpHdmiVideoStreamService : BackgroundService
 | 
			
		||||
{
 | 
			
		||||
    private readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
    private readonly Database.UserManager _userManager;
 | 
			
		||||
 | 
			
		||||
    private HttpListener? _httpListener;
 | 
			
		||||
    private readonly int _serverPort = 4322;
 | 
			
		||||
    private readonly ConcurrentDictionary<string, HdmiIn> _hdmiInDict = new();
 | 
			
		||||
    private readonly ConcurrentDictionary<string, CancellationTokenSource> _hdmiInCtsDict = new();
 | 
			
		||||
 | 
			
		||||
    public HttpHdmiVideoStreamService(Database.UserManager userManager)
 | 
			
		||||
    {
 | 
			
		||||
        _userManager = userManager;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public override async Task StartAsync(CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        _httpListener = new HttpListener();
 | 
			
		||||
        _httpListener.Prefixes.Add($"http://{Global.localhost}:{_serverPort}/");
 | 
			
		||||
        _httpListener.Prefixes.Add($"http://{Global.LocalHost}:{_serverPort}/");
 | 
			
		||||
        _httpListener.Start();
 | 
			
		||||
        logger.Info($"HDMI Video Stream Service started on port {_serverPort}");
 | 
			
		||||
 | 
			
		||||
@@ -133,14 +141,7 @@ public class HttpHdmiVideoStreamService : BackgroundService
 | 
			
		||||
            return hdmiIn;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var db = new Database.AppDataConnection();
 | 
			
		||||
        if (db == null)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error("Failed to create HdmiIn instance");
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var boardRet = db.GetBoardByID(Guid.Parse(boardId));
 | 
			
		||||
        var boardRet = _userManager.GetBoardByID(Guid.Parse(boardId));
 | 
			
		||||
        if (!boardRet.IsSuccessful || !boardRet.Value.HasValue)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"Failed to get board with ID {boardId}");
 | 
			
		||||
@@ -366,8 +367,7 @@ public class HttpHdmiVideoStreamService : BackgroundService
 | 
			
		||||
    /// <returns>返回所有可用的HDMI视频流终端点列表</returns>
 | 
			
		||||
    public List<HdmiVideoStreamEndpoint>? GetAllVideoEndpoints()
 | 
			
		||||
    {
 | 
			
		||||
        var db = new Database.AppDataConnection();
 | 
			
		||||
        var boards = db?.GetAllBoard();
 | 
			
		||||
        var boards = _userManager.GetAllBoard();
 | 
			
		||||
        if (boards == null)
 | 
			
		||||
            return null;
 | 
			
		||||
 | 
			
		||||
@@ -377,9 +377,9 @@ public class HttpHdmiVideoStreamService : BackgroundService
 | 
			
		||||
            endpoints.Add(new HdmiVideoStreamEndpoint
 | 
			
		||||
            {
 | 
			
		||||
                BoardId = board.ID.ToString(),
 | 
			
		||||
                MjpegUrl = $"http://{Global.localhost}:{_serverPort}/mjpeg?boardId={board.ID}",
 | 
			
		||||
                VideoUrl = $"http://{Global.localhost}:{_serverPort}/video?boardId={board.ID}",
 | 
			
		||||
                SnapshotUrl = $"http://{Global.localhost}:{_serverPort}/snapshot?boardId={board.ID}"
 | 
			
		||||
                MjpegUrl = $"http://{Global.LocalHost}:{_serverPort}/mjpeg?boardId={board.ID}",
 | 
			
		||||
                VideoUrl = $"http://{Global.LocalHost}:{_serverPort}/video?boardId={board.ID}",
 | 
			
		||||
                SnapshotUrl = $"http://{Global.LocalHost}:{_serverPort}/snapshot?boardId={board.ID}"
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        return endpoints;
 | 
			
		||||
@@ -395,9 +395,9 @@ public class HttpHdmiVideoStreamService : BackgroundService
 | 
			
		||||
        return new HdmiVideoStreamEndpoint
 | 
			
		||||
        {
 | 
			
		||||
            BoardId = boardId,
 | 
			
		||||
            MjpegUrl = $"http://{Global.localhost}:{_serverPort}/mjpeg?boardId={boardId}",
 | 
			
		||||
            VideoUrl = $"http://{Global.localhost}:{_serverPort}/video?boardId={boardId}",
 | 
			
		||||
            SnapshotUrl = $"http://{Global.localhost}:{_serverPort}/snapshot?boardId={boardId}"
 | 
			
		||||
            MjpegUrl = $"http://{Global.LocalHost}:{_serverPort}/mjpeg?boardId={boardId}",
 | 
			
		||||
            VideoUrl = $"http://{Global.LocalHost}:{_serverPort}/video?boardId={boardId}",
 | 
			
		||||
            SnapshotUrl = $"http://{Global.LocalHost}:{_serverPort}/snapshot?boardId={boardId}"
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -87,6 +87,8 @@ public class HttpVideoStreamService : BackgroundService
 | 
			
		||||
{
 | 
			
		||||
    private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
    private readonly Database.UserManager _userManager;
 | 
			
		||||
 | 
			
		||||
    private HttpListener? _httpListener;
 | 
			
		||||
    private readonly int _serverPort = 4321;
 | 
			
		||||
 | 
			
		||||
@@ -99,13 +101,18 @@ public class HttpVideoStreamService : BackgroundService
 | 
			
		||||
    private readonly object _usbCameraLock = new object();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    public HttpVideoStreamService(Database.UserManager userManager)
 | 
			
		||||
    {
 | 
			
		||||
        _userManager = userManager;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 初始化 HttpVideoStreamService
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public override async Task StartAsync(CancellationToken cancellationToken)
 | 
			
		||||
    {
 | 
			
		||||
        _httpListener = new HttpListener();
 | 
			
		||||
        _httpListener.Prefixes.Add($"http://{Global.localhost}:{_serverPort}/");
 | 
			
		||||
        _httpListener.Prefixes.Add($"http://{Global.LocalHost}:{_serverPort}/");
 | 
			
		||||
        _httpListener.Start();
 | 
			
		||||
        logger.Info($"Video Stream Service started on port {_serverPort}");
 | 
			
		||||
 | 
			
		||||
@@ -147,14 +154,7 @@ public class HttpVideoStreamService : BackgroundService
 | 
			
		||||
            return client;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var db = new Database.AppDataConnection();
 | 
			
		||||
        if (db == null)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error("Failed to create HdmiIn instance");
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var boardRet = db.GetBoardByID(Guid.Parse(boardId));
 | 
			
		||||
        var boardRet = _userManager.GetBoardByID(Guid.Parse(boardId));
 | 
			
		||||
        if (!boardRet.IsSuccessful || !boardRet.Value.HasValue)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"Failed to get board with ID {boardId}");
 | 
			
		||||
@@ -675,9 +675,9 @@ public class HttpVideoStreamService : BackgroundService
 | 
			
		||||
        return new VideoEndpoint
 | 
			
		||||
        {
 | 
			
		||||
            BoardId = boardId,
 | 
			
		||||
            MjpegUrl = $"http://{Global.localhost}:{_serverPort}/mjpeg?boardId={boardId}",
 | 
			
		||||
            VideoUrl = $"http://{Global.localhost}:{_serverPort}/video?boardId={boardId}",
 | 
			
		||||
            SnapshotUrl = $"http://{Global.localhost}:{_serverPort}/snapshot?boardId={boardId}",
 | 
			
		||||
            MjpegUrl = $"http://{Global.LocalHost}:{_serverPort}/mjpeg?boardId={boardId}",
 | 
			
		||||
            VideoUrl = $"http://{Global.LocalHost}:{_serverPort}/video?boardId={boardId}",
 | 
			
		||||
            SnapshotUrl = $"http://{Global.LocalHost}:{_serverPort}/snapshot?boardId={boardId}",
 | 
			
		||||
            Resolution = $"{client.FrameWidth}x{client.FrameHeight}",
 | 
			
		||||
            FrameRate = client.FrameRate
 | 
			
		||||
        };
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user