FixAuth
This commit is contained in:
@@ -14,11 +14,13 @@ namespace Entities.Contracts
|
|||||||
[Key]
|
[Key]
|
||||||
[Column("class_id")]
|
[Column("class_id")]
|
||||||
public Guid ClassId { get; set; }
|
public Guid ClassId { get; set; }
|
||||||
|
[ForeignKey(nameof(ClassId))]
|
||||||
public Class Class { get; set; }
|
public Class Class { get; set; }
|
||||||
|
|
||||||
[Key]
|
[Key]
|
||||||
[Column("teacher_id")]
|
[Column("teacher_id")]
|
||||||
public Guid TeacherId { get; set; }
|
public Guid TeacherId { get; set; }
|
||||||
|
[ForeignKey(nameof(TeacherId))]
|
||||||
public User Teacher { get; set; }
|
public User Teacher { get; set; }
|
||||||
|
|
||||||
[Column("subject_taught")]
|
[Column("subject_taught")]
|
||||||
|
@@ -21,7 +21,9 @@ namespace Entities.Contracts
|
|||||||
[Column("deleted")]
|
[Column("deleted")]
|
||||||
public bool IsDeleted { get; set; }
|
public bool IsDeleted { get; set; }
|
||||||
|
|
||||||
|
[InverseProperty(nameof(ClassTeacher.Teacher))]
|
||||||
public ICollection<ClassTeacher> TaughtClassesLink { get; set; }
|
public ICollection<ClassTeacher> TaughtClassesLink { get; set; }
|
||||||
|
[InverseProperty(nameof(ClassStudent.Student))]
|
||||||
public ICollection<ClassStudent> EnrolledClassesLink { get; set; }
|
public ICollection<ClassStudent> EnrolledClassesLink { get; set; }
|
||||||
|
|
||||||
public ICollection<Question> CreatedQuestions { get; set; }
|
public ICollection<Question> CreatedQuestions { get; set; }
|
||||||
|
@@ -24,9 +24,9 @@ namespace TechHelper.Client.Exam
|
|||||||
|
|
||||||
public class QuestionAnswerStatus
|
public class QuestionAnswerStatus
|
||||||
{
|
{
|
||||||
public string QuestionSequence { get; set; } = string.Empty; // 题目序号,例如 "1.1"
|
public string QuestionSequence { get; set; } = string.Empty; // 题目序号,例如 "1.1"
|
||||||
public string QuestionText { get; set; } = string.Empty; // 题目文本
|
public string QuestionText { get; set; } = string.Empty; // 题目文本
|
||||||
public float QuestionScore { get; set; } // 题目分值
|
public float QuestionScore { get; set; } // 题目分值
|
||||||
public Dictionary<Guid, bool> StudentCorrectStatus { get; set; } = new Dictionary<Guid, bool>();
|
public Dictionary<Guid, bool> StudentCorrectStatus { get; set; } = new Dictionary<Guid, bool>();
|
||||||
// Key: Student.Id, Value: true 表示正确,false 表示错误
|
// Key: Student.Id, Value: true 表示正确,false 表示错误
|
||||||
}
|
}
|
||||||
|
@@ -1,36 +0,0 @@
|
|||||||
using System.Xml.Serialization;
|
|
||||||
|
|
||||||
namespace TechHelper.Client.Exam.QuestionSimple
|
|
||||||
{
|
|
||||||
|
|
||||||
[XmlRoot("QG")]
|
|
||||||
public class QuestionGroup
|
|
||||||
{
|
|
||||||
[XmlElement("T")]
|
|
||||||
public string Title { get; set; }
|
|
||||||
[XmlElement("QR")]
|
|
||||||
public string QuestionReference { get; set; } = "";
|
|
||||||
[XmlArray("SQs")]
|
|
||||||
[XmlArrayItem("SQ")]
|
|
||||||
public List<SubQuestion> SubQuestions { get; set; } = new List<SubQuestion>();
|
|
||||||
[XmlArray("SQGs")]
|
|
||||||
[XmlArrayItem("QG")]
|
|
||||||
public List<QuestionGroup> SubQuestionGroups { get; set; } = new List<QuestionGroup>();
|
|
||||||
}
|
|
||||||
public class SubQuestion
|
|
||||||
{
|
|
||||||
[XmlElement("T")]
|
|
||||||
public string Stem { get; set; }
|
|
||||||
[XmlArray("Os")]
|
|
||||||
[XmlArrayItem("O")]
|
|
||||||
public List<Option> Options { get; set; } = new List<Option>();
|
|
||||||
[XmlElement("SA")]
|
|
||||||
public string SampleAnswer { get; set; } = "";
|
|
||||||
}
|
|
||||||
public class Option
|
|
||||||
{
|
|
||||||
[XmlAttribute("V")]
|
|
||||||
public string Value { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,19 +1,19 @@
|
|||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using TechHelper.Client.HttpRepository;
|
using TechHelper.Client.HttpRepository;
|
||||||
|
|
||||||
|
|
||||||
namespace BlazorProducts.Client.HttpInterceptor
|
namespace BlazorProducts.Client.HttpInterceptor
|
||||||
{
|
{
|
||||||
public class HttpInterceptorHandlerService : DelegatingHandler
|
public class HttpInterceptorHandlerService : DelegatingHandler
|
||||||
{
|
{
|
||||||
private readonly NavigationManager _navManager;
|
private readonly NavigationManager _navManager;
|
||||||
private readonly RefreshTokenService2 _refreshTokenService;
|
private readonly IRefreshTokenService _refreshTokenService;
|
||||||
|
|
||||||
public HttpInterceptorHandlerService(
|
public HttpInterceptorHandlerService(
|
||||||
NavigationManager navManager,
|
NavigationManager navManager,
|
||||||
RefreshTokenService2 refreshTokenService)
|
IRefreshTokenService refreshTokenService)
|
||||||
{
|
{
|
||||||
_navManager = navManager;
|
_navManager = navManager;
|
||||||
_refreshTokenService = refreshTokenService;
|
_refreshTokenService = refreshTokenService;
|
||||||
@@ -25,15 +25,14 @@ namespace BlazorProducts.Client.HttpInterceptor
|
|||||||
|
|
||||||
if (absolutePath != null && !absolutePath.Contains("token") && !absolutePath.Contains("account"))
|
if (absolutePath != null && !absolutePath.Contains("token") && !absolutePath.Contains("account"))
|
||||||
{
|
{
|
||||||
var token = await _refreshTokenService.TryRefreshToken();
|
var token = await _refreshTokenService.TryRefreshToken();
|
||||||
if (!string.IsNullOrEmpty(token))
|
if (!string.IsNullOrEmpty(token))
|
||||||
{
|
{
|
||||||
|
|
||||||
request.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
|
request.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var response = await base.SendAsync(request, cancellationToken);
|
var response = await base.SendAsync(request, cancellationToken);
|
||||||
|
|
||||||
await HandleResponse(response);
|
await HandleResponse(response);
|
||||||
@@ -45,17 +44,17 @@ namespace BlazorProducts.Client.HttpInterceptor
|
|||||||
{
|
{
|
||||||
if (response is null)
|
if (response is null)
|
||||||
{
|
{
|
||||||
|
|
||||||
_navManager.NavigateTo("/error");
|
_navManager.NavigateTo("/error");
|
||||||
throw new HttpResponseException("服务器不可用。");
|
throw new HttpResponseException("服务器不可用。");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
|
|
||||||
var errorContent = await response.Content.ReadAsStringAsync();
|
var errorContent = await response.Content.ReadAsStringAsync();
|
||||||
Console.WriteLine($"HTTP 错误: {response.StatusCode}. 详情: {errorContent}");
|
Console.WriteLine($"HTTP 错误: {response.StatusCode}. 详情: {errorContent}");
|
||||||
|
|
||||||
switch (response.StatusCode)
|
switch (response.StatusCode)
|
||||||
{
|
{
|
||||||
@@ -67,8 +66,15 @@ namespace BlazorProducts.Client.HttpInterceptor
|
|||||||
// 400 Bad Request error. Often, you don't navigate for this; you display validation errors on the UI.
|
// 400 Bad Request error. Often, you don't navigate for this; you display validation errors on the UI.
|
||||||
break;
|
break;
|
||||||
case HttpStatusCode.Unauthorized:
|
case HttpStatusCode.Unauthorized:
|
||||||
// 401 Unauthorized error. Navigate to an unauthorized page or login page.
|
var token = await _refreshTokenService.TryRefreshToken();
|
||||||
_navManager.NavigateTo("/unauthorized"); // Or: _navManager.NavigateTo("/authentication/login");
|
if (!string.IsNullOrEmpty(token))
|
||||||
|
{
|
||||||
|
_navManager.NavigateTo(_navManager.Uri, forceLoad: true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_navManager.NavigateTo("/unauthorized");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// For all other errors, navigate to a general error page.
|
// For all other errors, navigate to a general error page.
|
||||||
|
@@ -20,13 +20,12 @@ namespace TechHelper.Client.HttpRepository
|
|||||||
private readonly ILocalStorageService _localStorageService;
|
private readonly ILocalStorageService _localStorageService;
|
||||||
private readonly NavigationManager _navigationManager;
|
private readonly NavigationManager _navigationManager;
|
||||||
|
|
||||||
// 构造函数现在直接接收 HttpClient
|
public AuthenticationClientService(HttpClient client,
|
||||||
public AuthenticationClientService(HttpClient client, // <-- 修正点:直接注入 HttpClient
|
|
||||||
AuthenticationStateProvider authenticationStateProvider,
|
AuthenticationStateProvider authenticationStateProvider,
|
||||||
ILocalStorageService localStorageService,
|
ILocalStorageService localStorageService,
|
||||||
NavigationManager navigationManager)
|
NavigationManager navigationManager)
|
||||||
{
|
{
|
||||||
_client = client; // <-- 修正点:直接赋值
|
_client = client;
|
||||||
_localStorageService = localStorageService;
|
_localStorageService = localStorageService;
|
||||||
_stateProvider = authenticationStateProvider;
|
_stateProvider = authenticationStateProvider;
|
||||||
_navigationManager = navigationManager;
|
_navigationManager = navigationManager;
|
||||||
@@ -34,8 +33,6 @@ namespace TechHelper.Client.HttpRepository
|
|||||||
|
|
||||||
public async Task<AuthResponseDto> LoginAsync(UserForAuthenticationDto userForAuthenticationDto)
|
public async Task<AuthResponseDto> LoginAsync(UserForAuthenticationDto userForAuthenticationDto)
|
||||||
{
|
{
|
||||||
// 移除 using (_client = _clientFactory.CreateClient("Default"))
|
|
||||||
// _client 已经是注入的实例,直接使用它
|
|
||||||
var reponse = await _client.PostAsJsonAsync("account/login",
|
var reponse = await _client.PostAsJsonAsync("account/login",
|
||||||
userForAuthenticationDto);
|
userForAuthenticationDto);
|
||||||
|
|
||||||
@@ -71,7 +68,6 @@ namespace TechHelper.Client.HttpRepository
|
|||||||
|
|
||||||
public async Task<string> RefreshTokenAsync()
|
public async Task<string> RefreshTokenAsync()
|
||||||
{
|
{
|
||||||
// 移除 using (_client = _clientFactory.CreateClient("Default"))
|
|
||||||
var token = _localStorageService.GetItem<string>("authToken");
|
var token = _localStorageService.GetItem<string>("authToken");
|
||||||
var refreshToken = _localStorageService.GetItem<string>("refreshToken");
|
var refreshToken = _localStorageService.GetItem<string>("refreshToken");
|
||||||
|
|
||||||
@@ -167,6 +163,9 @@ namespace TechHelper.Client.HttpRepository
|
|||||||
((AuthStateProvider)_stateProvider).NotifyUserAuthentication(
|
((AuthStateProvider)_stateProvider).NotifyUserAuthentication(
|
||||||
result.Token);
|
result.Token);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
_client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(
|
_client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(
|
||||||
"bearer", result.Token);
|
"bearer", result.Token);
|
||||||
|
|
||||||
|
@@ -24,16 +24,15 @@ namespace TechHelper.Client.HttpRepository
|
|||||||
var authState = await _authenticationStateProvider.Value.GetAuthenticationStateAsync();
|
var authState = await _authenticationStateProvider.Value.GetAuthenticationStateAsync();
|
||||||
var user = authState.User;
|
var user = authState.User;
|
||||||
|
|
||||||
// 如果 user 或 claims 为空,表示用户未认证,直接返回空字符串
|
|
||||||
if (user?.Identity == null || !user.Identity.IsAuthenticated)
|
if (user?.Identity == null || !user.Identity.IsAuthenticated)
|
||||||
{
|
{
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
var expClaim = user.FindFirst(c => c.Type.Equals("exp"))?.Value; // 使用 ?. 防止空引用
|
var expClaim = user.FindFirst(c => c.Type.Equals("exp"))?.Value;
|
||||||
if (string.IsNullOrEmpty(expClaim))
|
if (string.IsNullOrEmpty(expClaim))
|
||||||
{
|
{
|
||||||
return string.Empty; // 没有过期时间声明,也直接返回
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
var expTime = DateTimeOffset.FromUnixTimeSeconds(
|
var expTime = DateTimeOffset.FromUnixTimeSeconds(
|
||||||
@@ -41,9 +40,11 @@ namespace TechHelper.Client.HttpRepository
|
|||||||
|
|
||||||
var diff = expTime - DateTime.UtcNow;
|
var diff = expTime - DateTime.UtcNow;
|
||||||
|
|
||||||
// 只有当令牌即将过期时才尝试刷新
|
|
||||||
|
var n = DateTime.UtcNow;
|
||||||
|
|
||||||
if (diff.TotalMinutes <= 2)
|
if (diff.TotalMinutes <= 2)
|
||||||
return await _authenticationClientService.Value.RefreshTokenAsync(); // 访问 .Value 来调用方法
|
return await _authenticationClientService.Value.RefreshTokenAsync();
|
||||||
|
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
@@ -1,46 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Components.Authorization;
|
|
||||||
|
|
||||||
namespace TechHelper.Client.HttpRepository
|
|
||||||
{
|
|
||||||
public class RefreshTokenService2
|
|
||||||
{
|
|
||||||
private readonly Lazy<AuthenticationStateProvider> _authenticationStateProvider;
|
|
||||||
private readonly Lazy<IAuthenticationClientService> _authenticationClientService;
|
|
||||||
|
|
||||||
public RefreshTokenService2(IServiceProvider serviceProvider)
|
|
||||||
{
|
|
||||||
_authenticationStateProvider = new Lazy<AuthenticationStateProvider>(
|
|
||||||
() => serviceProvider.GetRequiredService<AuthenticationStateProvider>());
|
|
||||||
|
|
||||||
_authenticationClientService = new Lazy<IAuthenticationClientService>(
|
|
||||||
() => serviceProvider.GetRequiredService<IAuthenticationClientService>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string> TryRefreshToken()
|
|
||||||
{
|
|
||||||
var authState = await _authenticationStateProvider.Value.GetAuthenticationStateAsync();
|
|
||||||
var user = authState.User;
|
|
||||||
|
|
||||||
if (user?.Identity == null || !user.Identity.IsAuthenticated)
|
|
||||||
{
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
var expClaim = user.FindFirst(c => c.Type.Equals("exp"))?.Value;
|
|
||||||
if (string.IsNullOrEmpty(expClaim))
|
|
||||||
{
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
var expTime = DateTimeOffset.FromUnixTimeSeconds(
|
|
||||||
Convert.ToInt64(expClaim));
|
|
||||||
|
|
||||||
var diff = expTime - DateTime.UtcNow;
|
|
||||||
|
|
||||||
if (diff.TotalMinutes <= 2)
|
|
||||||
return await _authenticationClientService.Value.RefreshTokenAsync();
|
|
||||||
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
10
TechHelper.Client/Pages/Exam/AssignmentDetailView.razor
Normal file
10
TechHelper.Client/Pages/Exam/AssignmentDetailView.razor
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<MudText> QuestionNum </MudText>
|
||||||
|
<MudText> QuestionTypeDis </MudText>
|
||||||
|
<MudText> ErrorQuestionTypeDis </MudText>
|
||||||
|
<MudText> ErrorLessonDis </MudText>
|
||||||
|
<MudText> ErrorIndexDis </MudText>
|
||||||
|
<MudText> ErrorNum </MudText>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
|
||||||
|
}
|
9
TechHelper.Client/Pages/Exam/QuestionDetailView.razor
Normal file
9
TechHelper.Client/Pages/Exam/QuestionDetailView.razor
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<MudText> Title </MudText>
|
||||||
|
<MudText> Error Person Num </MudText>
|
||||||
|
<MudText> List Error Person </MudText>
|
||||||
|
<MudText> Lesson </MudText>
|
||||||
|
<MudText> Keypoint </MudText>
|
||||||
|
<MudText> Answer </MudText>
|
||||||
|
@code {
|
||||||
|
|
||||||
|
}
|
@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Components.Authorization;
|
|||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
using System.Collections.Frozen;
|
using System.Collections.Frozen;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Security.Claims;
|
||||||
using TechHelper.Client.HttpRepository;
|
using TechHelper.Client.HttpRepository;
|
||||||
using TechHelper.Client.Services;
|
using TechHelper.Client.Services;
|
||||||
|
|
||||||
|
@@ -44,7 +44,6 @@ builder.Services.AddScoped<IRefreshTokenService, RefreshTokenService>();
|
|||||||
builder.Services.AddScoped<IClassServices, ClasssServices>();
|
builder.Services.AddScoped<IClassServices, ClasssServices>();
|
||||||
builder.Services.AddScoped<IEmailSender, QEmailSender>();
|
builder.Services.AddScoped<IEmailSender, QEmailSender>();
|
||||||
builder.Services.AddScoped<HttpInterceptorHandlerService>();
|
builder.Services.AddScoped<HttpInterceptorHandlerService>();
|
||||||
builder.Services.AddScoped<RefreshTokenService2>();
|
|
||||||
builder.Services.AddScoped<IAIService, AiService>();
|
builder.Services.AddScoped<IAIService, AiService>();
|
||||||
builder.Services.AddHttpClient("WebApiClient", client =>
|
builder.Services.AddHttpClient("WebApiClient", client =>
|
||||||
{
|
{
|
||||||
|
@@ -1,15 +1,18 @@
|
|||||||
using Entities.DTO;
|
using Entities.DTO;
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
|
using TechHelper.Client.HttpRepository;
|
||||||
|
|
||||||
namespace TechHelper.Client.Services
|
namespace TechHelper.Client.Services
|
||||||
{
|
{
|
||||||
public class ClasssServices : IClassServices
|
public class ClasssServices : IClassServices
|
||||||
{
|
{
|
||||||
private readonly HttpClient _client;
|
private readonly HttpClient _client;
|
||||||
|
private readonly IAuthenticationClientService _authenticationClientService;
|
||||||
|
|
||||||
public ClasssServices(HttpClient client)
|
public ClasssServices(HttpClient client, IAuthenticationClientService authenticationClientService)
|
||||||
{
|
{
|
||||||
_client = client;
|
_client = client;
|
||||||
|
_authenticationClientService = authenticationClientService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<ResponseDto> CreateClass(UserRegistrationToClassDto userClass)
|
public Task<ResponseDto> CreateClass(UserRegistrationToClassDto userClass)
|
||||||
@@ -25,6 +28,8 @@ namespace TechHelper.Client.Services
|
|||||||
userRegistrationToClassDto);
|
userRegistrationToClassDto);
|
||||||
var data = await result.Content.ReadAsStringAsync();
|
var data = await result.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
await _authenticationClientService.RefreshTokenAsync();
|
||||||
|
|
||||||
return new ResponseDto
|
return new ResponseDto
|
||||||
{
|
{
|
||||||
IsSuccessfulRegistration = result.IsSuccessStatusCode,
|
IsSuccessfulRegistration = result.IsSuccessStatusCode,
|
||||||
|
@@ -9,13 +9,13 @@ namespace TechHelper.Client.Services
|
|||||||
{
|
{
|
||||||
public class ExamService : IExamService
|
public class ExamService : IExamService
|
||||||
{
|
{
|
||||||
private readonly IAIService _aIService; // 遵循命名规范,字段前加下划线
|
private readonly IAIService _aIService;
|
||||||
private readonly HttpClient _client; // 直接注入 HttpClient
|
private readonly HttpClient _client;
|
||||||
|
|
||||||
public ExamService(IAIService aIService, HttpClient client) // 修正点:直接注入 HttpClient
|
public ExamService(IAIService aIService, HttpClient client)
|
||||||
{
|
{
|
||||||
_aIService = aIService;
|
_aIService = aIService;
|
||||||
_client = client; // 赋值注入的 HttpClient 实例
|
_client = client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ApiResponse ConvertToXML<T>(string xmlContent)
|
public ApiResponse ConvertToXML<T>(string xmlContent)
|
||||||
|
@@ -53,7 +53,7 @@ namespace TechHelper.Context.Configuration
|
|||||||
builder.HasOne(ct => ct.Teacher) // 当前 ClassTeacher 链接到一个 User (老师)
|
builder.HasOne(ct => ct.Teacher) // 当前 ClassTeacher 链接到一个 User (老师)
|
||||||
.WithMany(u => u.TaughtClassesLink) // 那个 User (老师) 可以有多个 ClassTeacher 记录 (为所教授的班级)
|
.WithMany(u => u.TaughtClassesLink) // 那个 User (老师) 可以有多个 ClassTeacher 记录 (为所教授的班级)
|
||||||
.HasForeignKey(ct => ct.TeacherId) // 外键是 ClassTeacher.TeacherId
|
.HasForeignKey(ct => ct.TeacherId) // 外键是 ClassTeacher.TeacherId
|
||||||
.OnDelete(DeleteBehavior.Restrict); // 当 User (老师) 被删除时,如果还有相关的 ClassTeacher 记录,则会阻止删除。
|
.OnDelete(DeleteBehavior.Cascade); // 当 User (老师) 被删除时,如果还有相关的 ClassTeacher 记录,则会阻止删除。
|
||||||
// 这通常是防止数据丢失的更安全选择。如果你希望老师被删除时,其所有任教关系也一并删除,可改为 DeleteBehavior.Cascade。
|
// 这通常是防止数据丢失的更安全选择。如果你希望老师被删除时,其所有任教关系也一并删除,可改为 DeleteBehavior.Cascade。
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1238
TechHelper.Server/Migrations/20250625032845_up.Designer.cs
generated
Normal file
1238
TechHelper.Server/Migrations/20250625032845_up.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
95
TechHelper.Server/Migrations/20250625032845_up.cs
Normal file
95
TechHelper.Server/Migrations/20250625032845_up.cs
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
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 up : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_class_teachers_AspNetUsers_teacher_id",
|
||||||
|
table: "class_teachers");
|
||||||
|
|
||||||
|
migrationBuilder.DeleteData(
|
||||||
|
table: "AspNetRoles",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("8b814a50-fd96-4bfa-a666-b247c0f13e22"));
|
||||||
|
|
||||||
|
migrationBuilder.DeleteData(
|
||||||
|
table: "AspNetRoles",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("ab2c8f8c-1ade-4ff5-9eb4-2925a89567b1"));
|
||||||
|
|
||||||
|
migrationBuilder.DeleteData(
|
||||||
|
table: "AspNetRoles",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("c6f92bbf-190f-47a5-b4d6-ed6874a378e8"));
|
||||||
|
|
||||||
|
migrationBuilder.InsertData(
|
||||||
|
table: "AspNetRoles",
|
||||||
|
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
|
||||||
|
values: new object[,]
|
||||||
|
{
|
||||||
|
{ new Guid("195b19c5-fd30-455c-9f38-9842b44bf5c3"), null, "Teacher", "TEACHER" },
|
||||||
|
{ new Guid("53cc63db-74bc-47a8-b71a-7e120d4018a9"), null, "Administrator", "ADMINISTRATOR" },
|
||||||
|
{ new Guid("a203eb76-97f0-418f-bc06-9549297d2ac3"), null, "Student", "STUDENT" }
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_class_teachers_AspNetUsers_teacher_id",
|
||||||
|
table: "class_teachers",
|
||||||
|
column: "teacher_id",
|
||||||
|
principalTable: "AspNetUsers",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_class_teachers_AspNetUsers_teacher_id",
|
||||||
|
table: "class_teachers");
|
||||||
|
|
||||||
|
migrationBuilder.DeleteData(
|
||||||
|
table: "AspNetRoles",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("195b19c5-fd30-455c-9f38-9842b44bf5c3"));
|
||||||
|
|
||||||
|
migrationBuilder.DeleteData(
|
||||||
|
table: "AspNetRoles",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("53cc63db-74bc-47a8-b71a-7e120d4018a9"));
|
||||||
|
|
||||||
|
migrationBuilder.DeleteData(
|
||||||
|
table: "AspNetRoles",
|
||||||
|
keyColumn: "Id",
|
||||||
|
keyValue: new Guid("a203eb76-97f0-418f-bc06-9549297d2ac3"));
|
||||||
|
|
||||||
|
migrationBuilder.InsertData(
|
||||||
|
table: "AspNetRoles",
|
||||||
|
columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" },
|
||||||
|
values: new object[,]
|
||||||
|
{
|
||||||
|
{ new Guid("8b814a50-fd96-4bfa-a666-b247c0f13e22"), null, "Administrator", "ADMINISTRATOR" },
|
||||||
|
{ new Guid("ab2c8f8c-1ade-4ff5-9eb4-2925a89567b1"), null, "Student", "STUDENT" },
|
||||||
|
{ new Guid("c6f92bbf-190f-47a5-b4d6-ed6874a378e8"), null, "Teacher", "TEACHER" }
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_class_teachers_AspNetUsers_teacher_id",
|
||||||
|
table: "class_teachers",
|
||||||
|
column: "teacher_id",
|
||||||
|
principalTable: "AspNetUsers",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -744,19 +744,19 @@ namespace TechHelper.Server.Migrations
|
|||||||
b.HasData(
|
b.HasData(
|
||||||
new
|
new
|
||||||
{
|
{
|
||||||
Id = new Guid("ab2c8f8c-1ade-4ff5-9eb4-2925a89567b1"),
|
Id = new Guid("a203eb76-97f0-418f-bc06-9549297d2ac3"),
|
||||||
Name = "Student",
|
Name = "Student",
|
||||||
NormalizedName = "STUDENT"
|
NormalizedName = "STUDENT"
|
||||||
},
|
},
|
||||||
new
|
new
|
||||||
{
|
{
|
||||||
Id = new Guid("c6f92bbf-190f-47a5-b4d6-ed6874a378e8"),
|
Id = new Guid("195b19c5-fd30-455c-9f38-9842b44bf5c3"),
|
||||||
Name = "Teacher",
|
Name = "Teacher",
|
||||||
NormalizedName = "TEACHER"
|
NormalizedName = "TEACHER"
|
||||||
},
|
},
|
||||||
new
|
new
|
||||||
{
|
{
|
||||||
Id = new Guid("8b814a50-fd96-4bfa-a666-b247c0f13e22"),
|
Id = new Guid("53cc63db-74bc-47a8-b71a-7e120d4018a9"),
|
||||||
Name = "Administrator",
|
Name = "Administrator",
|
||||||
NormalizedName = "ADMINISTRATOR"
|
NormalizedName = "ADMINISTRATOR"
|
||||||
});
|
});
|
||||||
@@ -982,7 +982,7 @@ namespace TechHelper.Server.Migrations
|
|||||||
b.HasOne("Entities.Contracts.User", "Teacher")
|
b.HasOne("Entities.Contracts.User", "Teacher")
|
||||||
.WithMany("TaughtClassesLink")
|
.WithMany("TaughtClassesLink")
|
||||||
.HasForeignKey("TeacherId")
|
.HasForeignKey("TeacherId")
|
||||||
.OnDelete(DeleteBehavior.Restrict)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.Navigation("Class");
|
b.Navigation("Class");
|
||||||
|
@@ -60,9 +60,14 @@ namespace TechHelper.Services
|
|||||||
var classInfo = await _classService.GetUserClass(user.Id);
|
var classInfo = await _classService.GetUserClass(user.Id);
|
||||||
if (classInfo.Status)
|
if (classInfo.Status)
|
||||||
{
|
{
|
||||||
(byte, byte) info = ((byte, byte))classInfo.Result;
|
var classs = classInfo.Result as List<Class>;
|
||||||
claims.Add(new Claim("Grade", info.Item1.ToString()));
|
|
||||||
claims.Add(new Claim("Class", info.Item2.ToString()));
|
classs?.ForEach(c =>
|
||||||
|
{
|
||||||
|
|
||||||
|
claims.Add(new Claim("Grade", c.Grade.ToString()));
|
||||||
|
claims.Add(new Claim("Class", c.Number.ToString()));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return claims;
|
return claims;
|
||||||
@@ -74,7 +79,7 @@ namespace TechHelper.Services
|
|||||||
issuer: _jwtSettings.ValidIssuer,
|
issuer: _jwtSettings.ValidIssuer,
|
||||||
audience: _jwtSettings.ValidAudience,
|
audience: _jwtSettings.ValidAudience,
|
||||||
claims: claims,
|
claims: claims,
|
||||||
expires: DateTime.Now.AddMinutes(Convert.ToDouble(
|
expires: DateTime.UtcNow.AddMinutes(Convert.ToDouble(
|
||||||
_jwtSettings.ExpiryInMinutes)),
|
_jwtSettings.ExpiryInMinutes)),
|
||||||
signingCredentials: signingCredentials);
|
signingCredentials: signingCredentials);
|
||||||
|
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
using Entities.Contracts;
|
using Entities.Contracts;
|
||||||
using Entities.DTO;
|
using Entities.DTO;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Org.BouncyCastle.Crypto;
|
using Org.BouncyCastle.Crypto;
|
||||||
using SharedDATA.Api;
|
using SharedDATA.Api;
|
||||||
|
|
||||||
@@ -105,7 +106,7 @@ namespace TechHelper.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 实现 IBaseService<ClassDto, int>.GetAsync
|
// 实现 IBaseService<ClassDto, int>.GetAsync
|
||||||
public async Task<ApiResponse> GetAsync(Guid id)
|
public async Task<ApiResponse> GetAsync(Guid id)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -126,13 +127,20 @@ namespace TechHelper.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ApiResponse> GetUserClass(Guid user)
|
public async Task<ApiResponse> GetUserClass(Guid id)
|
||||||
{
|
{
|
||||||
var existingUserClass = await _work.GetRepository<ClassStudent>().GetFirstOrDefaultAsync(predicate: c=>c.StudentId == user);
|
var tch = await _work.GetRepository<ClassTeacher>().GetAllAsync(predicate: user => user.TeacherId == id, include: i => i
|
||||||
if(existingUserClass == null) return new ApiResponse("该学生没有班级。");
|
.Include(t => t.Class));
|
||||||
|
var std = await _work.GetRepository<ClassStudent>().GetAllAsync(predicate: user => user.StudentId == id, include: i => i
|
||||||
|
.Include(t => t.Class));
|
||||||
|
|
||||||
var classId = await _work.GetRepository<Class>().GetFirstOrDefaultAsync(predicate: c => c.Id == existingUserClass.ClassId);
|
if (tch == null && std == null) return new ApiResponse("你没有加入任何班级。");
|
||||||
return new ApiResponse(true, (classId.Grade, classId.Number));
|
|
||||||
|
|
||||||
|
List<Class> result = new List<Class>();
|
||||||
|
tch?.ToList().ForEach(c => result.Add(c.Class));
|
||||||
|
std?.ToList().ForEach(c => result.Add(c.Class));
|
||||||
|
return ApiResponse.Success(result: result);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 实现 IBaseService<ClassDto, int>.UpdateAsync
|
// 实现 IBaseService<ClassDto, int>.UpdateAsync
|
||||||
@@ -142,14 +150,14 @@ namespace TechHelper.Services
|
|||||||
{
|
{
|
||||||
// 首先通过 ID 查找现有实体
|
// 首先通过 ID 查找现有实体
|
||||||
var existingClass = await _work.GetRepository<Class>().GetFirstOrDefaultAsync(
|
var existingClass = await _work.GetRepository<Class>().GetFirstOrDefaultAsync(
|
||||||
predicate: c => c.Number == model.Class);
|
predicate: c => c.Number == model.Class);
|
||||||
|
|
||||||
if (existingClass == null)
|
if (existingClass == null)
|
||||||
{
|
{
|
||||||
return new ApiResponse("班级未找到。");
|
return new ApiResponse("班级未找到。");
|
||||||
}
|
}
|
||||||
|
|
||||||
_mapper.Map(model, existingClass);
|
_mapper.Map(model, existingClass);
|
||||||
_work.GetRepository<Class>().Update(existingClass);
|
_work.GetRepository<Class>().Update(existingClass);
|
||||||
|
|
||||||
if (await _work.SaveChangesAsync() > 0)
|
if (await _work.SaveChangesAsync() > 0)
|
||||||
@@ -172,47 +180,87 @@ namespace TechHelper.Services
|
|||||||
var existingClass = await _work.GetRepository<Class>().GetFirstOrDefaultAsync(
|
var existingClass = await _work.GetRepository<Class>().GetFirstOrDefaultAsync(
|
||||||
predicate: (c => c.Number == user.ClassId && c.Grade == user.GradeId));
|
predicate: (c => c.Number == user.ClassId && c.Grade == user.GradeId));
|
||||||
|
|
||||||
var finduser = await _userManager.FindByEmailAsync(user.User);
|
// finduser and usrinfo are redundant if they are for the same user.
|
||||||
|
// Let's just use usrinfo
|
||||||
|
// var finduser = await _userManager.FindByEmailAsync(user.User);
|
||||||
|
|
||||||
if (existingClass == null || finduser == null || usrinfo == null)
|
if (existingClass == null || usrinfo == null) // Simplified check
|
||||||
{
|
{
|
||||||
return new ApiResponse("班级未找到。");
|
return new ApiResponse("班级或用户未找到。"); // More accurate message
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool registrationExists = false;
|
||||||
|
|
||||||
if (user.Roles == UserRoles.Student)
|
if (user.Roles == UserRoles.Student)
|
||||||
{
|
{
|
||||||
var addresult = await _work.GetRepository<ClassStudent>().InsertAsync(new ClassStudent
|
// Check for existing student registration
|
||||||
|
var existingStudentRegistration = await _work.GetRepository<ClassStudent>().GetFirstOrDefaultAsync(
|
||||||
|
predicate: cs => cs.StudentId == usrinfo.Id && cs.ClassId == existingClass.Id);
|
||||||
|
|
||||||
|
if (existingStudentRegistration != null)
|
||||||
{
|
{
|
||||||
StudentId = finduser.Id,
|
registrationExists = true;
|
||||||
ClassId = existingClass.Id
|
}
|
||||||
});
|
else
|
||||||
|
{
|
||||||
|
var addresult = await _work.GetRepository<ClassStudent>().InsertAsync(new ClassStudent
|
||||||
|
{
|
||||||
|
StudentId = usrinfo.Id, // Use usrinfo.Id
|
||||||
|
ClassId = existingClass.Id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await _userManager.AddToRoleAsync(usrinfo, UserRoles.Student.ToString());
|
await _userManager.AddToRoleAsync(usrinfo, UserRoles.Student.ToString());
|
||||||
}
|
}
|
||||||
else if (user.Roles == UserRoles.Teacher)
|
else if (user.Roles == UserRoles.Teacher)
|
||||||
{
|
{
|
||||||
var classTeacher = new ClassTeacher
|
// Check for existing teacher registration
|
||||||
|
var existingTeacherRegistration = await _work.GetRepository<ClassTeacher>().GetFirstOrDefaultAsync(
|
||||||
|
predicate: ct => ct.TeacherId == usrinfo.Id && ct.ClassId == existingClass.Id);
|
||||||
|
|
||||||
|
if (existingTeacherRegistration != null)
|
||||||
{
|
{
|
||||||
ClassId = existingClass.Id,
|
registrationExists = true;
|
||||||
TeacherId = existingClass.Id,
|
}
|
||||||
SubjectTaught = user.SubjectArea
|
else
|
||||||
};
|
{
|
||||||
await _work.GetRepository<ClassTeacher>().InsertAsync(classTeacher);
|
var classTeacher = new ClassTeacher
|
||||||
|
{
|
||||||
|
ClassId = existingClass.Id,
|
||||||
|
TeacherId = usrinfo.Id, // Use usrinfo.Id
|
||||||
|
SubjectTaught = user.SubjectArea
|
||||||
|
};
|
||||||
|
await _work.GetRepository<ClassTeacher>().InsertAsync(classTeacher);
|
||||||
|
}
|
||||||
|
|
||||||
await _userManager.AddToRoleAsync(usrinfo, UserRoles.Teacher.ToString());
|
await _userManager.AddToRoleAsync(usrinfo, UserRoles.Teacher.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (registrationExists)
|
||||||
|
{
|
||||||
|
return new ApiResponse("用户已在此班级注册,请勿重复注册。");
|
||||||
|
}
|
||||||
|
|
||||||
if (await _work.SaveChangesAsync() > 0)
|
if (await _work.SaveChangesAsync() > 0)
|
||||||
{
|
{
|
||||||
|
// It's possible SaveChangesAsync returns 0 even if a role was added
|
||||||
|
// A more robust check might be needed depending on your exact requirements.
|
||||||
return new ApiResponse(true, _mapper.Map<ClassDto>(existingClass));
|
return new ApiResponse(true, _mapper.Map<ClassDto>(existingClass));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If SaveChangesAsync returns 0 and registrationExists is false, it means no actual DB change occurred for the ClassStudent/ClassTeacher insert.
|
||||||
|
// This could happen if only the role was added, but the ClassStudent/Teacher was already there (and caught by the check).
|
||||||
|
// Or it means the insert failed for another reason not caught by the duplicate check.
|
||||||
|
// The previous error message "班级注册失败" is still appropriate in such cases.
|
||||||
return new ApiResponse("班级注册失败。");
|
return new ApiResponse("班级注册失败。");
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex) { return new ApiResponse($"注册进班级时发生错误: {ex.Message}"); }
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Log the exception details for debugging
|
||||||
|
// _logger.LogError(ex, "注册进班级时发生错误");
|
||||||
|
return new ApiResponse($"注册进班级时发生错误: {ex.Message}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@@ -8,5 +8,6 @@ namespace TechHelper.Services
|
|||||||
{
|
{
|
||||||
public Task<ApiResponse> UserRegister(UserRegistrationToClassDto user);
|
public Task<ApiResponse> UserRegister(UserRegistrationToClassDto user);
|
||||||
public Task<ApiResponse> GetUserClass(Guid user);
|
public Task<ApiResponse> GetUserClass(Guid user);
|
||||||
|
public Task<ApiResponse> GetClassStudents(ClassDto classDto);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user