exam_service
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using AutoMapper;
|
||||
using AutoMapper.Internal.Mappers;
|
||||
using Entities.Contracts;
|
||||
using Entities.DTO;
|
||||
|
||||
@@ -6,6 +7,19 @@ namespace TechHelper.Context
|
||||
{
|
||||
public class AutoMapperProFile : Profile
|
||||
{
|
||||
|
||||
public static class EnumMappingHelpers
|
||||
{
|
||||
public static TEnum ParseEnumSafe<TEnum>(string sourceString, TEnum defaultValue) where TEnum : struct, Enum
|
||||
{
|
||||
if (Enum.TryParse(sourceString, true, out TEnum parsedValue))
|
||||
{
|
||||
return parsedValue;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
public AutoMapperProFile()
|
||||
{
|
||||
CreateMap<UserForRegistrationDto, User>()
|
||||
@@ -24,9 +38,9 @@ namespace TechHelper.Context
|
||||
.ForMember(dest => dest.Id, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.QuestionText, opt => opt.MapFrom(src => src.Stem))
|
||||
.ForMember(dest => dest.CorrectAnswer, opt => opt.MapFrom(src => src.SampleAnswer))
|
||||
.ForMember(dest => dest.QuestionType, opt => opt.MapFrom(src => Enum.Parse<QuestionType>(src.QuestionType, true)))
|
||||
.ForMember(dest => dest.DifficultyLevel, opt => opt.MapFrom(src => Enum.Parse<DifficultyLevel>(src.DifficultyLevel, true)))
|
||||
.ForMember(dest => dest.SubjectArea, opt => opt.Ignore()) // SubjectArea 来自 Assignment 而不是 SubQuestionDto
|
||||
.ForMember(dest => dest.QuestionType, opt => opt.MapFrom(src => EnumMappingHelpers.ParseEnumSafe(src.QuestionType, QuestionType.Unknown)))
|
||||
.ForMember(dest => dest.DifficultyLevel, opt => opt.MapFrom(src => EnumMappingHelpers.ParseEnumSafe(src.DifficultyLevel, DifficultyLevel.easy)))
|
||||
.ForMember(dest => dest.SubjectArea, opt => opt.MapFrom(src => EnumMappingHelpers.ParseEnumSafe(src.DifficultyLevel, SubjectAreaEnum.Unknown)))
|
||||
.ForMember(dest => dest.CreatedBy, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.CreatedAt, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.UpdatedAt, opt => opt.Ignore())
|
||||
@@ -40,6 +54,13 @@ namespace TechHelper.Context
|
||||
.ForMember(dest => dest.QuestionType, opt => opt.MapFrom(src => src.QuestionType.ToString()))
|
||||
.ForMember(dest => dest.DifficultyLevel, opt => opt.MapFrom(src => src.DifficultyLevel.ToString()))
|
||||
.ForMember(dest => dest.Options, opt => opt.Ignore()); // Options 需要单独处理
|
||||
|
||||
CreateMap<Assignment, ExamDto>()
|
||||
.ForMember(dest => dest.AssignmentTitle, opt => opt.MapFrom(src => src.Title))
|
||||
.ForMember(dest => dest.Description, opt => opt.MapFrom(src => src.Description))
|
||||
.ForMember(dest => dest.AssignmentId, opt => opt.MapFrom(src=> src.Id))
|
||||
.ForMember(dest => dest.SubjectArea, opt => opt.MapFrom(src => src.SubjectArea.ToString()));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -25,8 +25,7 @@ namespace TechHelper.Context.Configuration
|
||||
|
||||
// 配置 AssignmentId 属性对应的数据库列名为 "assignment",并设置为必需字段。
|
||||
builder.Property(ag => ag.AssignmentId)
|
||||
.HasColumnName("assignment")
|
||||
.IsRequired();
|
||||
.HasColumnName("assignment");
|
||||
|
||||
// 配置 Title 属性对应的数据库列名为 "title",设置为必需字段,并设置最大长度。
|
||||
builder.Property(ag => ag.Title)
|
||||
@@ -52,7 +51,8 @@ namespace TechHelper.Context.Configuration
|
||||
// 配置 ParentGroup 属性对应的数据库列名为 "sub_group"。
|
||||
// ParentGroup 是 Guid? 类型,默认就是可选的,无需 IsRequired(false)。
|
||||
builder.Property(ag => ag.ParentGroup)
|
||||
.HasColumnName("sub_group");
|
||||
.HasColumnName("parent_group")
|
||||
.IsRequired(false);
|
||||
|
||||
// 配置 IsDeleted 属性对应的数据库列名为 "deleted",并设置默认值为 false。
|
||||
builder.Property(ag => ag.IsDeleted)
|
||||
|
@@ -41,7 +41,7 @@ namespace TechHelper.Context.Configuration
|
||||
// 配置 AssignmentGroupId 列
|
||||
// 该列在数据库中名为 "detail_id"
|
||||
builder.Property(aq => aq.AssignmentGroupId)
|
||||
.HasColumnName("detail_id")
|
||||
.HasColumnName("group_id")
|
||||
.IsRequired();
|
||||
|
||||
// 配置 IsDeleted 列
|
||||
|
74
TechHelper.Server/Controllers/ExamController.cs
Normal file
74
TechHelper.Server/Controllers/ExamController.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using Entities.Contracts;
|
||||
using Entities.DTO;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using TechHelper.Server.Services;
|
||||
using System.Security.Claims;
|
||||
|
||||
|
||||
namespace TechHelper.Server.Controllers
|
||||
{
|
||||
[Route("api/exam")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
|
||||
|
||||
public class ExamController : ControllerBase
|
||||
{
|
||||
private IExamService _examService;
|
||||
private readonly UserManager<User> _userManager;
|
||||
|
||||
public ExamController(IExamService examService, UserManager<User> userManager)
|
||||
{
|
||||
_examService = examService;
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
[HttpPost("add")]
|
||||
public async Task<IActionResult> AddExam(
|
||||
[FromBody] ExamDto examDto)
|
||||
{
|
||||
var result = await _examService.AddAsync(examDto);
|
||||
if (result.Status)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("get")]
|
||||
public async Task<IActionResult> GetExamById(Guid id)
|
||||
{
|
||||
|
||||
var result = await _examService.GetAsync(id);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("getAllPreview")]
|
||||
public async Task<IActionResult> GetAllExamPreview(string user)
|
||||
{
|
||||
string? userId = User.Identity.Name;
|
||||
|
||||
var userid = await _userManager.FindByEmailAsync(user);
|
||||
if (userid == null) return BadRequest("用户验证失败, 无效用户");
|
||||
|
||||
|
||||
|
||||
var result = await _examService.GetAllExamPreview(userid.Id);
|
||||
|
||||
if (result.Status)
|
||||
{
|
||||
return Ok(result.Result);
|
||||
}
|
||||
return BadRequest(result);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -12,7 +12,7 @@ using TechHelper.Context;
|
||||
namespace TechHelper.Server.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationContext))]
|
||||
[Migration("20250528090233_init")]
|
||||
[Migration("20250610025325_init")]
|
||||
partial class init
|
||||
{
|
||||
/// <inheritdoc />
|
||||
@@ -158,7 +158,8 @@ namespace TechHelper.Server.Migrations
|
||||
.HasColumnType("char(36)")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AssignmentId")
|
||||
b.Property<Guid?>("AssignmentId")
|
||||
.IsRequired()
|
||||
.HasColumnType("char(36)")
|
||||
.HasColumnName("assignment");
|
||||
|
||||
@@ -180,7 +181,7 @@ namespace TechHelper.Server.Migrations
|
||||
|
||||
b.Property<Guid?>("ParentGroup")
|
||||
.HasColumnType("char(36)")
|
||||
.HasColumnName("sub_group");
|
||||
.HasColumnName("parent_group");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
@@ -188,10 +189,14 @@ namespace TechHelper.Server.Migrations
|
||||
.HasColumnType("longtext")
|
||||
.HasColumnName("title");
|
||||
|
||||
b.Property<decimal?>("TotalPoints")
|
||||
.HasColumnType("decimal(65,30)")
|
||||
b.Property<float?>("TotalPoints")
|
||||
.HasColumnType("float")
|
||||
.HasColumnName("total_points");
|
||||
|
||||
b.Property<bool>("ValidQuestionGroup")
|
||||
.HasColumnType("tinyint(1)")
|
||||
.HasColumnName("valid_question_group");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AssignmentId");
|
||||
@@ -210,7 +215,7 @@ namespace TechHelper.Server.Migrations
|
||||
|
||||
b.Property<Guid>("AssignmentGroupId")
|
||||
.HasColumnType("char(36)")
|
||||
.HasColumnName("detail_id");
|
||||
.HasColumnName("group_id");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
@@ -404,6 +409,10 @@ namespace TechHelper.Server.Migrations
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.Property<bool>("ValidQuestion")
|
||||
.HasColumnType("tinyint(1)")
|
||||
.HasColumnName("valid_question");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedBy");
|
||||
@@ -653,19 +662,19 @@ namespace TechHelper.Server.Migrations
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = new Guid("ea0c88d8-1a52-4034-bb37-5a95043821eb"),
|
||||
Id = new Guid("9e526681-e57e-46b5-a01c-5731b27bfc4a"),
|
||||
Name = "Student",
|
||||
NormalizedName = "STUDENT"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("9de22e41-c096-4d5a-b55a-ce0122aa3ada"),
|
||||
Id = new Guid("dfdfb884-4063-4161-84e0-9c225f4e883c"),
|
||||
Name = "Teacher",
|
||||
NormalizedName = "TEACHER"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("dee718d9-b731-485f-96bb-a59ce777870f"),
|
||||
Id = new Guid("02a808ba-bd16-4f90-bf2b-0bc42f767e00"),
|
||||
Name = "Administrator",
|
||||
NormalizedName = "ADMINISTRATOR"
|
||||
});
|
@@ -281,7 +281,8 @@ namespace TechHelper.Server.Migrations
|
||||
created_at = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
updated_at = table.Column<DateTime>(type: "datetime(6)", rowVersion: true, nullable: false),
|
||||
deleted = table.Column<bool>(type: "tinyint(1)", nullable: false, defaultValue: false)
|
||||
deleted = table.Column<bool>(type: "tinyint(1)", nullable: false, defaultValue: false),
|
||||
valid_question = table.Column<bool>(type: "tinyint(1)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
@@ -330,17 +331,18 @@ namespace TechHelper.Server.Migrations
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
descript = table.Column<string>(type: "longtext", maxLength: 65535, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
total_points = table.Column<decimal>(type: "decimal(65,30)", nullable: true),
|
||||
total_points = table.Column<float>(type: "float", nullable: true),
|
||||
number = table.Column<byte>(type: "tinyint unsigned", nullable: false),
|
||||
sub_group = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
|
||||
deleted = table.Column<bool>(type: "tinyint(1)", nullable: false, defaultValue: false)
|
||||
parent_group = table.Column<Guid>(type: "char(36)", nullable: true, collation: "ascii_general_ci"),
|
||||
deleted = table.Column<bool>(type: "tinyint(1)", nullable: false, defaultValue: false),
|
||||
valid_question_group = table.Column<bool>(type: "tinyint(1)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_assignment_group", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "FK_assignment_group_assignment_group_sub_group",
|
||||
column: x => x.sub_group,
|
||||
name: "FK_assignment_group_assignment_group_parent_group",
|
||||
column: x => x.parent_group,
|
||||
principalTable: "assignment_group",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
@@ -482,18 +484,18 @@ namespace TechHelper.Server.Migrations
|
||||
{
|
||||
id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
question_id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
group_id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
question_number = table.Column<byte>(type: "tinyint unsigned", nullable: false),
|
||||
created_at = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
score = table.Column<float>(type: "float", nullable: true),
|
||||
detail_id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
deleted = table.Column<bool>(type: "tinyint(1)", nullable: false, defaultValue: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_assignment_questions", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "FK_assignment_questions_assignment_group_detail_id",
|
||||
column: x => x.detail_id,
|
||||
name: "FK_assignment_questions_assignment_group_group_id",
|
||||
column: x => x.group_id,
|
||||
principalTable: "assignment_group",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
@@ -554,9 +556,9 @@ namespace TechHelper.Server.Migrations
|
||||
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ new Guid("9de22e41-c096-4d5a-b55a-ce0122aa3ada"), null, "Teacher", "TEACHER" },
|
||||
{ new Guid("dee718d9-b731-485f-96bb-a59ce777870f"), null, "Administrator", "ADMINISTRATOR" },
|
||||
{ new Guid("ea0c88d8-1a52-4034-bb37-5a95043821eb"), null, "Student", "STUDENT" }
|
||||
{ new Guid("02a808ba-bd16-4f90-bf2b-0bc42f767e00"), null, "Administrator", "ADMINISTRATOR" },
|
||||
{ new Guid("9e526681-e57e-46b5-a01c-5731b27bfc4a"), null, "Student", "STUDENT" },
|
||||
{ new Guid("dfdfb884-4063-4161-84e0-9c225f4e883c"), null, "Teacher", "TEACHER" }
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
@@ -612,14 +614,14 @@ namespace TechHelper.Server.Migrations
|
||||
column: "assignment");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_assignment_group_sub_group",
|
||||
name: "IX_assignment_group_parent_group",
|
||||
table: "assignment_group",
|
||||
column: "sub_group");
|
||||
column: "parent_group");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_assignment_questions_detail_id",
|
||||
name: "IX_assignment_questions_group_id",
|
||||
table: "assignment_questions",
|
||||
column: "detail_id");
|
||||
column: "group_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_assignment_questions_question_id",
|
1091
TechHelper.Server/Migrations/20250610025810_assignment-not_required.Designer.cs
generated
Normal file
1091
TechHelper.Server/Migrations/20250610025810_assignment-not_required.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
|
||||
|
||||
namespace TechHelper.Server.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class assignmentnot_required : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DeleteData(
|
||||
table: "AspNetRoles",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("02a808ba-bd16-4f90-bf2b-0bc42f767e00"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "AspNetRoles",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("9e526681-e57e-46b5-a01c-5731b27bfc4a"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "AspNetRoles",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("dfdfb884-4063-4161-84e0-9c225f4e883c"));
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "assignment",
|
||||
table: "assignment_group",
|
||||
type: "char(36)",
|
||||
nullable: true,
|
||||
collation: "ascii_general_ci",
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "char(36)")
|
||||
.OldAnnotation("Relational:Collation", "ascii_general_ci");
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "AspNetRoles",
|
||||
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ new Guid("b2e087e6-ea32-46c4-aeb3-09b936cd0cf4"), null, "Teacher", "TEACHER" },
|
||||
{ new Guid("ba33e047-8354-4f2c-b8b1-1f46441c28fc"), null, "Administrator", "ADMINISTRATOR" },
|
||||
{ new Guid("d4b41bc3-612e-49dd-aeda-6a98ea0e4e68"), null, "Student", "STUDENT" }
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DeleteData(
|
||||
table: "AspNetRoles",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("b2e087e6-ea32-46c4-aeb3-09b936cd0cf4"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "AspNetRoles",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("ba33e047-8354-4f2c-b8b1-1f46441c28fc"));
|
||||
|
||||
migrationBuilder.DeleteData(
|
||||
table: "AspNetRoles",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("d4b41bc3-612e-49dd-aeda-6a98ea0e4e68"));
|
||||
|
||||
migrationBuilder.AlterColumn<Guid>(
|
||||
name: "assignment",
|
||||
table: "assignment_group",
|
||||
type: "char(36)",
|
||||
nullable: false,
|
||||
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
|
||||
collation: "ascii_general_ci",
|
||||
oldClrType: typeof(Guid),
|
||||
oldType: "char(36)",
|
||||
oldNullable: true)
|
||||
.OldAnnotation("Relational:Collation", "ascii_general_ci");
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "AspNetRoles",
|
||||
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
|
||||
values: new object[,]
|
||||
{
|
||||
{ new Guid("02a808ba-bd16-4f90-bf2b-0bc42f767e00"), null, "Administrator", "ADMINISTRATOR" },
|
||||
{ new Guid("9e526681-e57e-46b5-a01c-5731b27bfc4a"), null, "Student", "STUDENT" },
|
||||
{ new Guid("dfdfb884-4063-4161-84e0-9c225f4e883c"), null, "Teacher", "TEACHER" }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -155,7 +155,7 @@ namespace TechHelper.Server.Migrations
|
||||
.HasColumnType("char(36)")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AssignmentId")
|
||||
b.Property<Guid?>("AssignmentId")
|
||||
.HasColumnType("char(36)")
|
||||
.HasColumnName("assignment");
|
||||
|
||||
@@ -177,7 +177,7 @@ namespace TechHelper.Server.Migrations
|
||||
|
||||
b.Property<Guid?>("ParentGroup")
|
||||
.HasColumnType("char(36)")
|
||||
.HasColumnName("sub_group");
|
||||
.HasColumnName("parent_group");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
@@ -185,10 +185,14 @@ namespace TechHelper.Server.Migrations
|
||||
.HasColumnType("longtext")
|
||||
.HasColumnName("title");
|
||||
|
||||
b.Property<decimal?>("TotalPoints")
|
||||
.HasColumnType("decimal(65,30)")
|
||||
b.Property<float?>("TotalPoints")
|
||||
.HasColumnType("float")
|
||||
.HasColumnName("total_points");
|
||||
|
||||
b.Property<bool>("ValidQuestionGroup")
|
||||
.HasColumnType("tinyint(1)")
|
||||
.HasColumnName("valid_question_group");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AssignmentId");
|
||||
@@ -207,7 +211,7 @@ namespace TechHelper.Server.Migrations
|
||||
|
||||
b.Property<Guid>("AssignmentGroupId")
|
||||
.HasColumnType("char(36)")
|
||||
.HasColumnName("detail_id");
|
||||
.HasColumnName("group_id");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime(6)")
|
||||
@@ -401,6 +405,10 @@ namespace TechHelper.Server.Migrations
|
||||
.HasColumnType("datetime(6)")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.Property<bool>("ValidQuestion")
|
||||
.HasColumnType("tinyint(1)")
|
||||
.HasColumnName("valid_question");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedBy");
|
||||
@@ -650,19 +658,19 @@ namespace TechHelper.Server.Migrations
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
Id = new Guid("ea0c88d8-1a52-4034-bb37-5a95043821eb"),
|
||||
Id = new Guid("d4b41bc3-612e-49dd-aeda-6a98ea0e4e68"),
|
||||
Name = "Student",
|
||||
NormalizedName = "STUDENT"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("9de22e41-c096-4d5a-b55a-ce0122aa3ada"),
|
||||
Id = new Guid("b2e087e6-ea32-46c4-aeb3-09b936cd0cf4"),
|
||||
Name = "Teacher",
|
||||
NormalizedName = "TEACHER"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("dee718d9-b731-485f-96bb-a59ce777870f"),
|
||||
Id = new Guid("ba33e047-8354-4f2c-b8b1-1f46441c28fc"),
|
||||
Name = "Administrator",
|
||||
NormalizedName = "ADMINISTRATOR"
|
||||
});
|
||||
@@ -821,8 +829,7 @@ namespace TechHelper.Server.Migrations
|
||||
b.HasOne("Entities.Contracts.Assignment", "Assignment")
|
||||
.WithMany("AssignmentGroups")
|
||||
.HasForeignKey("AssignmentId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("Entities.Contracts.AssignmentGroup", "ParentAssignmentGroup")
|
||||
.WithMany("ChildAssignmentGroups")
|
||||
|
@@ -10,6 +10,7 @@ using Microsoft.IdentityModel.Tokens;
|
||||
using System.Text;
|
||||
using TechHelper.Features;
|
||||
using TechHelper.Services;
|
||||
using TechHelper.Server.Services;
|
||||
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
@@ -83,6 +84,7 @@ builder.Services.AddScoped<IAuthenticationService, AuthenticationService>();
|
||||
builder.Services.AddScoped<IEmailSender, QEmailSender>();
|
||||
builder.Services.AddTransient<IUserRegistrationService, UserRegistrationService>();
|
||||
builder.Services.AddScoped<IClassService, ClassService>();
|
||||
builder.Services.AddScoped<IExamService, ExamService>();
|
||||
|
||||
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
|
@@ -3,8 +3,10 @@ using Entities.Contracts;
|
||||
using Entities.DTO;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MySqlConnector;
|
||||
using SharedDATA.Api;
|
||||
using TechHelper.Services;
|
||||
using static TechHelper.Context.AutoMapperProFile;
|
||||
|
||||
namespace TechHelper.Server.Services
|
||||
{
|
||||
@@ -81,6 +83,74 @@ namespace TechHelper.Server.Services
|
||||
}
|
||||
|
||||
|
||||
public async Task<IEnumerable<AssignmentGroup>> LoadFullGroupTree(Guid rootGroupId)
|
||||
{
|
||||
var query = @"
|
||||
WITH RECURSIVE GroupTree AS (
|
||||
SELECT
|
||||
ag.*,
|
||||
CAST(ag.`number` AS CHAR(255)) AS path
|
||||
FROM assignment_group ag
|
||||
WHERE ag.id = @rootId
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
c.*,
|
||||
CONCAT(ct.path, '.', c.`number`)
|
||||
FROM assignment_group c
|
||||
INNER JOIN GroupTree ct ON c.parent_group = ct.id
|
||||
)
|
||||
SELECT * FROM GroupTree ORDER BY path;
|
||||
";
|
||||
|
||||
// 执行查询
|
||||
var groups = await _unitOfWork.GetRepository<AssignmentGroup>()
|
||||
.FromSql(query, new MySqlParameter("rootId", rootGroupId))
|
||||
.ToListAsync();
|
||||
|
||||
// 内存中构建树结构
|
||||
var groupDict = groups.ToDictionary(g => g.Id);
|
||||
var root = groupDict[rootGroupId];
|
||||
|
||||
foreach (var group in groups)
|
||||
{
|
||||
if (group.ParentGroup != null && groupDict.TryGetValue(group.ParentGroup.Value, out var parent))
|
||||
{
|
||||
parent.ChildAssignmentGroups ??= new List<AssignmentGroup>();
|
||||
parent.ChildAssignmentGroups.Add(group);
|
||||
}
|
||||
}
|
||||
|
||||
return new List<AssignmentGroup> { root };
|
||||
}
|
||||
|
||||
public async Task LoadRecursiveAssignmentGroups(IEnumerable<AssignmentGroup> groups)
|
||||
{
|
||||
foreach (var group in groups.ToList())
|
||||
{
|
||||
|
||||
var loadedGroup = await _unitOfWork.GetRepository<AssignmentGroup>()
|
||||
.GetFirstOrDefaultAsync(
|
||||
predicate: ag => ag.Id == group.Id,
|
||||
include: source => source
|
||||
.Include(ag => ag.AssignmentQuestions.Where(aq => !aq.IsDeleted))
|
||||
.ThenInclude(aq => aq.Question)
|
||||
.Include(ag => ag.ChildAssignmentGroups)
|
||||
);
|
||||
|
||||
if (loadedGroup == null) continue;
|
||||
|
||||
group.ChildAssignmentGroups = loadedGroup.ChildAssignmentGroups;
|
||||
group.AssignmentQuestions = loadedGroup.AssignmentQuestions;
|
||||
|
||||
if (group.ChildAssignmentGroups is { Count: > 0 })
|
||||
{
|
||||
await LoadRecursiveAssignmentGroups(group.ChildAssignmentGroups);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ApiResponse> GetExamByIdAsync(Guid assignmentId)
|
||||
{
|
||||
try
|
||||
@@ -93,15 +163,17 @@ namespace TechHelper.Server.Services
|
||||
return ApiResponse.Error($"找不到 ID 为 {assignmentId} 的试卷。");
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 获取所有相关题组和题目,并过滤掉已删除的
|
||||
var allGroups = await _unitOfWork.GetRepository<AssignmentGroup>().GetAllAsync(
|
||||
var allGroups = await _unitOfWork.GetRepository<AssignmentGroup>().GetFirstOrDefaultAsync(
|
||||
predicate: ag => ag.AssignmentId == assignmentId && !ag.IsDeleted,
|
||||
include: source => source
|
||||
.Include(ag => ag.AssignmentQuestions.Where(aq => !aq.IsDeleted))
|
||||
.ThenInclude(aq => aq.Question)
|
||||
.Include(ag => ag.ChildAssignmentGroups)
|
||||
);
|
||||
await LoadRecursiveAssignmentGroups(allGroups.ChildAssignmentGroups);
|
||||
|
||||
if (allGroups == null || !allGroups.Any())
|
||||
if (allGroups == null || !allGroups.ChildAssignmentGroups.Any())
|
||||
{
|
||||
// 试卷存在但没有内容,返回一个空的 ExamDto
|
||||
return ApiResponse.Success("试卷没有内容。", new ExamDto
|
||||
@@ -113,10 +185,16 @@ namespace TechHelper.Server.Services
|
||||
});
|
||||
}
|
||||
|
||||
var rootGroups = allGroups
|
||||
.Where(ag => ag.ParentGroup == null)
|
||||
.OrderBy(ag => ag.Number)
|
||||
.ToList();
|
||||
var rootGroups = allGroups.ChildAssignmentGroups.ToList();
|
||||
|
||||
var rootqg = new QuestionGroupDto();
|
||||
|
||||
foreach (var ag in rootGroups.OrderBy(g => g.Number))
|
||||
{
|
||||
var agDto = MapAssignmentGroupToDto(ag);
|
||||
rootqg.SubQuestionGroups.Add(agDto);
|
||||
}
|
||||
|
||||
|
||||
// 递归映射到 ExamDto
|
||||
var examDto = new ExamDto
|
||||
@@ -125,7 +203,7 @@ namespace TechHelper.Server.Services
|
||||
AssignmentTitle = assignment.Title,
|
||||
Description = assignment.Description,
|
||||
SubjectArea = assignment.Submissions.ToString(),
|
||||
QuestionGroups = MapAssignmentGroupsToDto(rootGroups, allGroups)
|
||||
QuestionGroups = rootqg
|
||||
};
|
||||
|
||||
return ApiResponse.Success("试卷信息已成功获取。", examDto);
|
||||
@@ -137,9 +215,45 @@ namespace TechHelper.Server.Services
|
||||
}
|
||||
|
||||
|
||||
private List<QuestionGroupDto> MapAssignmentGroupsToDto(
|
||||
List<AssignmentGroup> currentLevelGroups,
|
||||
IEnumerable<AssignmentGroup> allFetchedGroups)
|
||||
public QuestionGroupDto MapAssignmentGroupToDto(AssignmentGroup ag)
|
||||
{
|
||||
// 创建当前节点的DTO
|
||||
var dto = new QuestionGroupDto
|
||||
{
|
||||
Title = ag.Title,
|
||||
Score = (int)(ag.TotalPoints ?? 0),
|
||||
Descript = ag.Descript,
|
||||
SubQuestions = ag.AssignmentQuestions?
|
||||
.OrderBy(aq => aq.QuestionNumber)
|
||||
.Select(aq => new SubQuestionDto
|
||||
{
|
||||
Index = aq.QuestionNumber,
|
||||
Stem = aq.Question?.QuestionText,
|
||||
Score = aq.Score ?? 0,
|
||||
SampleAnswer = aq.Question?.CorrectAnswer,
|
||||
QuestionType = aq.Question?.QuestionType.ToString(),
|
||||
DifficultyLevel = aq.Question?.DifficultyLevel.ToString(),
|
||||
Options = new List<OptionDto>() // 根据需要初始化
|
||||
}).ToList() ?? new List<SubQuestionDto>(),
|
||||
SubQuestionGroups = new List<QuestionGroupDto>() // 初始化子集合
|
||||
};
|
||||
|
||||
// 递归处理子组
|
||||
if (ag.ChildAssignmentGroups != null && ag.ChildAssignmentGroups.Count > 0)
|
||||
{
|
||||
foreach (var child in ag.ChildAssignmentGroups.OrderBy(c => c.Number))
|
||||
{
|
||||
var childDto = MapAssignmentGroupToDto(child); // 递归获取子DTO
|
||||
dto.SubQuestionGroups.Add(childDto); // 添加到当前节点的子集合
|
||||
}
|
||||
}
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
private List<QuestionGroupDto> MapAssignmentGroupsToDto2(
|
||||
List<AssignmentGroup> currentLevelGroups,
|
||||
IEnumerable<AssignmentGroup> allFetchedGroups)
|
||||
{
|
||||
var dtos = new List<QuestionGroupDto>();
|
||||
|
||||
@@ -147,25 +261,25 @@ namespace TechHelper.Server.Services
|
||||
{
|
||||
var groupDto = new QuestionGroupDto
|
||||
{
|
||||
|
||||
|
||||
Title = group.Title,
|
||||
Score = (int)(group.TotalPoints ?? 0),
|
||||
QuestionReference = group.Descript,
|
||||
Descript = group.Descript,
|
||||
SubQuestions = group.AssignmentQuestions
|
||||
.OrderBy(aq => aq.QuestionNumber)
|
||||
.Select(aq => new SubQuestionDto
|
||||
{
|
||||
Index = aq.QuestionNumber,
|
||||
Stem = aq.Question.QuestionText,
|
||||
Score = aq.Score?? 0, // 使用 AssignmentQuestion 上的 Score
|
||||
Score = aq.Score ?? 0,
|
||||
SampleAnswer = aq.Question.CorrectAnswer,
|
||||
QuestionType = aq.Question.QuestionType.ToString(),
|
||||
DifficultyLevel = aq.Question.DifficultyLevel.ToString(),
|
||||
Options = new List<OptionDto>() // 这里需要您根据实际存储方式填充 Option
|
||||
Options = new List<OptionDto>()
|
||||
}).ToList(),
|
||||
// 递归映射子题组
|
||||
SubQuestionGroups = MapAssignmentGroupsToDto(
|
||||
allFetchedGroups.Where(ag => ag.ParentGroup == group.Id && !ag.IsDeleted).ToList(), // 从所有已获取的组中筛选子组
|
||||
SubQuestionGroups = MapAssignmentGroupsToDto2(
|
||||
allFetchedGroups.Where(ag => ag.ParentGroup == group.Id && !ag.IsDeleted).ToList(),
|
||||
allFetchedGroups)
|
||||
};
|
||||
dtos.Add(groupDto);
|
||||
@@ -176,7 +290,7 @@ namespace TechHelper.Server.Services
|
||||
public async Task<TechHelper.Services.ApiResponse> SaveParsedExam(ExamDto examData)
|
||||
{
|
||||
// 获取当前登录用户
|
||||
var currentUser = await _userManager.GetUserAsync(null);
|
||||
var currentUser = await _userManager.FindByEmailAsync(examData.CreaterEmail);
|
||||
if (currentUser == null)
|
||||
{
|
||||
return ApiResponse.Error("未找到当前登录用户,无法保存试题。");
|
||||
@@ -204,16 +318,13 @@ namespace TechHelper.Server.Services
|
||||
|
||||
// 从 ExamDto.QuestionGroups 获取根题组。
|
||||
// 确保只有一个根题组,因为您的模型是“试卷只有一个根节点”。
|
||||
if (examData.QuestionGroups == null || examData.QuestionGroups.Count != 1)
|
||||
if (examData.QuestionGroups == null)
|
||||
{
|
||||
throw new ArgumentException("试卷必须包含且只能包含一个根题组。");
|
||||
}
|
||||
|
||||
// 递归处理根题组及其所有子题组和题目
|
||||
// 传入的 assignmentId 仅用于设置根题组的 AssignmentId 字段
|
||||
// 对于子题组,ProcessAndSaveAssignmentGroupsRecursive 会将 AssignmentId 设置为 null
|
||||
await ProcessAndSaveAssignmentGroupsRecursive(
|
||||
examData.QuestionGroups.Single(),
|
||||
examData.QuestionGroups,
|
||||
examData.SubjectArea.ToString(),
|
||||
assignmentId,
|
||||
null, // 根题组没有父级
|
||||
@@ -246,14 +357,12 @@ namespace TechHelper.Server.Services
|
||||
{
|
||||
Id = Guid.NewGuid(), // 后端生成 GUID
|
||||
Title = qgDto.Title,
|
||||
Descript = qgDto.QuestionReference,
|
||||
Descript = qgDto.Descript,
|
||||
TotalPoints = qgDto.Score,
|
||||
Number = (byte)qgDto.Index, // 使用 DTO 的 Index 作为 Number
|
||||
ParentGroup = parentAssignmentGroupId, // 设置父级题组 GUID
|
||||
|
||||
// 关键修正:只有当 parentAssignmentGroupId 为 null 时,才设置 AssignmentId
|
||||
// 这意味着当前题组是顶级题组
|
||||
AssignmentId = parentAssignmentGroupId == null ? assignmentId : Guid.Empty,
|
||||
Number = (byte)qgDto.Index,
|
||||
ValidQuestionGroup = qgDto.ValidQuestionGroup,
|
||||
ParentGroup = parentAssignmentGroupId,
|
||||
AssignmentId = parentAssignmentGroupId == null ? assignmentId : (Guid?)null,
|
||||
IsDeleted = false
|
||||
};
|
||||
await _unitOfWork.GetRepository<AssignmentGroup>().InsertAsync(newAssignmentGroup);
|
||||
@@ -268,10 +377,7 @@ namespace TechHelper.Server.Services
|
||||
newQuestion.CreatedAt = DateTime.UtcNow;
|
||||
newQuestion.UpdatedAt = DateTime.UtcNow;
|
||||
newQuestion.IsDeleted = false;
|
||||
newQuestion.SubjectArea = (SubjectAreaEnum)Enum.Parse(typeof(SubjectAreaEnum), subjectarea, true);
|
||||
|
||||
// 处理 Options:如果 Options 是 JSON 字符串或需要其他存储方式,在这里处理
|
||||
// 例如:newQuestion.QuestionText += (JsonConvert.SerializeObject(sqDto.Options));
|
||||
newQuestion.SubjectArea = EnumMappingHelpers.ParseEnumSafe(subjectarea, SubjectAreaEnum.Unknown);
|
||||
|
||||
await _unitOfWork.GetRepository<Question>().InsertAsync(newQuestion);
|
||||
|
||||
@@ -279,9 +385,9 @@ namespace TechHelper.Server.Services
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
QuestionId = newQuestion.Id,
|
||||
QuestionNumber = (byte)questionNumber, // 使用递增的 questionNumber
|
||||
AssignmentGroupId = newAssignmentGroup.Id, // 关联到当前题组
|
||||
Score = sqDto.Score, // 从 DTO 获取单个子题分数
|
||||
QuestionNumber = (byte)questionNumber,
|
||||
AssignmentGroupId = newAssignmentGroup.Id,
|
||||
Score = sqDto.Score,
|
||||
IsDeleted = false,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
@@ -302,5 +408,26 @@ namespace TechHelper.Server.Services
|
||||
createdById);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ApiResponse> GetAllExamPreview(Guid user)
|
||||
{
|
||||
try
|
||||
{
|
||||
var assignments = await _unitOfWork.GetRepository<Assignment>().GetAllAsync(
|
||||
predicate: a => a.CreatedBy == user && !a.IsDeleted);
|
||||
|
||||
if (assignments.Any())
|
||||
{
|
||||
var exam = _mapper.Map<IEnumerable<ExamDto>>(assignments);
|
||||
return ApiResponse.Success(result: exam);
|
||||
}
|
||||
|
||||
return ApiResponse.Error("你还没有创建任何试卷");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ApiResponse.Error($"查询出了一点问题 , 详细信息为: {ex.Message}, 请稍后再试");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,6 @@ namespace TechHelper.Server.Services
|
||||
{
|
||||
public interface IExamService : IBaseService<ExamDto, Guid>
|
||||
{
|
||||
|
||||
Task<ApiResponse> GetAllExamPreview(Guid user);
|
||||
}
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@
|
||||
}
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"XSDB": "Server=mysql.eazygame.cn;Port=13002;Database=tech_helper;User=root;Password=wx1998WX"
|
||||
"XSDB": "Server=mysql.eazygame.cn;Port=13002;Database=test;User=root;Password=wx1998WX"
|
||||
},
|
||||
"JWTSettings": {
|
||||
"securityKey": "MxcxQHVYVDQ0U3lqWkIwdjZlSGx4eFp6YnFpUGxodmc5Y3hPZk5vWm9MZEg2Y0I=",
|
||||
|
Reference in New Issue
Block a user