feat: 完成提交作业的后端

This commit is contained in:
2025-08-14 11:38:09 +08:00
parent 24622d30cf
commit e5dac3e731
13 changed files with 808 additions and 203 deletions

View File

@@ -440,7 +440,7 @@ public class DataController : ControllerBase
[ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public IActionResult UpdateBoardStatus(Guid boardId, Database.Board.BoardStatus newStatus)
public IActionResult UpdateBoardStatus(Guid boardId, Database.BoardStatus newStatus)
{
if (boardId == Guid.Empty)
return BadRequest("板子Guid不能为空");
@@ -456,6 +456,24 @@ public class DataController : ControllerBase
}
}
[HttpPost("AddEmptyBoard")]
[EnableCors("Development")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public IActionResult AddEmptyBoard()
{
try
{
var boardId = _userManager.AddBoard("Test");
var result = _userManager.UpdateBoardStatus(boardId, Database.BoardStatus.Available);
return Ok();
}
catch (Exception ex)
{
logger.Error(ex, "新增板子时发生异常");
return StatusCode(StatusCodes.Status500InternalServerError, "新增失败,请稍后重试");
}
}
/// <summary>
/// [TODO:description]

View File

@@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
using DotNext;
using Database;
namespace server.Controllers;
@@ -14,11 +15,18 @@ public class ExamController : ControllerBase
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private readonly Database.ExamManager _examManager;
private readonly ExamManager _examManager;
private readonly ResourceManager _resourceManager;
private readonly UserManager _userManager;
public ExamController(Database.ExamManager examManager)
public ExamController(
ExamManager examManager,
ResourceManager resourceManager,
UserManager userManager)
{
_examManager = examManager;
_resourceManager = resourceManager;
_userManager = userManager;
}
/// <summary>
@@ -211,6 +219,222 @@ public class ExamController : ControllerBase
return StatusCode(StatusCodes.Status500InternalServerError, $"更新实验失败: {ex.Message}");
}
}
/// <summary>
/// 提交作业
/// </summary>
/// <param name="examId">实验ID</param>
/// <param name="file">提交的文件</param>
/// <returns>提交结果</returns>
[Authorize]
[HttpPost("commit/{examId}")]
[EnableCors("Users")]
[ProducesResponseType(typeof(Resource), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> SubmitHomework(string examId, IFormFile file)
{
if (string.IsNullOrWhiteSpace(examId))
return BadRequest("实验ID不能为空");
if (file == null || file.Length == 0)
return BadRequest("文件不能为空");
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 examResult = _examManager.GetExamByID(examId);
if (!examResult.IsSuccessful)
{
logger.Error($"检查实验是否存在时出错: {examResult.Error.Message}");
return StatusCode(StatusCodes.Status500InternalServerError, $"检查实验失败: {examResult.Error.Message}");
}
if (!examResult.Value.HasValue)
{
logger.Warn($"实验不存在: {examId}");
return NotFound($"实验 {examId} 不存在");
}
// 读取文件内容
byte[] fileData;
using (var memoryStream = new MemoryStream())
{
await file.CopyToAsync(memoryStream);
fileData = memoryStream.ToArray();
}
// 提交作业
var commitResult = _resourceManager.AddResource(
user.ID, ResourceTypes.Compression, ResourcePurpose.Homework,
file.FileName, fileData, examId);
if (!commitResult.IsSuccessful)
{
logger.Error($"提交作业时出错: {commitResult.Error.Message}");
return StatusCode(StatusCodes.Status500InternalServerError, $"提交作业失败: {commitResult.Error.Message}");
}
var commit = commitResult.Value;
logger.Info($"用户 {userName} 成功提交实验 {examId} 的作业Commit ID: {commit.ID}");
return CreatedAtAction(nameof(GetCommitsByExamId), new { examId = examId }, commit);
}
catch (Exception ex)
{
logger.Error($"提交实验 {examId} 作业时出错: {ex.Message}");
return StatusCode(StatusCodes.Status500InternalServerError, $"提交作业失败: {ex.Message}");
}
}
/// <summary>
/// 获取用户在指定实验中的提交记录
/// </summary>
/// <param name="examId">实验ID</param>
/// <returns>提交记录列表</returns>
[Authorize]
[HttpGet("commits/{examId}")]
[EnableCors("Users")]
[ProducesResponseType(typeof(Resource[]), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public IActionResult GetCommitsByExamId(string examId)
{
if (string.IsNullOrWhiteSpace(examId))
return BadRequest("实验ID不能为空");
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 examResult = _examManager.GetExamByID(examId);
if (!examResult.IsSuccessful)
{
logger.Error($"检查实验是否存在时出错: {examResult.Error.Message}");
return StatusCode(StatusCodes.Status500InternalServerError, $"检查实验失败: {examResult.Error.Message}");
}
if (!examResult.Value.HasValue)
{
logger.Warn($"实验不存在: {examId}");
return NotFound($"实验 {examId} 不存在");
}
// 获取用户的提交记录
var commitsResult = _resourceManager.GetResourceListByType(
ResourceTypes.Compression, ResourcePurpose.Homework, examId);
if (!commitsResult.IsSuccessful)
{
logger.Error($"获取提交记录时出错: {commitsResult.Error.Message}");
return StatusCode(StatusCodes.Status500InternalServerError, $"获取提交记录失败: {commitsResult.Error.Message}");
}
var commits = commitsResult.Value;
logger.Info($"成功获取用户 {userName} 在实验 {examId} 中的提交记录,共 {commits.Length} 条");
return Ok(commits);
}
catch (Exception ex)
{
logger.Error($"获取实验 {examId} 提交记录时出错: {ex.Message}");
return StatusCode(StatusCodes.Status500InternalServerError, $"获取提交记录失败: {ex.Message}");
}
}
/// <summary>
/// 删除提交记录
/// </summary>
/// <param name="commitId">提交记录ID</param>
/// <returns>删除结果</returns>
[Authorize]
[HttpDelete("commit/{commitId}")]
[EnableCors("Users")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public IActionResult DeleteCommit(string commitId)
{
if (!Guid.TryParse(commitId, out _))
return BadRequest("提交记录ID格式不正确");
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 isAdmin = user.Permission == UserPermission.Admin;
// 如果不是管理员,检查提交记录是否属于当前用户
if (!isAdmin)
{
var commitResult = _resourceManager.GetResourceById(commitId);
if (!commitResult.HasValue)
{
logger.Warn($"提交记录不存在: {commitId}");
return NotFound($"提交记录 {commitId} 不存在");
}
var commit = commitResult.Value;
if (commit.UserID != user.ID)
{
logger.Warn($"用户 {userName} 尝试删除不属于自己的提交记录: {commitId}");
return Forbid("您只能删除自己的提交记录");
}
}
// 执行删除
var deleteResult = _resourceManager.DeleteResource(commitId);
if (!deleteResult)
{
logger.Warn($"提交记录不存在: {commitId}");
return NotFound($"提交记录 {commitId} 不存在");
}
logger.Info($"用户 {userName} 成功删除提交记录: {commitId}");
return Ok($"提交记录 {commitId} 已成功删除");
}
catch (Exception ex)
{
logger.Error($"删除提交记录 {commitId} 时出错: {ex.Message}");
return StatusCode(StatusCodes.Status500InternalServerError, $"删除提交记录失败: {ex.Message}");
}
}
}
/// <summary>
@@ -258,7 +482,7 @@ public class ExamInfo
/// </summary>
public bool IsVisibleToUsers { get; set; } = true;
public ExamInfo(Database.Exam exam)
public ExamInfo(Exam exam)
{
ID = exam.ID;
Name = exam.Name;

View File

@@ -140,7 +140,7 @@ public class JtagController : ControllerBase
[ProducesResponseType(typeof(string), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public IResult DownloadBitstream(string address, int port, int bitstreamId, CancellationToken cancelToken)
public IResult DownloadBitstream(string address, int port, string bitstreamId, CancellationToken cancelToken)
{
logger.Info($"User {User.Identity?.Name} initiating bitstream download to device {address}:{port} using bitstream ID: {bitstreamId}");

View File

@@ -40,15 +40,15 @@ public class ResourceController : ControllerBase
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> AddResource([FromForm] AddResourceRequest request, IFormFile file)
{
if (string.IsNullOrWhiteSpace(request.ResourceType) || string.IsNullOrWhiteSpace(request.ResourcePurpose) || file == null)
if (string.IsNullOrWhiteSpace(request.ResourceType) || file == null)
return BadRequest("资源类型、资源用途和文件不能为空");
// 验证资源用途
if (request.ResourcePurpose != Resource.ResourcePurposes.Template && request.ResourcePurpose != Resource.ResourcePurposes.User)
if (request.ResourcePurpose != ResourcePurpose.Template && request.ResourcePurpose != ResourcePurpose.User)
return BadRequest($"无效的资源用途: {request.ResourcePurpose}");
// 模板资源需要管理员权限
if (request.ResourcePurpose == Resource.ResourcePurposes.Template && !User.IsInRole("Admin"))
if (request.ResourcePurpose == ResourcePurpose.Template && !User.IsInRole("Admin"))
return Forbid("只有管理员可以添加模板资源");
try
@@ -85,10 +85,10 @@ public class ResourceController : ControllerBase
var resource = result.Value;
var resourceInfo = new ResourceInfo
{
ID = resource.ID,
ID = resource.ID.ToString(),
Name = resource.ResourceName,
Type = resource.ResourceType,
Purpose = resource.ResourcePurpose,
Purpose = resource.Purpose,
UploadTime = resource.UploadTime,
ExamID = resource.ExamID,
MimeType = resource.MimeType
@@ -117,7 +117,10 @@ public class ResourceController : ControllerBase
[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)
public IActionResult GetResourceList(
[FromQuery] string? examId = null,
[FromQuery] string? resourceType = null,
[FromQuery] ResourcePurpose? resourcePurpose = null)
{
try
{
@@ -132,52 +135,53 @@ public class ResourceController : ControllerBase
var user = userResult.Value.Value;
// 普通用户只能查看自己的资源和模板资源
Guid? userId = null;
if (!User.IsInRole("Admin"))
Result<List<Resource>> result;
// 管理员
if (user.Permission == UserPermission.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);
}
result = _resourceManager.GetFullResourceList(examId, resourceType, resourcePurpose);
}
// 用户
else if (resourcePurpose == ResourcePurpose.User)
{
result = _resourceManager.GetFullResourceList(examId, resourceType, resourcePurpose, user.ID);
}
// 模板
else if (resourcePurpose == ResourcePurpose.Template)
{
result = _resourceManager.GetFullResourceList(examId, resourceType, resourcePurpose);
}
// 其他
else
{
// 这种情况下需要分别查询并合并结果
var userResourcesResult = _resourceManager.GetFullResourceList(
examId, resourceType, ResourcePurpose.User, user.ID);
var templateResourcesResult = _resourceManager.GetFullResourceList(
examId, resourceType, ResourcePurpose.Template, null);
var result = _resourceManager.GetFullResourceList(examId, resourceType, resourcePurpose, userId);
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.ToString(),
Name = r.ResourceName,
Type = r.ResourceType,
Purpose = r.Purpose,
UploadTime = r.UploadTime,
ExamID = r.ExamID,
MimeType = r.MimeType
}).ToArray();
logger.Info($"成功获取资源列表,共 {mergedResourceInfos.Length} 个资源");
return Ok(mergedResourceInfos);
}
if (!result.IsSuccessful)
{
@@ -187,10 +191,10 @@ public class ResourceController : ControllerBase
var resources = result.Value.Select(r => new ResourceInfo
{
ID = r.ID,
ID = r.ID.ToString(),
Name = r.ResourceName,
Type = r.ResourceType,
Purpose = r.ResourcePurpose,
Purpose = r.Purpose,
UploadTime = r.UploadTime,
ExamID = r.ExamID,
MimeType = r.MimeType
@@ -217,7 +221,7 @@ public class ResourceController : ControllerBase
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public IActionResult GetResourceById(int resourceId)
public IActionResult GetResourceById(string resourceId)
{
try
{
@@ -267,7 +271,7 @@ public class ResourceController : ControllerBase
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public IActionResult DeleteResource(int resourceId)
public IActionResult DeleteResource(string resourceId)
{
try
{
@@ -301,7 +305,7 @@ public class ResourceController : ControllerBase
// 权限检查:管理员可以删除所有资源,普通用户只能删除自己的用户资源
if (!User.IsInRole("Admin"))
{
if (resource.ResourcePurpose == Resource.ResourcePurposes.Template)
if (resource.Purpose == ResourcePurpose.Template)
return Forbid("普通用户不能删除模板资源");
if (resource.UserID != user.ID)
@@ -333,7 +337,7 @@ public class ResourceController : ControllerBase
/// <summary>
/// 资源ID
/// </summary>
public int ID { get; set; }
public required string ID { get; set; }
/// <summary>
/// 资源名称
@@ -348,7 +352,7 @@ public class ResourceController : ControllerBase
/// <summary>
/// 资源用途template/user
/// </summary>
public required string Purpose { get; set; }
public required ResourcePurpose Purpose { get; set; }
/// <summary>
/// 上传时间
@@ -379,7 +383,7 @@ public class ResourceController : ControllerBase
/// <summary>
/// 资源用途template/user
/// </summary>
public required string ResourcePurpose { get; set; }
public required ResourcePurpose ResourcePurpose { get; set; }
/// <summary>
/// 所属实验ID可选