登录
This commit is contained in:
@@ -57,59 +57,64 @@ public sealed class AuthController(
|
|||||||
{
|
{
|
||||||
var name = request.Name.Trim();
|
var name = request.Name.Trim();
|
||||||
var studentNumber = request.StudentNumber.Trim();
|
var studentNumber = request.StudentNumber.Trim();
|
||||||
var student = await db.Students
|
var executionStrategy = db.Database.CreateExecutionStrategy();
|
||||||
.Include(x => x.AdministrativeClass)
|
return await executionStrategy.ExecuteAsync<ActionResult>(async () =>
|
||||||
.ThenInclude(x => x!.Major)
|
{
|
||||||
.FirstOrDefaultAsync(x =>
|
var student = await db.Students
|
||||||
x.Status == StudentStatus.Active &&
|
.Include(x => x.AdministrativeClass)
|
||||||
x.Name == name &&
|
.ThenInclude(x => x!.Major)
|
||||||
x.StudentNumber == studentNumber &&
|
.FirstOrDefaultAsync(x =>
|
||||||
x.EnrollmentYear == request.Grade &&
|
x.Status == StudentStatus.Active &&
|
||||||
x.AdministrativeClassId == request.AdministrativeClassId &&
|
x.Name == name &&
|
||||||
x.AdministrativeClass!.Grade == request.Grade &&
|
x.StudentNumber == studentNumber &&
|
||||||
x.AdministrativeClass.MajorId == request.MajorId &&
|
x.EnrollmentYear == request.Grade &&
|
||||||
x.AdministrativeClass.Major!.CollegeId == request.CollegeId,
|
x.AdministrativeClassId == request.AdministrativeClassId &&
|
||||||
cancellationToken);
|
x.AdministrativeClass!.Grade == request.Grade &&
|
||||||
if (student is null)
|
x.AdministrativeClass.MajorId == request.MajorId &&
|
||||||
return ActivationProblem(
|
x.AdministrativeClass.Major!.CollegeId == request.CollegeId,
|
||||||
"填写的信息与在籍学生档案不完全一致,请核对后重试。",
|
cancellationToken);
|
||||||
StatusCodes.Status400BadRequest);
|
if (student is null)
|
||||||
if (student.UserId.HasValue)
|
return ActivationProblem(
|
||||||
return ActivationProblem(
|
"填写的信息与在籍学生档案不完全一致,请核对后重试。",
|
||||||
"该学号已经激活,请直接登录;如忘记密码请联系管理员重置。",
|
StatusCodes.Status400BadRequest);
|
||||||
StatusCodes.Status409Conflict);
|
if (student.UserId.HasValue)
|
||||||
if (await userManager.FindByNameAsync(studentNumber) is not null)
|
return ActivationProblem(
|
||||||
return ActivationProblem(
|
"该学号已经激活,请直接登录;如忘记密码请联系管理员重置。",
|
||||||
"该学号已有登录账号但未正确关联,请联系管理员处理。",
|
StatusCodes.Status409Conflict);
|
||||||
StatusCodes.Status409Conflict);
|
if (await userManager.FindByNameAsync(studentNumber) is not null)
|
||||||
|
return ActivationProblem(
|
||||||
|
"该学号已有登录账号但未正确关联,请联系管理员处理。",
|
||||||
|
StatusCodes.Status409Conflict);
|
||||||
|
|
||||||
await using var transaction = await db.Database.BeginTransactionAsync(cancellationToken);
|
await using var transaction =
|
||||||
var user = new ApplicationUser
|
await db.Database.BeginTransactionAsync(cancellationToken);
|
||||||
{
|
var user = new ApplicationUser
|
||||||
UserName = studentNumber,
|
{
|
||||||
DisplayName = student.Name,
|
UserName = studentNumber,
|
||||||
StaffNumber = studentNumber,
|
DisplayName = student.Name,
|
||||||
CollegeId = request.CollegeId,
|
StaffNumber = studentNumber,
|
||||||
IsEnabled = true,
|
CollegeId = request.CollegeId,
|
||||||
LockoutEnabled = true
|
IsEnabled = true,
|
||||||
};
|
LockoutEnabled = true
|
||||||
var result = await userManager.CreateAsync(user, request.Password);
|
};
|
||||||
if (!result.Succeeded)
|
var result = await userManager.CreateAsync(user, request.Password);
|
||||||
{
|
if (!result.Succeeded)
|
||||||
await transaction.RollbackAsync(cancellationToken);
|
{
|
||||||
return IdentityValidationProblem(result);
|
await transaction.RollbackAsync(cancellationToken);
|
||||||
}
|
return IdentityValidationProblem(result);
|
||||||
result = await userManager.AddToRoleAsync(user, SystemRoles.Student);
|
}
|
||||||
if (!result.Succeeded)
|
result = await userManager.AddToRoleAsync(user, SystemRoles.Student);
|
||||||
{
|
if (!result.Succeeded)
|
||||||
await transaction.RollbackAsync(cancellationToken);
|
{
|
||||||
return IdentityValidationProblem(result);
|
await transaction.RollbackAsync(cancellationToken);
|
||||||
}
|
return IdentityValidationProblem(result);
|
||||||
|
}
|
||||||
|
|
||||||
student.UserId = user.Id;
|
student.UserId = user.Id;
|
||||||
await db.SaveChangesAsync(cancellationToken);
|
await db.SaveChangesAsync(cancellationToken);
|
||||||
await transaction.CommitAsync(cancellationToken);
|
await transaction.CommitAsync(cancellationToken);
|
||||||
return Ok(new { UserName = studentNumber });
|
return Ok(new { UserName = studentNumber });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
using Jiaowu.Api.Controllers;
|
||||||
|
using Jiaowu.Api.Domain.Academic;
|
||||||
|
using Jiaowu.Api.Domain.Identity;
|
||||||
|
using Jiaowu.Api.Infrastructure.Auth;
|
||||||
|
using Jiaowu.Api.Infrastructure.Persistence;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Jiaowu.Api.Tests;
|
||||||
|
|
||||||
|
public sealed class AuthControllerTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task ActivateStudent_WorksWithRetryingExecutionStrategy()
|
||||||
|
{
|
||||||
|
await using var connection = new SqliteConnection("Data Source=:memory:");
|
||||||
|
await connection.OpenAsync();
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
services.AddLogging();
|
||||||
|
services.AddDbContext<AppDbContext>(options => options
|
||||||
|
.UseSqlite(connection)
|
||||||
|
.ReplaceService<IExecutionStrategyFactory, RetryingExecutionStrategyFactory>());
|
||||||
|
services
|
||||||
|
.AddIdentityCore<ApplicationUser>(options =>
|
||||||
|
{
|
||||||
|
options.Password.RequiredLength = 8;
|
||||||
|
options.Password.RequireDigit = true;
|
||||||
|
options.Password.RequireLowercase = true;
|
||||||
|
options.Password.RequireUppercase = true;
|
||||||
|
options.Password.RequireNonAlphanumeric = true;
|
||||||
|
})
|
||||||
|
.AddRoles<ApplicationRole>()
|
||||||
|
.AddEntityFrameworkStores<AppDbContext>();
|
||||||
|
|
||||||
|
await using var provider = services.BuildServiceProvider();
|
||||||
|
await using var scope = provider.CreateAsyncScope();
|
||||||
|
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||||
|
await db.Database.EnsureCreatedAsync();
|
||||||
|
var roleManager = scope.ServiceProvider
|
||||||
|
.GetRequiredService<RoleManager<ApplicationRole>>();
|
||||||
|
Assert.True((await roleManager.CreateAsync(new ApplicationRole
|
||||||
|
{
|
||||||
|
Name = SystemRoles.Student,
|
||||||
|
Description = "学生"
|
||||||
|
})).Succeeded);
|
||||||
|
|
||||||
|
var college = new College { Code = "CS", Name = "计算机学院" };
|
||||||
|
var major = new Major
|
||||||
|
{
|
||||||
|
Code = "SE",
|
||||||
|
Name = "软件工程",
|
||||||
|
CollegeId = college.Id,
|
||||||
|
DegreeType = "工学"
|
||||||
|
};
|
||||||
|
var administrativeClass = new AdministrativeClass
|
||||||
|
{
|
||||||
|
Code = "SE202601",
|
||||||
|
Name = "软件工程 2026 级 1 班",
|
||||||
|
MajorId = major.Id,
|
||||||
|
Grade = 2026
|
||||||
|
};
|
||||||
|
var student = new Student
|
||||||
|
{
|
||||||
|
StudentNumber = "202601999",
|
||||||
|
Name = "测试学生",
|
||||||
|
AdministrativeClassId = administrativeClass.Id,
|
||||||
|
EnrollmentYear = 2026,
|
||||||
|
EnrollmentDate = new DateOnly(2026, 9, 1),
|
||||||
|
Status = StudentStatus.Active
|
||||||
|
};
|
||||||
|
db.AddRange(college, major, administrativeClass, student);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
var userManager = scope.ServiceProvider
|
||||||
|
.GetRequiredService<UserManager<ApplicationUser>>();
|
||||||
|
Assert.True(db.Database.CreateExecutionStrategy().RetriesOnFailure);
|
||||||
|
var controller = new AuthController(db, userManager, new StubTokenService());
|
||||||
|
var request = new StudentActivationRequest(
|
||||||
|
student.Name,
|
||||||
|
student.StudentNumber,
|
||||||
|
college.Id,
|
||||||
|
major.Id,
|
||||||
|
administrativeClass.Grade,
|
||||||
|
administrativeClass.Id,
|
||||||
|
"Student@123");
|
||||||
|
|
||||||
|
var result = await controller.ActivateStudent(request, CancellationToken.None);
|
||||||
|
|
||||||
|
Assert.IsType<OkObjectResult>(result);
|
||||||
|
var user = await userManager.FindByNameAsync(student.StudentNumber);
|
||||||
|
Assert.NotNull(user);
|
||||||
|
Assert.Equal(student.Name, user.DisplayName);
|
||||||
|
Assert.Equal(college.Id, user.CollegeId);
|
||||||
|
Assert.True(await userManager.IsInRoleAsync(user, SystemRoles.Student));
|
||||||
|
await db.Entry(student).ReloadAsync();
|
||||||
|
Assert.Equal(user.Id, student.UserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class RetryingExecutionStrategyFactory(
|
||||||
|
ExecutionStrategyDependencies dependencies) : IExecutionStrategyFactory
|
||||||
|
{
|
||||||
|
public IExecutionStrategy Create() => new TestRetryingExecutionStrategy(dependencies);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TestRetryingExecutionStrategy(
|
||||||
|
ExecutionStrategyDependencies dependencies)
|
||||||
|
: ExecutionStrategy(dependencies, 1, TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
protected override bool ShouldRetryOn(Exception exception) => false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class StubTokenService : ITokenService
|
||||||
|
{
|
||||||
|
public string Create(ApplicationUser user, IEnumerable<string> roles) => string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user