using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; using DotNext; namespace server.Controllers; /// /// 实验控制器 /// [ApiController] [Route("api/[controller]")] public class ExamController : ControllerBase { private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); /// /// 实验信息类 /// public class ExamInfo { /// /// 实验的唯一标识符 /// public required string ID { get; set; } /// /// 实验文档内容(Markdown格式) /// public required string DocContent { get; set; } /// /// 实验创建时间 /// public DateTime CreatedTime { get; set; } /// /// 实验最后更新时间 /// public DateTime UpdatedTime { get; set; } } /// /// 实验简要信息类(用于列表显示) /// public class ExamSummary { /// /// 实验的唯一标识符 /// public required string ID { get; set; } /// /// 实验创建时间 /// public DateTime CreatedTime { get; set; } /// /// 实验最后更新时间 /// public DateTime UpdatedTime { get; set; } /// /// 实验标题(从文档内容中提取) /// public string Title { get; set; } = ""; } /// /// 扫描结果类 /// public class ScanResult { /// /// 结果消息 /// public required string Message { get; set; } /// /// 更新的实验数量 /// public int UpdateCount { get; set; } } /// /// 获取所有实验列表 /// /// 实验列表 [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, CreatedTime = exam.CreatedTime, UpdatedTime = exam.UpdatedTime, Title = ExtractTitleFromMarkdown(exam.DocContent) }).ToArray(); logger.Info($"成功获取实验列表,共 {examSummaries.Length} 个实验"); return Ok(examSummaries); } catch (Exception ex) { logger.Error($"获取实验列表时出错: {ex.Message}"); return StatusCode(StatusCodes.Status500InternalServerError, $"获取实验列表失败: {ex.Message}"); } } /// /// 根据实验ID获取实验详细信息 /// /// 实验ID /// 实验详细信息 [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, DocContent = exam.DocContent, CreatedTime = exam.CreatedTime, UpdatedTime = exam.UpdatedTime }; logger.Info($"成功获取实验信息: {examId}"); return Ok(examInfo); } catch (Exception ex) { logger.Error($"获取实验 {examId} 时出错: {ex.Message}"); return StatusCode(StatusCodes.Status500InternalServerError, $"获取实验失败: {ex.Message}"); } } /// /// 重新扫描实验文件夹并更新数据库 /// /// 更新结果 [Authorize("Admin")] [HttpPost("scan")] [EnableCors("Users")] [ProducesResponseType(typeof(ScanResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public IActionResult ScanExams() { try { using var db = new Database.AppDataConnection(); var examFolderPath = Path.Combine(Directory.GetCurrentDirectory(), "exam"); var updateCount = db.ScanAndUpdateExams(examFolderPath); var result = new ScanResult { Message = $"扫描完成,更新了 {updateCount} 个实验", UpdateCount = updateCount }; logger.Info($"手动扫描实验完成,更新了 {updateCount} 个实验"); return Ok(result); } catch (Exception ex) { logger.Error($"扫描实验时出错: {ex.Message}"); return StatusCode(StatusCodes.Status500InternalServerError, $"扫描实验失败: {ex.Message}"); } } /// /// 从 Markdown 内容中提取标题 /// /// Markdown 内容 /// 提取的标题 private static string ExtractTitleFromMarkdown(string markdownContent) { if (string.IsNullOrEmpty(markdownContent)) return ""; var lines = markdownContent.Split('\n'); foreach (var line in lines) { var trimmedLine = line.Trim(); if (trimmedLine.StartsWith("# ")) { return trimmedLine.Substring(2).Trim(); } } return ""; } }